Skip to main content

oximedia_transcode/
lib.rs

1//! High-level transcoding pipeline for `OxiMedia`.
2//!
3//! This crate provides a comprehensive, professional-grade transcoding system with:
4//!
5//! # Features
6//!
7//! ## High-Level API
8//!
9//! - **Simple One-Liner Transcoding** - Quick transcoding with sensible defaults
10//! - **Preset Library** - Industry-standard presets for major platforms
11//! - **Fluent Builder API** - Complex workflows with readable code
12//!
13//! ## Professional Features
14//!
15//! - **Multi-Pass Encoding** - 2-pass and 3-pass encoding for optimal quality
16//! - **ABR Ladder Generation** - Adaptive bitrate encoding for HLS/DASH
17//! - **Parallel Encoding** - Encode multiple outputs simultaneously
18//! - **Hardware Acceleration** - Auto-detection and use of GPU encoders
19//! - **Progress Tracking** - Real-time progress with ETA estimation
20//! - **Audio Normalization** - Automatic loudness normalization (EBU R128/ATSC A/85)
21//! - **Quality Control** - CRF, CBR, VBR, and constrained VBR modes
22//! - **Subtitle Support** - Burn-in or soft subtitle embedding
23//! - **Chapter Markers** - Preserve or add chapter information
24//! - **Metadata Preservation** - Copy or map metadata fields
25//!
26//! ## Job Management
27//!
28//! - **Job Queuing** - Queue multiple transcode jobs
29//! - **Priority Scheduling** - High, normal, and low priority jobs
30//! - **Resource Management** - CPU/GPU limits and throttling
31//! - **Error Recovery** - Automatic retry logic with exponential backoff
32//! - **Validation** - Input/output validation before processing
33//!
34//! # Supported Platforms
35//!
36//! ## Streaming Platforms
37//!
38//! - **`YouTube`** - 1080p60, 4K, VP9/H.264 variants
39//! - **Vimeo** - Professional quality presets
40//! - **Twitch** - Live streaming optimized
41//! - **Social Media** - Instagram, `TikTok`, Twitter optimized
42//!
43//! ## Broadcast
44//!
45//! - **`ProRes` Proxy** - High-quality editing proxies
46//! - **`DNxHD` Proxy** - Avid editing proxies
47//! - **Broadcast HD/4K** - Broadcast-ready deliverables
48//!
49//! ## Streaming Protocols
50//!
51//! - **HLS** - HTTP Live Streaming ABR ladders
52//! - **DASH** - MPEG-DASH ABR ladders
53//! - **CMAF** - Common Media Application Format
54//!
55//! ## Archive
56//!
57//! - **Lossless** - FFV1 lossless preservation
58//! - **High Quality** - VP9/AV1 archival encoding
59//!
60//! # Quick Start
61//!
62//! ## Simple Transcoding
63//!
64//! ```rust,no_run
65//! use oximedia_transcode::{Transcoder, presets};
66//!
67//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
68//! // Simple transcode to YouTube 1080p
69//! Transcoder::new()
70//!     .input("input.mp4")
71//!     .output("output.mp4")
72//!     .preset(presets::youtube::youtube_1080p())
73//!     .transcode()
74//!     .await?;
75//! # Ok(())
76//! # }
77//! ```
78//!
79//! ## Complex Pipeline
80//!
81//! ```rust,ignore
82//! use oximedia_transcode::{TranscodePipeline, Quality};
83//! use oximedia_transcode::presets::streaming;
84//!
85//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
86//! // Create HLS ABR ladder with multiple qualities
87//! TranscodePipeline::builder()
88//!     .input("source.mp4")
89//!     .abr_ladder(streaming::hls_ladder())
90//!     .audio_normalize(true)
91//!     .quality(Quality::High)
92//!     .parallel_encode(true)
93//!     .progress(|p| {
94//!         println!("Progress: {}% - ETA: {:?}", p.percent, p.eta);
95//!     })
96//!     .execute()
97//!     .await?;
98//! # Ok(())
99//! # }
100//! ```
101//!
102//! ## Multi-Pass Encoding
103//!
104//! ```rust,no_run
105//! use oximedia_transcode::{Transcoder, MultiPassMode};
106//!
107//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
108//! // 2-pass encoding for optimal quality
109//! Transcoder::new()
110//!     .input("input.mp4")
111//!     .output("output.webm")
112//!     .multi_pass(MultiPassMode::TwoPass)
113//!     .target_bitrate(5_000_000) // 5 Mbps
114//!     .transcode()
115//!     .await?;
116//! # Ok(())
117//! # }
118//! ```
119
120#![forbid(unsafe_code)]
121#![warn(missing_docs)]
122#![allow(clippy::module_name_repetitions)]
123#![allow(clippy::missing_errors_doc)]
124#![allow(clippy::missing_panics_doc)]
125#![allow(clippy::too_many_arguments)]
126
127mod abr;
128pub mod adaptive_bitrate;
129pub mod audio_transcode;
130pub mod bitrate_estimator;
131mod builder;
132mod codec_config;
133pub mod codec_mapping;
134pub mod crf_optimizer;
135mod filters;
136mod hw_accel;
137mod multipass;
138mod normalization;
139mod parallel;
140mod pipeline;
141mod progress;
142mod quality;
143pub mod segment_encoder;
144pub mod segment_transcoder;
145pub mod thumbnail;
146mod transcode_job;
147pub mod two_pass;
148pub mod validation;
149
150pub mod ab_compare;
151pub mod abr_ladder;
152pub mod audio_channel_map;
153pub mod bitrate_control;
154pub mod burn_subs;
155pub mod codec_profile;
156/// Concatenation and joining of multiple media sources.
157pub mod concat_transcode;
158pub mod crop_scale;
159pub mod encoding_log;
160pub mod examples;
161pub mod frame_stats;
162pub mod output_verify;
163pub mod presets;
164/// Rate-distortion analysis for optimal encoding parameter selection.
165pub mod rate_distortion;
166pub mod resolution_select;
167pub mod scene_cut;
168pub mod stage_graph;
169pub mod transcode_metrics;
170pub mod transcode_session;
171pub mod utils;
172/// Watermark and graphic overlay embedding during transcoding.
173pub mod watermark_overlay;
174pub use codec_config::{
175    codec_config_from_quality, Av1Config, Av1Usage, CodecConfig, H264Config, H264Profile,
176    OpusApplication, OpusConfig, Vp9Config,
177};
178pub use filters::{AudioFilter, FilterNode, VideoFilter};
179pub use hw_accel::{
180    detect_available_hw_accel, detect_best_hw_accel_for_codec, get_available_encoders,
181    HwAccelConfig, HwAccelType, HwEncoder, HwFeature,
182};
183
184pub use abr::{AbrLadder, AbrLadderBuilder, AbrRung, AbrStrategy};
185pub use builder::TranscodeBuilder;
186pub use multipass::{MultiPassConfig, MultiPassEncoder, MultiPassMode};
187pub use normalization::{AudioNormalizer, LoudnessStandard, LoudnessTarget, NormalizationConfig};
188pub use parallel::{ParallelConfig, ParallelEncodeBuilder, ParallelEncoder};
189pub use pipeline::{Pipeline, PipelineStage, TranscodePipeline};
190pub use progress::{ProgressCallback, ProgressInfo, ProgressTracker};
191pub use quality::{QualityConfig, QualityMode, QualityPreset, RateControlMode, TuneMode};
192pub use transcode_job::{JobPriority, JobQueue, TranscodeJob, TranscodeJobConfig, TranscodeStatus};
193pub use validation::{InputValidator, OutputValidator, ValidationError};
194
195use thiserror::Error;
196
197/// Errors that can occur during transcoding operations.
198#[derive(Debug, Clone, Error)]
199pub enum TranscodeError {
200    /// Invalid input file or format.
201    #[error("Invalid input: {0}")]
202    InvalidInput(String),
203
204    /// Invalid output configuration.
205    #[error("Invalid output: {0}")]
206    InvalidOutput(String),
207
208    /// Codec error during encoding/decoding.
209    #[error("Codec error: {0}")]
210    CodecError(String),
211
212    /// Container format error.
213    #[error("Container error: {0}")]
214    ContainerError(String),
215
216    /// I/O error during transcoding.
217    #[error("I/O error: {0}")]
218    IoError(String),
219
220    /// Pipeline execution error.
221    #[error("Pipeline error: {0}")]
222    PipelineError(String),
223
224    /// Multi-pass encoding error.
225    #[error("Multi-pass error: {0}")]
226    MultiPassError(String),
227
228    /// Audio normalization error.
229    #[error("Normalization error: {0}")]
230    NormalizationError(String),
231
232    /// Validation error.
233    #[error("Validation error: {0}")]
234    ValidationError(#[from] ValidationError),
235
236    /// Job execution error.
237    #[error("Job error: {0}")]
238    JobError(String),
239
240    /// Unsupported operation or feature.
241    #[error("Unsupported: {0}")]
242    Unsupported(String),
243}
244
245impl From<std::io::Error> for TranscodeError {
246    fn from(err: std::io::Error) -> Self {
247        TranscodeError::IoError(err.to_string())
248    }
249}
250
251/// Result type for transcoding operations.
252pub type Result<T> = std::result::Result<T, TranscodeError>;
253
254/// Main transcoding interface with simple API.
255///
256/// # Example
257///
258/// ```rust,no_run
259/// use oximedia_transcode::Transcoder;
260///
261/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
262/// Transcoder::new()
263///     .input("input.mp4")
264///     .output("output.webm")
265///     .video_codec("vp9")
266///     .audio_codec("opus")
267///     .transcode()
268///     .await?;
269/// # Ok(())
270/// # }
271/// ```
272pub struct Transcoder {
273    config: TranscodeConfig,
274}
275
276/// Transcoding configuration.
277#[derive(Debug, Clone)]
278pub struct TranscodeConfig {
279    /// Input file path.
280    pub input: Option<String>,
281    /// Output file path.
282    pub output: Option<String>,
283    /// Video codec name.
284    pub video_codec: Option<String>,
285    /// Audio codec name.
286    pub audio_codec: Option<String>,
287    /// Target video bitrate in bits per second.
288    pub video_bitrate: Option<u64>,
289    /// Target audio bitrate in bits per second.
290    pub audio_bitrate: Option<u64>,
291    /// Video width in pixels.
292    pub width: Option<u32>,
293    /// Video height in pixels.
294    pub height: Option<u32>,
295    /// Frame rate as a rational number (numerator, denominator).
296    pub frame_rate: Option<(u32, u32)>,
297    /// Multi-pass encoding mode.
298    pub multi_pass: Option<MultiPassMode>,
299    /// Quality mode for encoding.
300    pub quality_mode: Option<QualityMode>,
301    /// Enable audio normalization.
302    pub normalize_audio: bool,
303    /// Loudness normalization standard.
304    pub loudness_standard: Option<LoudnessStandard>,
305    /// Enable hardware acceleration.
306    pub hw_accel: bool,
307    /// Preserve metadata from input.
308    pub preserve_metadata: bool,
309    /// Subtitle handling mode.
310    pub subtitle_mode: Option<SubtitleMode>,
311    /// Chapter handling mode.
312    pub chapter_mode: Option<ChapterMode>,
313}
314
315/// Subtitle handling modes.
316#[derive(Debug, Clone, Copy, PartialEq, Eq)]
317pub enum SubtitleMode {
318    /// Ignore subtitles.
319    Ignore,
320    /// Copy subtitles as separate stream.
321    Copy,
322    /// Burn subtitles into video.
323    BurnIn,
324}
325
326/// Chapter handling modes.
327#[derive(Debug, Clone, Copy, PartialEq, Eq)]
328pub enum ChapterMode {
329    /// Ignore chapters.
330    Ignore,
331    /// Copy chapters from input.
332    Copy,
333    /// Add custom chapters.
334    Custom,
335}
336
337impl Default for TranscodeConfig {
338    fn default() -> Self {
339        Self {
340            input: None,
341            output: None,
342            video_codec: None,
343            audio_codec: None,
344            video_bitrate: None,
345            audio_bitrate: None,
346            width: None,
347            height: None,
348            frame_rate: None,
349            multi_pass: None,
350            quality_mode: None,
351            normalize_audio: false,
352            loudness_standard: None,
353            hw_accel: true,
354            preserve_metadata: true,
355            subtitle_mode: None,
356            chapter_mode: None,
357        }
358    }
359}
360
361impl Transcoder {
362    /// Get a reference to the transcoder configuration.
363    #[must_use]
364    pub fn config(&self) -> &TranscodeConfig {
365        &self.config
366    }
367
368    /// Creates a new transcoder with default configuration.
369    #[must_use]
370    pub fn new() -> Self {
371        Self {
372            config: TranscodeConfig::default(),
373        }
374    }
375
376    /// Sets the input file path.
377    #[must_use]
378    pub fn input(mut self, path: impl Into<String>) -> Self {
379        self.config.input = Some(path.into());
380        self
381    }
382
383    /// Sets the output file path.
384    #[must_use]
385    pub fn output(mut self, path: impl Into<String>) -> Self {
386        self.config.output = Some(path.into());
387        self
388    }
389
390    /// Sets the video codec.
391    #[must_use]
392    pub fn video_codec(mut self, codec: impl Into<String>) -> Self {
393        self.config.video_codec = Some(codec.into());
394        self
395    }
396
397    /// Sets the audio codec.
398    #[must_use]
399    pub fn audio_codec(mut self, codec: impl Into<String>) -> Self {
400        self.config.audio_codec = Some(codec.into());
401        self
402    }
403
404    /// Sets the target video bitrate.
405    #[must_use]
406    pub fn video_bitrate(mut self, bitrate: u64) -> Self {
407        self.config.video_bitrate = Some(bitrate);
408        self
409    }
410
411    /// Sets the target audio bitrate.
412    #[must_use]
413    pub fn audio_bitrate(mut self, bitrate: u64) -> Self {
414        self.config.audio_bitrate = Some(bitrate);
415        self
416    }
417
418    /// Sets the output resolution.
419    #[must_use]
420    pub fn resolution(mut self, width: u32, height: u32) -> Self {
421        self.config.width = Some(width);
422        self.config.height = Some(height);
423        self
424    }
425
426    /// Sets the output frame rate.
427    #[must_use]
428    pub fn frame_rate(mut self, num: u32, den: u32) -> Self {
429        self.config.frame_rate = Some((num, den));
430        self
431    }
432
433    /// Sets the multi-pass encoding mode.
434    #[must_use]
435    pub fn multi_pass(mut self, mode: MultiPassMode) -> Self {
436        self.config.multi_pass = Some(mode);
437        self
438    }
439
440    /// Sets the quality mode.
441    #[must_use]
442    pub fn quality(mut self, mode: QualityMode) -> Self {
443        self.config.quality_mode = Some(mode);
444        self
445    }
446
447    /// Sets the target bitrate (convenience method for video bitrate).
448    #[must_use]
449    pub fn target_bitrate(mut self, bitrate: u64) -> Self {
450        self.config.video_bitrate = Some(bitrate);
451        self
452    }
453
454    /// Enables or disables audio normalization.
455    #[must_use]
456    pub fn normalize_audio(mut self, enable: bool) -> Self {
457        self.config.normalize_audio = enable;
458        self
459    }
460
461    /// Sets the loudness normalization standard.
462    #[must_use]
463    pub fn loudness_standard(mut self, standard: LoudnessStandard) -> Self {
464        self.config.loudness_standard = Some(standard);
465        self.config.normalize_audio = true;
466        self
467    }
468
469    /// Enables or disables hardware acceleration.
470    #[must_use]
471    pub fn hw_accel(mut self, enable: bool) -> Self {
472        self.config.hw_accel = enable;
473        self
474    }
475
476    /// Applies a preset configuration.
477    #[must_use]
478    pub fn preset(mut self, preset: PresetConfig) -> Self {
479        if let Some(codec) = preset.video_codec {
480            self.config.video_codec = Some(codec);
481        }
482        if let Some(codec) = preset.audio_codec {
483            self.config.audio_codec = Some(codec);
484        }
485        if let Some(bitrate) = preset.video_bitrate {
486            self.config.video_bitrate = Some(bitrate);
487        }
488        if let Some(bitrate) = preset.audio_bitrate {
489            self.config.audio_bitrate = Some(bitrate);
490        }
491        if let Some(width) = preset.width {
492            self.config.width = Some(width);
493        }
494        if let Some(height) = preset.height {
495            self.config.height = Some(height);
496        }
497        if let Some(fps) = preset.frame_rate {
498            self.config.frame_rate = Some(fps);
499        }
500        if let Some(mode) = preset.quality_mode {
501            self.config.quality_mode = Some(mode);
502        }
503        self
504    }
505
506    /// Executes the transcode operation.
507    ///
508    /// # Errors
509    ///
510    /// Returns an error if:
511    /// - Input or output path is not set
512    /// - Input file is invalid or cannot be opened
513    /// - Output configuration is invalid
514    /// - Transcoding fails
515    pub async fn transcode(self) -> Result<TranscodeOutput> {
516        // Validate configuration
517        let input = self
518            .config
519            .input
520            .ok_or_else(|| TranscodeError::InvalidInput("No input file specified".to_string()))?;
521        let output = self
522            .config
523            .output
524            .ok_or_else(|| TranscodeError::InvalidOutput("No output file specified".to_string()))?;
525
526        // Create a basic pipeline and execute
527        let mut pipeline = TranscodePipeline::builder()
528            .input(&input)
529            .output(&output)
530            .build()?;
531
532        // Apply configuration to pipeline
533        if let Some(codec) = &self.config.video_codec {
534            pipeline.set_video_codec(codec);
535        }
536        if let Some(codec) = &self.config.audio_codec {
537            pipeline.set_audio_codec(codec);
538        }
539
540        // Execute pipeline
541        pipeline.execute().await
542    }
543}
544
545impl Default for Transcoder {
546    fn default() -> Self {
547        Self::new()
548    }
549}
550
551/// Preset configuration for common transcoding scenarios.
552#[derive(Debug, Clone, Default)]
553pub struct PresetConfig {
554    /// Video codec name.
555    pub video_codec: Option<String>,
556    /// Audio codec name.
557    pub audio_codec: Option<String>,
558    /// Video bitrate.
559    pub video_bitrate: Option<u64>,
560    /// Audio bitrate.
561    pub audio_bitrate: Option<u64>,
562    /// Video width.
563    pub width: Option<u32>,
564    /// Video height.
565    pub height: Option<u32>,
566    /// Frame rate.
567    pub frame_rate: Option<(u32, u32)>,
568    /// Quality mode.
569    pub quality_mode: Option<QualityMode>,
570    /// Container format.
571    pub container: Option<String>,
572}
573
574/// Output from a successful transcode operation.
575#[derive(Debug, Clone)]
576pub struct TranscodeOutput {
577    /// Output file path.
578    pub output_path: String,
579    /// File size in bytes.
580    pub file_size: u64,
581    /// Duration in seconds.
582    pub duration: f64,
583    /// Video bitrate in bits per second.
584    pub video_bitrate: u64,
585    /// Audio bitrate in bits per second.
586    pub audio_bitrate: u64,
587    /// Actual encoding time in seconds.
588    pub encoding_time: f64,
589    /// Speed factor (input duration / encoding time).
590    pub speed_factor: f64,
591}
592
593#[cfg(test)]
594mod tests {
595    use super::*;
596
597    #[test]
598    fn test_transcoder_builder() {
599        let transcoder = Transcoder::new()
600            .input("input.mp4")
601            .output("output.webm")
602            .video_codec("vp9")
603            .audio_codec("opus")
604            .resolution(1920, 1080)
605            .frame_rate(30, 1);
606
607        assert_eq!(transcoder.config.input, Some("input.mp4".to_string()));
608        assert_eq!(transcoder.config.output, Some("output.webm".to_string()));
609        assert_eq!(transcoder.config.video_codec, Some("vp9".to_string()));
610        assert_eq!(transcoder.config.audio_codec, Some("opus".to_string()));
611        assert_eq!(transcoder.config.width, Some(1920));
612        assert_eq!(transcoder.config.height, Some(1080));
613        assert_eq!(transcoder.config.frame_rate, Some((30, 1)));
614    }
615
616    #[test]
617    fn test_default_config() {
618        let config = TranscodeConfig::default();
619        assert!(config.input.is_none());
620        assert!(config.output.is_none());
621        assert!(config.hw_accel);
622        assert!(config.preserve_metadata);
623        assert!(!config.normalize_audio);
624    }
625
626    #[test]
627    fn test_preset_application() {
628        let preset = PresetConfig {
629            video_codec: Some("vp9".to_string()),
630            audio_codec: Some("opus".to_string()),
631            video_bitrate: Some(5_000_000),
632            audio_bitrate: Some(128_000),
633            width: Some(1920),
634            height: Some(1080),
635            frame_rate: Some((60, 1)),
636            quality_mode: Some(QualityMode::High),
637            container: Some("webm".to_string()),
638        };
639
640        let transcoder = Transcoder::new().preset(preset);
641
642        assert_eq!(transcoder.config.video_codec, Some("vp9".to_string()));
643        assert_eq!(transcoder.config.audio_codec, Some("opus".to_string()));
644        assert_eq!(transcoder.config.video_bitrate, Some(5_000_000));
645        assert_eq!(transcoder.config.audio_bitrate, Some(128_000));
646        assert_eq!(transcoder.config.width, Some(1920));
647        assert_eq!(transcoder.config.height, Some(1080));
648        assert_eq!(transcoder.config.frame_rate, Some((60, 1)));
649        assert_eq!(transcoder.config.quality_mode, Some(QualityMode::High));
650    }
651
652    #[test]
653    fn test_subtitle_modes() {
654        assert_eq!(SubtitleMode::Ignore, SubtitleMode::Ignore);
655        assert_ne!(SubtitleMode::Ignore, SubtitleMode::Copy);
656        assert_ne!(SubtitleMode::Copy, SubtitleMode::BurnIn);
657    }
658
659    #[test]
660    fn test_chapter_modes() {
661        assert_eq!(ChapterMode::Ignore, ChapterMode::Ignore);
662        assert_ne!(ChapterMode::Ignore, ChapterMode::Copy);
663        assert_ne!(ChapterMode::Copy, ChapterMode::Custom);
664    }
665}