Skip to main content

oximedia_packager/
lib.rs

1//! Adaptive streaming packaging for `OxiMedia`.
2//!
3//! `oximedia-packager` provides comprehensive support for packaging media content into
4//! adaptive streaming formats:
5//!
6//! - **HLS (HTTP Live Streaming)**: Master playlists, media playlists, TS/fMP4 segments
7//! - **DASH (Dynamic Adaptive Streaming over HTTP)**: MPD manifests, CMAF segments
8//!
9//! # Features
10//!
11//! - Automatic bitrate ladder generation
12//! - Multi-bitrate variant streams
13//! - Segment creation (TS, fMP4, CMAF)
14//! - Manifest generation (M3U8, MPD)
15//! - Encryption support (AES-128, SAMPLE-AES, CENC)
16//! - Keyframe alignment
17//! - Fast start optimization
18//! - S3/cloud upload integration
19//! - Live and VOD packaging
20//!
21//! # Green List Only
22//!
23//! Like all `OxiMedia` crates, only patent-free codecs are supported:
24//!
25//! | Video | Audio | Subtitle |
26//! |-------|-------|----------|
27//! | AV1   | Opus  | `WebVTT`   |
28//! | VP9   | Vorbis| SRT      |
29//! | VP8   | FLAC  |          |
30//!
31//! # Example: HLS Packaging
32//!
33//! ```ignore
34//! use oximedia_packager::{Packager, PackagerConfig};
35//! use oximedia_packager::config::PackagingFormat;
36//! use std::time::Duration;
37//!
38//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
39//! // Create configuration
40//! let config = PackagerConfig::new()
41//!     .with_format(PackagingFormat::HlsFmp4);
42//!
43//! // Create packager
44//! let mut packager = Packager::new(config)?;
45//!
46//! // Package video to HLS
47//! packager.package_hls("input.mkv", "output/hls").await?;
48//! # Ok(())
49//! # }
50//! ```
51//!
52//! # Example: DASH Packaging
53//!
54//! ```ignore
55//! use oximedia_packager::{Packager, PackagerConfig};
56//! use oximedia_packager::config::PackagingFormat;
57//!
58//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
59//! let config = PackagerConfig::new()
60//!     .with_format(PackagingFormat::Dash);
61//!
62//! let mut packager = Packager::new(config)?;
63//! packager.package_dash("input.mkv", "output/dash").await?;
64//! # Ok(())
65//! # }
66//! ```
67//!
68//! # Example: Custom Bitrate Ladder
69//!
70//! ```ignore
71//! use oximedia_packager::config::{BitrateEntry, BitrateLadder, PackagerConfig};
72//!
73//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
74//! let mut ladder = BitrateLadder::new();
75//!
76//! // Add 1080p variant
77//! ladder.add_entry(BitrateEntry::new(5_000_000, 1920, 1080, "av1"));
78//!
79//! // Add 720p variant
80//! ladder.add_entry(BitrateEntry::new(3_000_000, 1280, 720, "av1"));
81//!
82//! // Add 480p variant
83//! ladder.add_entry(BitrateEntry::new(1_500_000, 854, 480, "av1"));
84//!
85//! let config = PackagerConfig::new().with_ladder(ladder);
86//! # Ok(())
87//! # }
88//! ```
89//!
90//! # Example: With Encryption
91//!
92//! ```ignore
93//! use oximedia_packager::config::{EncryptionConfig, EncryptionMethod, PackagerConfig};
94//! use oximedia_packager::encryption::KeyGenerator;
95//!
96//! # fn example() -> Result<(), Box<dyn std::error::Error>> {
97//! // Generate encryption key
98//! let key = KeyGenerator::generate_aes128_key();
99//! let iv = KeyGenerator::generate_iv();
100//!
101//! let mut encryption = EncryptionConfig::default();
102//! encryption.method = EncryptionMethod::Aes128;
103//! encryption.key = Some(key);
104//! encryption.iv = Some(iv);
105//! encryption.key_uri = Some("https://example.com/key".to_string());
106//!
107//! let config = PackagerConfig::new().with_encryption(encryption);
108//! # Ok(())
109//! # }
110//! ```
111
112#![warn(missing_docs)]
113#![allow(clippy::module_name_repetitions)]
114#![allow(clippy::missing_errors_doc)]
115#![allow(clippy::missing_panics_doc)]
116
117pub mod audio_track;
118pub mod bandwidth_estimator;
119pub mod bitrate_calc;
120pub mod byte_range;
121pub mod cmaf;
122pub mod cmaf_byterange;
123pub mod config;
124pub mod content_boundary;
125pub mod dash;
126pub mod dash_event_stream;
127pub mod drm_info;
128pub mod drm_packager;
129pub mod encryption;
130pub mod encryption_info;
131pub mod error;
132pub mod hls;
133pub mod hls_interstitial;
134pub mod isobmff_writer;
135pub mod keyframe_alignment;
136pub mod ladder;
137pub mod low_latency;
138pub mod manifest;
139pub mod manifest_builder;
140pub mod manifest_update;
141pub mod metadata_cache;
142pub mod multivariant;
143pub mod multivariant_builder;
144pub mod output;
145pub mod packaging_config;
146pub mod packaging_optimizer;
147pub mod parallel_packager;
148pub mod playlist_generator;
149pub mod pre_roll;
150pub mod pssh;
151pub mod scene_segmenter;
152pub mod segment;
153pub mod segment_index;
154pub mod segment_list;
155pub mod segment_naming;
156pub mod segment_timeline;
157pub mod segment_validator;
158pub mod ssai;
159pub mod streaming_output;
160pub mod subtitle_track;
161pub mod thumbnail_track;
162pub mod timed_metadata;
163pub mod variant_stream;
164
165// Re-export commonly used types
166pub use config::{
167    BitrateEntry, BitrateLadder, EncryptionConfig, EncryptionMethod, OutputConfig, PackagerConfig,
168    PackagingFormat, SegmentConfig, SegmentFormat,
169};
170pub use dash::{DashPackager, DashPackagerBuilder};
171pub use error::{PackagerError, PackagerResult};
172pub use hls::{HlsPackager, HlsPackagerBuilder};
173pub use ladder::{BitrateLadderGenerator, LadderGenerator, LadderRung, SourceAnalysis, SourceInfo};
174pub use multivariant_builder::{
175    DashAdaptationSetBuilder, HlsMultivariantBuilder, MultivariantPlaylistBuilder,
176};
177pub use variant_stream::{StreamCodec, VariantSet, VariantStream};
178
179/// Main packager for adaptive streaming.
180pub struct Packager {
181    config: PackagerConfig,
182}
183
184impl Packager {
185    /// Create a new packager with the given configuration.
186    pub fn new(config: PackagerConfig) -> PackagerResult<Self> {
187        config.validate()?;
188        Ok(Self { config })
189    }
190
191    /// Package input to HLS format.
192    ///
193    /// # Arguments
194    ///
195    /// * `input` - Path to input media file
196    /// * `output` - Output directory path
197    ///
198    /// # Example
199    ///
200    /// ```ignore
201    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
202    /// let config = PackagerConfig::new();
203    /// let mut packager = Packager::new(config)?;
204    /// packager.package_hls("input.mkv", "output/hls").await?;
205    /// # Ok(())
206    /// # }
207    /// ```
208    pub async fn package_hls(&self, input: &str, output: &str) -> PackagerResult<()> {
209        let mut config = self.config.clone();
210        config.output.directory = output.into();
211
212        let mut hls_packager = HlsPackager::new(config)?;
213        hls_packager.package(input).await?;
214
215        Ok(())
216    }
217
218    /// Package input to DASH format.
219    ///
220    /// # Arguments
221    ///
222    /// * `input` - Path to input media file
223    /// * `output` - Output directory path
224    ///
225    /// # Example
226    ///
227    /// ```ignore
228    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
229    /// let config = PackagerConfig::new();
230    /// let mut packager = Packager::new(config)?;
231    /// packager.package_dash("input.mkv", "output/dash").await?;
232    /// # Ok(())
233    /// # }
234    /// ```
235    pub async fn package_dash(&self, input: &str, output: &str) -> PackagerResult<()> {
236        let mut config = self.config.clone();
237        config.output.directory = output.into();
238
239        let mut dash_packager = DashPackager::new(config)?;
240        dash_packager.package(input).await?;
241
242        Ok(())
243    }
244
245    /// Package input to both HLS and DASH formats.
246    ///
247    /// # Arguments
248    ///
249    /// * `input` - Path to input media file
250    /// * `output_base` - Base output directory path
251    ///
252    /// This will create:
253    /// - `{output_base}/hls/` for HLS output
254    /// - `{output_base}/dash/` for DASH output
255    pub async fn package_both(&self, input: &str, output_base: &str) -> PackagerResult<()> {
256        use std::path::PathBuf;
257
258        let hls_output = PathBuf::from(output_base).join("hls");
259        let dash_output = PathBuf::from(output_base).join("dash");
260
261        let hls_str = hls_output.to_str().ok_or_else(|| {
262            PackagerError::InvalidConfig(format!(
263                "HLS output path contains invalid UTF-8: {}",
264                hls_output.display()
265            ))
266        })?;
267
268        let dash_str = dash_output.to_str().ok_or_else(|| {
269            PackagerError::InvalidConfig(format!(
270                "DASH output path contains invalid UTF-8: {}",
271                dash_output.display()
272            ))
273        })?;
274
275        self.package_hls(input, hls_str).await?;
276        self.package_dash(input, dash_str).await?;
277
278        Ok(())
279    }
280
281    /// Get the packager configuration.
282    #[must_use]
283    pub fn config(&self) -> &PackagerConfig {
284        &self.config
285    }
286}
287
288/// Builder for creating a packager with fluent configuration.
289pub struct PackagerBuilder {
290    config: PackagerConfig,
291}
292
293impl PackagerBuilder {
294    /// Create a new packager builder.
295    #[must_use]
296    pub fn new() -> Self {
297        Self {
298            config: PackagerConfig::default(),
299        }
300    }
301
302    /// Set the packaging format.
303    #[must_use]
304    pub fn format(mut self, format: PackagingFormat) -> Self {
305        self.config.format = format;
306        self
307    }
308
309    /// Set the bitrate ladder.
310    #[must_use]
311    pub fn ladder(mut self, ladder: BitrateLadder) -> Self {
312        self.config.ladder = ladder;
313        self
314    }
315
316    /// Set the segment configuration.
317    #[must_use]
318    pub fn segment_config(mut self, segment: SegmentConfig) -> Self {
319        self.config.segment = segment;
320        self
321    }
322
323    /// Set the encryption configuration.
324    #[must_use]
325    pub fn encryption(mut self, encryption: EncryptionConfig) -> Self {
326        self.config.encryption = encryption;
327        self
328    }
329
330    /// Set the output configuration.
331    #[must_use]
332    pub fn output(mut self, output: OutputConfig) -> Self {
333        self.config.output = output;
334        self
335    }
336
337    /// Enable low latency mode.
338    #[must_use]
339    pub fn low_latency(mut self, enabled: bool) -> Self {
340        self.config.low_latency = enabled;
341        self
342    }
343
344    /// Enable manifest versioning.
345    #[must_use]
346    pub fn manifest_versioning(mut self, enabled: bool) -> Self {
347        self.config.manifest_versioning = enabled;
348        self
349    }
350
351    /// Build the packager.
352    pub fn build(self) -> PackagerResult<Packager> {
353        Packager::new(self.config)
354    }
355}
356
357impl Default for PackagerBuilder {
358    fn default() -> Self {
359        Self::new()
360    }
361}
362
363#[cfg(test)]
364mod tests {
365    use super::*;
366
367    #[test]
368    fn test_packager_creation() {
369        let config = PackagerConfig::default();
370        let packager = Packager::new(config);
371        assert!(packager.is_ok());
372    }
373
374    #[test]
375    fn test_packager_builder() {
376        let packager = PackagerBuilder::new()
377            .format(PackagingFormat::HlsFmp4)
378            .low_latency(true)
379            .build();
380
381        assert!(packager.is_ok());
382        let p = packager.expect("should succeed in test");
383        assert_eq!(p.config.format, PackagingFormat::HlsFmp4);
384        assert!(p.config.low_latency);
385    }
386
387    #[test]
388    fn test_bitrate_ladder_validation() {
389        let mut ladder = BitrateLadder::new();
390        ladder.add_entry(BitrateEntry::new(1_000_000, 1280, 720, "av1"));
391
392        let config = PackagerConfig::new().with_ladder(ladder);
393        assert!(config.validate().is_ok());
394    }
395
396    #[test]
397    fn test_package_both_valid_paths() {
398        // Ensure package_both constructs valid paths from ASCII base
399        let config = PackagerConfig::default();
400        let packager = Packager::new(config).expect("should succeed in test");
401        // We can't run async here easily, but we verify the builder works
402        assert_eq!(packager.config().format, PackagingFormat::HlsFmp4);
403    }
404
405    #[test]
406    fn test_packager_builder_with_encryption() {
407        let mut enc = EncryptionConfig::default();
408        enc.method = EncryptionMethod::None;
409        let packager = PackagerBuilder::new().encryption(enc).build();
410        assert!(packager.is_ok());
411    }
412}