Skip to main content

oximedia_packager/
config.rs

1//! Configuration types for adaptive streaming packaging.
2
3use crate::error::{PackagerError, PackagerResult};
4use serde::{Deserialize, Serialize};
5use std::path::PathBuf;
6use std::time::Duration;
7
8/// Packaging format.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
10pub enum PackagingFormat {
11    /// HLS with MPEG-TS segments.
12    HlsTs,
13    /// HLS with fragmented MP4 segments.
14    #[default]
15    HlsFmp4,
16    /// DASH with fragmented MP4 segments.
17    Dash,
18    /// Both HLS and DASH.
19    Both,
20}
21
22/// Segment format.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
24pub enum SegmentFormat {
25    /// MPEG-TS segments (HLS).
26    MpegTs,
27    /// Fragmented MP4 segments.
28    Fmp4,
29    /// CMAF segments.
30    Cmaf,
31}
32
33/// Encryption method.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
35pub enum EncryptionMethod {
36    /// No encryption.
37    #[default]
38    None,
39    /// AES-128 encryption (HLS).
40    Aes128,
41    /// SAMPLE-AES encryption (HLS).
42    SampleAes,
43    /// Common Encryption (CENC) for DASH.
44    Cenc,
45}
46
47/// Bitrate ladder entry.
48#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct BitrateEntry {
50    /// Target bitrate in bits per second.
51    pub bitrate: u32,
52    /// Video width in pixels.
53    pub width: u32,
54    /// Video height in pixels.
55    pub height: u32,
56    /// Video codec (AV1, VP9, VP8).
57    pub codec: String,
58    /// Frame rate (optional, defaults to source).
59    pub framerate: Option<f64>,
60}
61
62impl BitrateEntry {
63    /// Create a new bitrate entry.
64    #[must_use]
65    pub fn new(bitrate: u32, width: u32, height: u32, codec: &str) -> Self {
66        Self {
67            bitrate,
68            width,
69            height,
70            codec: codec.to_string(),
71            framerate: None,
72        }
73    }
74
75    /// Set the frame rate.
76    #[must_use]
77    pub fn with_framerate(mut self, fps: f64) -> Self {
78        self.framerate = Some(fps);
79        self
80    }
81}
82
83/// Bitrate ladder configuration.
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct BitrateLadder {
86    /// List of bitrate entries.
87    pub entries: Vec<BitrateEntry>,
88    /// Whether to generate automatically based on source.
89    pub auto_generate: bool,
90}
91
92impl Default for BitrateLadder {
93    fn default() -> Self {
94        Self {
95            entries: Vec::new(),
96            auto_generate: true,
97        }
98    }
99}
100
101impl BitrateLadder {
102    /// Create a new bitrate ladder.
103    #[must_use]
104    pub fn new() -> Self {
105        Self::default()
106    }
107
108    /// Add a bitrate entry.
109    pub fn add_entry(&mut self, entry: BitrateEntry) {
110        self.entries.push(entry);
111    }
112
113    /// Enable auto-generation.
114    #[must_use]
115    pub fn with_auto_generate(mut self, enabled: bool) -> Self {
116        self.auto_generate = enabled;
117        self
118    }
119
120    /// Validate the ladder configuration.
121    pub fn validate(&self) -> PackagerResult<()> {
122        if !self.auto_generate && self.entries.is_empty() {
123            return Err(PackagerError::invalid_config(
124                "Bitrate ladder has no entries and auto-generation is disabled",
125            ));
126        }
127
128        for entry in &self.entries {
129            if entry.bitrate == 0 {
130                return Err(PackagerError::invalid_config("Bitrate cannot be zero"));
131            }
132            if entry.width == 0 || entry.height == 0 {
133                return Err(PackagerError::invalid_config(
134                    "Width and height must be greater than zero",
135                ));
136            }
137        }
138
139        Ok(())
140    }
141}
142
143/// Segment configuration.
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct SegmentConfig {
146    /// Target segment duration.
147    pub duration: Duration,
148    /// Segment format.
149    pub format: SegmentFormat,
150    /// Enable keyframe alignment.
151    pub keyframe_alignment: bool,
152    /// Enable fast start (moov before mdat).
153    pub fast_start: bool,
154}
155
156impl Default for SegmentConfig {
157    fn default() -> Self {
158        Self {
159            duration: Duration::from_secs(6),
160            format: SegmentFormat::Fmp4,
161            keyframe_alignment: true,
162            fast_start: true,
163        }
164    }
165}
166
167/// Encryption configuration.
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct EncryptionConfig {
170    /// Encryption method.
171    pub method: EncryptionMethod,
172    /// Encryption key (16 bytes for AES-128).
173    pub key: Option<Vec<u8>>,
174    /// Key URI for HLS.
175    pub key_uri: Option<String>,
176    /// IV (initialization vector).
177    pub iv: Option<Vec<u8>>,
178}
179
180impl Default for EncryptionConfig {
181    fn default() -> Self {
182        Self {
183            method: EncryptionMethod::None,
184            key: None,
185            key_uri: None,
186            iv: None,
187        }
188    }
189}
190
191impl EncryptionConfig {
192    /// Check if encryption is enabled.
193    #[must_use]
194    pub fn is_enabled(&self) -> bool {
195        self.method != EncryptionMethod::None
196    }
197
198    /// Validate the encryption configuration.
199    pub fn validate(&self) -> PackagerResult<()> {
200        if !self.is_enabled() {
201            return Ok(());
202        }
203
204        match self.method {
205            EncryptionMethod::None => Ok(()),
206            EncryptionMethod::Aes128 | EncryptionMethod::SampleAes => {
207                if self.key.is_none() {
208                    return Err(PackagerError::invalid_config("Encryption key is required"));
209                }
210                if let Some(key) = &self.key {
211                    if key.len() != 16 {
212                        return Err(PackagerError::invalid_config(
213                            "AES-128 key must be 16 bytes",
214                        ));
215                    }
216                }
217                if self.key_uri.is_none() {
218                    return Err(PackagerError::invalid_config("Key URI is required for HLS"));
219                }
220                Ok(())
221            }
222            EncryptionMethod::Cenc => {
223                if self.key.is_none() {
224                    return Err(PackagerError::invalid_config("Encryption key is required"));
225                }
226                Ok(())
227            }
228        }
229    }
230}
231
232/// Output configuration.
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct OutputConfig {
235    /// Output directory.
236    pub directory: PathBuf,
237    /// Base URL for manifests.
238    pub base_url: Option<String>,
239    /// Enable S3 upload.
240    pub s3_upload: bool,
241    /// S3 bucket name.
242    pub s3_bucket: Option<String>,
243    /// S3 key prefix.
244    pub s3_prefix: Option<String>,
245    /// Keep local files after upload.
246    pub keep_local: bool,
247    /// Maximum number of segments to keep.
248    pub max_segments: Option<usize>,
249}
250
251impl Default for OutputConfig {
252    fn default() -> Self {
253        Self {
254            directory: PathBuf::from("output"),
255            base_url: None,
256            s3_upload: false,
257            s3_bucket: None,
258            s3_prefix: None,
259            keep_local: true,
260            max_segments: None,
261        }
262    }
263}
264
265/// Main packager configuration.
266#[derive(Debug, Clone, Serialize, Deserialize, Default)]
267pub struct PackagerConfig {
268    /// Packaging format.
269    pub format: PackagingFormat,
270    /// Bitrate ladder.
271    pub ladder: BitrateLadder,
272    /// Segment configuration.
273    pub segment: SegmentConfig,
274    /// Encryption configuration.
275    pub encryption: EncryptionConfig,
276    /// Output configuration.
277    pub output: OutputConfig,
278    /// Enable low latency mode.
279    pub low_latency: bool,
280    /// Manifest version tracking.
281    pub manifest_versioning: bool,
282    /// Optional explicit variant set for multi-variant playlist generation.
283    ///
284    /// When set, `MultivariantPlaylistBuilder` uses this set directly instead
285    /// of deriving variants from the bitrate ladder.
286    #[serde(skip)]
287    pub variant_set: Option<crate::variant_stream::VariantSet>,
288}
289
290impl PackagerConfig {
291    /// Create a new packager configuration.
292    #[must_use]
293    pub fn new() -> Self {
294        Self::default()
295    }
296
297    /// Set the packaging format.
298    #[must_use]
299    pub fn with_format(mut self, format: PackagingFormat) -> Self {
300        self.format = format;
301        self
302    }
303
304    /// Set the bitrate ladder.
305    #[must_use]
306    pub fn with_ladder(mut self, ladder: BitrateLadder) -> Self {
307        self.ladder = ladder;
308        self
309    }
310
311    /// Set the segment configuration.
312    #[must_use]
313    pub fn with_segment_config(mut self, segment: SegmentConfig) -> Self {
314        self.segment = segment;
315        self
316    }
317
318    /// Set the encryption configuration.
319    #[must_use]
320    pub fn with_encryption(mut self, encryption: EncryptionConfig) -> Self {
321        self.encryption = encryption;
322        self
323    }
324
325    /// Set the output configuration.
326    #[must_use]
327    pub fn with_output(mut self, output: OutputConfig) -> Self {
328        self.output = output;
329        self
330    }
331
332    /// Enable low latency mode.
333    #[must_use]
334    pub fn with_low_latency(mut self, enabled: bool) -> Self {
335        self.low_latency = enabled;
336        self
337    }
338
339    /// Set an explicit variant set for multi-variant playlist generation.
340    #[must_use]
341    pub fn with_variant_set(mut self, vs: crate::variant_stream::VariantSet) -> Self {
342        self.variant_set = Some(vs);
343        self
344    }
345
346    /// Validate the configuration.
347    pub fn validate(&self) -> PackagerResult<()> {
348        self.ladder.validate()?;
349        self.encryption.validate()?;
350
351        if self.output.s3_upload && self.output.s3_bucket.is_none() {
352            return Err(PackagerError::invalid_config("S3 bucket is required"));
353        }
354
355        if let Some(vs) = &self.variant_set {
356            vs.validate()?;
357        }
358
359        Ok(())
360    }
361}