Skip to main content

oximedia_transcode/
quality.rs

1//! Quality control modes and rate control for video encoding.
2
3use serde::{Deserialize, Serialize};
4
5/// Quality preset levels.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7pub enum QualityMode {
8    /// Low quality (faster encoding, lower bitrate).
9    Low,
10    /// Medium quality (balanced speed and quality).
11    Medium,
12    /// High quality (slower encoding, higher bitrate).
13    High,
14    /// Very high quality (very slow encoding, very high bitrate).
15    VeryHigh,
16    /// Custom quality with specific parameters.
17    Custom,
18}
19
20/// Quality preset configurations.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
22pub enum QualityPreset {
23    /// Ultra-fast encoding with minimal quality.
24    UltraFast,
25    /// Super-fast encoding with low quality.
26    SuperFast,
27    /// Very fast encoding with reduced quality.
28    VeryFast,
29    /// Fast encoding with acceptable quality.
30    Fast,
31    /// Medium speed and quality (balanced).
32    Medium,
33    /// Slow encoding with good quality.
34    Slow,
35    /// Very slow encoding with excellent quality.
36    VerySlow,
37    /// Placebo (extremely slow, diminishing returns).
38    Placebo,
39}
40
41/// Rate control modes for video encoding.
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
43pub enum RateControlMode {
44    /// Constant Rate Factor (quality-based).
45    ///
46    /// Maintains consistent quality across the video.
47    /// Lower values = higher quality, larger file.
48    /// Typical range: 18-28 (23 is default for many codecs).
49    Crf(u8),
50
51    /// Constant Bitrate (CBR).
52    ///
53    /// Maintains a constant bitrate throughout.
54    /// Good for streaming with bandwidth constraints.
55    /// Value in bits per second.
56    Cbr(u64),
57
58    /// Variable Bitrate (VBR).
59    ///
60    /// Allows bitrate to vary based on complexity.
61    /// Target bitrate in bits per second, with quality factor.
62    Vbr {
63        /// Target average bitrate.
64        target: u64,
65        /// Maximum bitrate.
66        max: u64,
67    },
68
69    /// Constrained Variable Bitrate.
70    ///
71    /// VBR with strict maximum bitrate limits.
72    ConstrainedVbr {
73        /// Target average bitrate.
74        target: u64,
75        /// Maximum bitrate (hard limit).
76        max: u64,
77        /// Buffer size for rate control.
78        buffer_size: u64,
79    },
80
81    /// Average Bitrate (ABR).
82    ///
83    /// Targets an average bitrate over the entire video.
84    Abr(u64),
85}
86
87/// Quality configuration for encoding.
88#[derive(Debug, Clone)]
89pub struct QualityConfig {
90    /// Quality preset.
91    pub preset: QualityPreset,
92    /// Rate control mode.
93    pub rate_control: RateControlMode,
94    /// Enable two-pass encoding for better quality.
95    pub two_pass: bool,
96    /// Look-ahead frames for better quality decisions.
97    pub lookahead: Option<u32>,
98    /// Tune for specific content type.
99    pub tune: Option<TuneMode>,
100}
101
102/// Tuning modes for different content types.
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
104pub enum TuneMode {
105    /// Optimize for film content.
106    Film,
107    /// Optimize for animation.
108    Animation,
109    /// Optimize for grain preservation.
110    Grain,
111    /// Optimize for still images.
112    StillImage,
113    /// Optimize for fast decode.
114    FastDecode,
115    /// Optimize for zero latency (live streaming).
116    ZeroLatency,
117    /// Optimize for PSNR quality metric.
118    Psnr,
119    /// Optimize for SSIM quality metric.
120    Ssim,
121}
122
123impl QualityMode {
124    /// Converts quality mode to a CRF value for most codecs.
125    ///
126    /// # Returns
127    ///
128    /// CRF value (lower = higher quality):
129    /// - Low: 28
130    /// - Medium: 23
131    /// - High: 20
132    /// - `VeryHigh`: 18
133    #[must_use]
134    pub fn to_crf(self) -> u8 {
135        match self {
136            Self::Low => 28,
137            Self::Medium => 23,
138            Self::High => 20,
139            Self::VeryHigh => 18,
140            Self::Custom => 23, // Default fallback
141        }
142    }
143
144    /// Converts quality mode to a preset.
145    #[must_use]
146    pub fn to_preset(self) -> QualityPreset {
147        match self {
148            Self::Low => QualityPreset::VeryFast,
149            Self::Medium => QualityPreset::Medium,
150            Self::High => QualityPreset::Slow,
151            Self::VeryHigh => QualityPreset::VerySlow,
152            Self::Custom => QualityPreset::Medium,
153        }
154    }
155
156    /// Estimates encoding speed factor relative to real-time.
157    ///
158    /// # Returns
159    ///
160    /// Approximate speed factor:
161    /// - Low: 5.0x (encodes 5x faster than real-time)
162    /// - Medium: 1.5x
163    /// - High: 0.5x
164    /// - `VeryHigh`: 0.2x
165    #[must_use]
166    pub fn speed_factor(self) -> f64 {
167        match self {
168            Self::Low => 5.0,
169            Self::Medium => 1.5,
170            Self::High => 0.5,
171            Self::VeryHigh => 0.2,
172            Self::Custom => 1.0,
173        }
174    }
175}
176
177impl QualityPreset {
178    /// Gets the preset name as a string (compatible with x264/x265/libvpx naming).
179    #[must_use]
180    pub fn as_str(self) -> &'static str {
181        match self {
182            Self::UltraFast => "ultrafast",
183            Self::SuperFast => "superfast",
184            Self::VeryFast => "veryfast",
185            Self::Fast => "fast",
186            Self::Medium => "medium",
187            Self::Slow => "slow",
188            Self::VerySlow => "veryslow",
189            Self::Placebo => "placebo",
190        }
191    }
192
193    /// Gets the CPU usage level (0-8, higher = slower/better).
194    #[must_use]
195    pub fn cpu_used(self) -> u8 {
196        match self {
197            Self::UltraFast => 8,
198            Self::SuperFast => 7,
199            Self::VeryFast => 6,
200            Self::Fast => 5,
201            Self::Medium => 4,
202            Self::Slow => 2,
203            Self::VerySlow => 1,
204            Self::Placebo => 0,
205        }
206    }
207}
208
209impl RateControlMode {
210    /// Gets the target bitrate for this rate control mode.
211    #[must_use]
212    pub fn target_bitrate(&self) -> Option<u64> {
213        match self {
214            Self::Crf(_) => None,
215            Self::Cbr(bitrate) | Self::Abr(bitrate) => Some(*bitrate),
216            Self::Vbr { target, .. } | Self::ConstrainedVbr { target, .. } => Some(*target),
217        }
218    }
219
220    /// Gets the maximum bitrate for this rate control mode.
221    #[must_use]
222    pub fn max_bitrate(&self) -> Option<u64> {
223        match self {
224            Self::Crf(_) | Self::Cbr(_) | Self::Abr(_) => None,
225            Self::Vbr { max, .. } | Self::ConstrainedVbr { max, .. } => Some(*max),
226        }
227    }
228
229    /// Checks if this is a constant quality mode.
230    #[must_use]
231    pub fn is_constant_quality(&self) -> bool {
232        matches!(self, Self::Crf(_))
233    }
234
235    /// Checks if this is a bitrate-based mode.
236    #[must_use]
237    pub fn is_bitrate_mode(&self) -> bool {
238        !self.is_constant_quality()
239    }
240}
241
242impl Default for QualityConfig {
243    fn default() -> Self {
244        Self {
245            preset: QualityPreset::Medium,
246            rate_control: RateControlMode::Crf(23),
247            two_pass: false,
248            lookahead: Some(40),
249            tune: None,
250        }
251    }
252}
253
254impl TuneMode {
255    /// Gets the tune mode name as a string.
256    #[must_use]
257    pub fn as_str(self) -> &'static str {
258        match self {
259            Self::Film => "film",
260            Self::Animation => "animation",
261            Self::Grain => "grain",
262            Self::StillImage => "stillimage",
263            Self::FastDecode => "fastdecode",
264            Self::ZeroLatency => "zerolatency",
265            Self::Psnr => "psnr",
266            Self::Ssim => "ssim",
267        }
268    }
269}
270
271/// Builder for quality configuration.
272#[allow(dead_code)]
273pub struct QualityConfigBuilder {
274    config: QualityConfig,
275}
276
277#[allow(dead_code)]
278impl QualityConfigBuilder {
279    /// Creates a new quality config builder.
280    #[must_use]
281    pub fn new() -> Self {
282        Self {
283            config: QualityConfig::default(),
284        }
285    }
286
287    /// Sets the quality preset.
288    #[must_use]
289    pub fn preset(mut self, preset: QualityPreset) -> Self {
290        self.config.preset = preset;
291        self
292    }
293
294    /// Sets the rate control mode.
295    #[must_use]
296    pub fn rate_control(mut self, mode: RateControlMode) -> Self {
297        self.config.rate_control = mode;
298        self
299    }
300
301    /// Enables two-pass encoding.
302    #[must_use]
303    pub fn two_pass(mut self, enable: bool) -> Self {
304        self.config.two_pass = enable;
305        self
306    }
307
308    /// Sets the lookahead frame count.
309    #[must_use]
310    pub fn lookahead(mut self, frames: u32) -> Self {
311        self.config.lookahead = Some(frames);
312        self
313    }
314
315    /// Sets the tune mode.
316    #[must_use]
317    pub fn tune(mut self, mode: TuneMode) -> Self {
318        self.config.tune = Some(mode);
319        self
320    }
321
322    /// Builds the quality configuration.
323    #[must_use]
324    pub fn build(self) -> QualityConfig {
325        self.config
326    }
327}
328
329impl Default for QualityConfigBuilder {
330    fn default() -> Self {
331        Self::new()
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    use super::*;
338
339    #[test]
340    fn test_quality_mode_crf() {
341        assert_eq!(QualityMode::Low.to_crf(), 28);
342        assert_eq!(QualityMode::Medium.to_crf(), 23);
343        assert_eq!(QualityMode::High.to_crf(), 20);
344        assert_eq!(QualityMode::VeryHigh.to_crf(), 18);
345    }
346
347    #[test]
348    fn test_quality_preset_str() {
349        assert_eq!(QualityPreset::UltraFast.as_str(), "ultrafast");
350        assert_eq!(QualityPreset::Medium.as_str(), "medium");
351        assert_eq!(QualityPreset::VerySlow.as_str(), "veryslow");
352    }
353
354    #[test]
355    fn test_quality_preset_cpu_used() {
356        assert_eq!(QualityPreset::UltraFast.cpu_used(), 8);
357        assert_eq!(QualityPreset::Medium.cpu_used(), 4);
358        assert_eq!(QualityPreset::Placebo.cpu_used(), 0);
359    }
360
361    #[test]
362    fn test_rate_control_crf() {
363        let crf = RateControlMode::Crf(23);
364        assert!(crf.is_constant_quality());
365        assert!(!crf.is_bitrate_mode());
366        assert_eq!(crf.target_bitrate(), None);
367        assert_eq!(crf.max_bitrate(), None);
368    }
369
370    #[test]
371    fn test_rate_control_cbr() {
372        let cbr = RateControlMode::Cbr(5_000_000);
373        assert!(!cbr.is_constant_quality());
374        assert!(cbr.is_bitrate_mode());
375        assert_eq!(cbr.target_bitrate(), Some(5_000_000));
376        assert_eq!(cbr.max_bitrate(), None);
377    }
378
379    #[test]
380    fn test_rate_control_vbr() {
381        let vbr = RateControlMode::Vbr {
382            target: 5_000_000,
383            max: 8_000_000,
384        };
385        assert!(!vbr.is_constant_quality());
386        assert!(vbr.is_bitrate_mode());
387        assert_eq!(vbr.target_bitrate(), Some(5_000_000));
388        assert_eq!(vbr.max_bitrate(), Some(8_000_000));
389    }
390
391    #[test]
392    fn test_rate_control_constrained_vbr() {
393        let cvbr = RateControlMode::ConstrainedVbr {
394            target: 5_000_000,
395            max: 8_000_000,
396            buffer_size: 10_000_000,
397        };
398        assert_eq!(cvbr.target_bitrate(), Some(5_000_000));
399        assert_eq!(cvbr.max_bitrate(), Some(8_000_000));
400    }
401
402    #[test]
403    fn test_tune_mode_str() {
404        assert_eq!(TuneMode::Film.as_str(), "film");
405        assert_eq!(TuneMode::Animation.as_str(), "animation");
406        assert_eq!(TuneMode::ZeroLatency.as_str(), "zerolatency");
407    }
408
409    #[test]
410    fn test_quality_config_builder() {
411        let config = QualityConfigBuilder::new()
412            .preset(QualityPreset::Slow)
413            .rate_control(RateControlMode::Crf(20))
414            .two_pass(true)
415            .lookahead(60)
416            .tune(TuneMode::Film)
417            .build();
418
419        assert_eq!(config.preset, QualityPreset::Slow);
420        assert_eq!(config.rate_control, RateControlMode::Crf(20));
421        assert!(config.two_pass);
422        assert_eq!(config.lookahead, Some(60));
423        assert_eq!(config.tune, Some(TuneMode::Film));
424    }
425
426    #[test]
427    fn test_quality_mode_speed_factor() {
428        assert_eq!(QualityMode::Low.speed_factor(), 5.0);
429        assert_eq!(QualityMode::Medium.speed_factor(), 1.5);
430        assert_eq!(QualityMode::High.speed_factor(), 0.5);
431        assert_eq!(QualityMode::VeryHigh.speed_factor(), 0.2);
432    }
433}