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}