Skip to main content

ff_encode/video/
codec_options.rs

1//! Per-codec encoding options for [`VideoEncoderBuilder`](super::builder::VideoEncoderBuilder).
2//!
3//! Pass a [`VideoCodecOptions`] value to
4//! `VideoEncoderBuilder::codec_options()` to control codec-specific behaviour.
5//! Options are applied via `av_opt_set` / direct field assignment **before**
6//! `avcodec_open2`.  Any option that the chosen encoder does not support is
7//! logged as a warning and skipped — it never causes `build()` to return an
8//! error.
9
10/// Per-codec encoding options.
11///
12/// The variant must match the codec passed to
13/// `VideoEncoderBuilder::video_codec()`.  A mismatch is silently ignored
14/// (the options are not applied).
15///
16/// All variants are fully implemented.
17#[derive(Debug, Clone)]
18pub enum VideoCodecOptions {
19    /// H.264 (AVC) encoding options.
20    H264(H264Options),
21    /// H.265 (HEVC) encoding options.
22    H265(H265Options),
23    /// AV1 (libaom-av1) encoding options.
24    Av1(Av1Options),
25    /// AV1 (SVT-AV1 / libsvtav1) encoding options.
26    Av1Svt(SvtAv1Options),
27    /// VP9 encoding options (reserved for a future issue).
28    Vp9(Vp9Options),
29    /// Apple ProRes encoding options (reserved for a future issue).
30    ProRes(ProResOptions),
31    /// Avid DNxHD / DNxHR encoding options (reserved for a future issue).
32    Dnxhd(DnxhdOptions),
33}
34
35// ── H.264 ────────────────────────────────────────────────────────────────────
36
37/// H.264 (AVC) per-codec options.
38#[derive(Debug, Clone)]
39pub struct H264Options {
40    /// Encoding profile.
41    pub profile: H264Profile,
42    /// Encoding level as an integer (e.g. `31` = 3.1, `40` = 4.0, `51` = 5.1).
43    ///
44    /// `None` leaves the encoder default.
45    pub level: Option<u32>,
46    /// Maximum consecutive B-frames (0–16).
47    pub bframes: u32,
48    /// GOP size: number of frames between keyframes.
49    pub gop_size: u32,
50    /// Number of reference frames.
51    pub refs: u32,
52    /// libx264 encoding speed/quality preset.
53    ///
54    /// Overrides the global [`Preset`](crate::Preset) when set. `None` falls
55    /// back to whatever the builder's `.preset()` selector chose.
56    /// Hardware encoders do not support libx264 presets — the option is
57    /// silently skipped with a `warn!` log when unsupported.
58    pub preset: Option<H264Preset>,
59    /// libx264 perceptual tuning parameter.
60    ///
61    /// `None` leaves the encoder default. Hardware encoders ignore this
62    /// option (logged as a warning and skipped).
63    pub tune: Option<H264Tune>,
64}
65
66impl Default for H264Options {
67    fn default() -> Self {
68        Self {
69            profile: H264Profile::High,
70            level: None,
71            bframes: 2,
72            gop_size: 250,
73            refs: 3,
74            preset: None,
75            tune: None,
76        }
77    }
78}
79
80/// H.264 encoding profile.
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
82pub enum H264Profile {
83    /// Baseline profile — no B-frames, no CABAC (low latency / mobile).
84    Baseline,
85    /// Main profile.
86    Main,
87    /// High profile (recommended for most uses).
88    #[default]
89    High,
90    /// High 10-bit profile.
91    High10,
92}
93
94impl H264Profile {
95    pub(super) fn as_str(self) -> &'static str {
96        match self {
97            Self::Baseline => "baseline",
98            Self::Main => "main",
99            Self::High => "high",
100            Self::High10 => "high10",
101        }
102    }
103}
104
105/// libx264 encoding speed/quality preset.
106///
107/// Slower presets produce higher quality at the same bitrate but take longer
108/// to encode. Not supported by hardware encoders (NVENC, QSV, etc.) — the
109/// option is skipped with a warning when the encoder does not recognise it.
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
111pub enum H264Preset {
112    /// Fastest encoding, lowest quality.
113    Ultrafast,
114    /// Very fast encoding.
115    Superfast,
116    /// Fast encoding.
117    Veryfast,
118    /// Faster than default.
119    Faster,
120    /// Slightly faster than default.
121    Fast,
122    /// Default preset — balanced speed and quality.
123    Medium,
124    /// Slower encoding, better quality.
125    Slow,
126    /// Noticeably slower, noticeably better quality.
127    Slower,
128    /// Very slow, near-optimal quality.
129    Veryslow,
130    /// Slowest, maximum compression (not recommended for production).
131    Placebo,
132}
133
134impl H264Preset {
135    pub(super) fn as_str(self) -> &'static str {
136        match self {
137            Self::Ultrafast => "ultrafast",
138            Self::Superfast => "superfast",
139            Self::Veryfast => "veryfast",
140            Self::Faster => "faster",
141            Self::Fast => "fast",
142            Self::Medium => "medium",
143            Self::Slow => "slow",
144            Self::Slower => "slower",
145            Self::Veryslow => "veryslow",
146            Self::Placebo => "placebo",
147        }
148    }
149}
150
151/// libx264 perceptual tuning parameter.
152///
153/// Adjusts encoder settings for a specific type of source content or
154/// quality metric. Not supported by hardware encoders — skipped with a
155/// warning when the encoder does not recognise the option.
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub enum H264Tune {
158    /// Optimised for live-action film content.
159    Film,
160    /// Optimised for animation.
161    Animation,
162    /// Optimised for grainy content.
163    Grain,
164    /// Optimised for still images (single-frame encoding).
165    Stillimage,
166    /// Optimise for PSNR quality metric.
167    Psnr,
168    /// Optimise for SSIM quality metric.
169    Ssim,
170    /// Reduce decoding complexity (disables certain features).
171    Fastdecode,
172    /// Minimise encoding latency (no B-frames, no lookahead).
173    Zerolatency,
174}
175
176impl H264Tune {
177    pub(super) fn as_str(self) -> &'static str {
178        match self {
179            Self::Film => "film",
180            Self::Animation => "animation",
181            Self::Grain => "grain",
182            Self::Stillimage => "stillimage",
183            Self::Psnr => "psnr",
184            Self::Ssim => "ssim",
185            Self::Fastdecode => "fastdecode",
186            Self::Zerolatency => "zerolatency",
187        }
188    }
189}
190
191// ── H.265 ────────────────────────────────────────────────────────────────────
192
193/// H.265 (HEVC) per-codec options.
194#[derive(Debug, Clone)]
195pub struct H265Options {
196    /// Encoding profile.
197    pub profile: H265Profile,
198    /// Encoding tier.
199    pub tier: H265Tier,
200    /// Encoding level as an integer (e.g. `31` = 3.1, `51` = 5.1).
201    ///
202    /// `None` leaves the encoder default.
203    pub level: Option<u32>,
204    /// libx265 encoding speed/quality preset (e.g. `"ultrafast"`, `"medium"`, `"slow"`).
205    ///
206    /// `None` leaves the encoder default. Invalid or unsupported values are logged as a
207    /// warning and skipped — `build()` never fails due to an unsupported preset.
208    /// Hardware HEVC encoders (hevc_nvenc, etc.) ignore this option.
209    pub preset: Option<String>,
210    /// Raw x265-params string passed verbatim to libx265 (e.g. `"ctu=32:ref=4"`).
211    ///
212    /// **Note**: H.265 encoding requires an FFmpeg build with `--enable-libx265`.
213    ///
214    /// An invalid parameter string is logged as a warning and skipped. It never causes
215    /// `build()` to return an error.
216    pub x265_params: Option<String>,
217}
218
219impl Default for H265Options {
220    fn default() -> Self {
221        Self {
222            profile: H265Profile::Main,
223            tier: H265Tier::Main,
224            level: None,
225            preset: None,
226            x265_params: None,
227        }
228    }
229}
230
231/// H.265 encoding profile.
232#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
233pub enum H265Profile {
234    /// Main profile (8-bit, 4:2:0).
235    #[default]
236    Main,
237    /// Main 10-bit profile (HDR-capable).
238    Main10,
239}
240
241impl H265Profile {
242    pub(super) fn as_str(self) -> &'static str {
243        match self {
244            Self::Main => "main",
245            Self::Main10 => "main10",
246        }
247    }
248}
249
250/// H.265 encoding tier.
251#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
252pub enum H265Tier {
253    /// Main tier — lower bitrate ceiling (consumer content).
254    #[default]
255    Main,
256    /// High tier — higher bitrate ceiling (broadcast / professional).
257    High,
258}
259
260impl H265Tier {
261    pub(super) fn as_str(self) -> &'static str {
262        match self {
263            Self::Main => "main",
264            Self::High => "high",
265        }
266    }
267}
268
269// ── AV1 ──────────────────────────────────────────────────────────────────────
270
271/// AV1 per-codec options (libaom-av1).
272#[derive(Debug, Clone)]
273pub struct Av1Options {
274    /// CPU effort level: `0` = slowest / best quality, `8` = fastest / lowest quality.
275    pub cpu_used: u8,
276    /// Log2 of the number of tile rows (0–6).
277    pub tile_rows: u8,
278    /// Log2 of the number of tile columns (0–6).
279    pub tile_cols: u8,
280    /// Encoding usage mode.
281    pub usage: Av1Usage,
282}
283
284impl Default for Av1Options {
285    fn default() -> Self {
286        Self {
287            cpu_used: 4,
288            tile_rows: 0,
289            tile_cols: 0,
290            usage: Av1Usage::VoD,
291        }
292    }
293}
294
295/// AV1 encoding usage mode.
296#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
297pub enum Av1Usage {
298    /// Video-on-demand: maximise quality at the cost of speed.
299    #[default]
300    VoD,
301    /// Real-time: minimise encoding latency.
302    RealTime,
303}
304
305impl Av1Usage {
306    pub(super) fn as_str(self) -> &'static str {
307        match self {
308            Self::VoD => "vod",
309            Self::RealTime => "realtime",
310        }
311    }
312}
313
314// ── SVT-AV1 ──────────────────────────────────────────────────────────────────
315
316/// SVT-AV1 (libsvtav1) per-codec options.
317///
318/// **Note**: Requires an FFmpeg build with `--enable-libsvtav1` (LGPL).
319/// `build()` returns [`EncodeError::EncoderUnavailable`](crate::EncodeError::EncoderUnavailable)
320/// when libsvtav1 is absent from the FFmpeg build.
321#[derive(Debug, Clone)]
322pub struct SvtAv1Options {
323    /// Encoder preset: 0 = best quality / slowest, 13 = fastest / lowest quality.
324    pub preset: u8,
325    /// Log2 number of tile rows (0–6).
326    pub tile_rows: u8,
327    /// Log2 number of tile columns (0–6).
328    pub tile_cols: u8,
329    /// Raw SVT-AV1 params string passed verbatim (e.g. `"fast-decode=1:hdr=0"`).
330    ///
331    /// `None` leaves the encoder default. Invalid values are logged as a warning
332    /// and skipped — `build()` never fails due to an unsupported parameter.
333    pub svtav1_params: Option<String>,
334}
335
336impl Default for SvtAv1Options {
337    fn default() -> Self {
338        Self {
339            preset: 8,
340            tile_rows: 0,
341            tile_cols: 0,
342            svtav1_params: None,
343        }
344    }
345}
346
347// ── VP9 ───────────────────────────────────────────────────────────────────────
348
349/// VP9 (libvpx-vp9) per-codec options.
350#[derive(Debug, Clone, Default)]
351pub struct Vp9Options {
352    /// Encoder speed/quality trade-off: -8 = best quality / slowest, 8 = fastest.
353    pub cpu_used: i8,
354    /// Constrained Quality level (0–63).
355    ///
356    /// `Some(q)` enables CQ mode: sets `bit_rate = 0` and applies `q` as the `crf`
357    /// option, producing variable-bitrate output governed by perceptual quality.
358    /// `None` uses the bitrate mode configured on the builder.
359    pub cq_level: Option<u8>,
360    /// Log2 number of tile columns (0–6).
361    pub tile_columns: u8,
362    /// Log2 number of tile rows (0–6).
363    pub tile_rows: u8,
364    /// Enable row-based multithreading for better CPU utilisation.
365    pub row_mt: bool,
366}
367
368// ── Apple ProRes ──────────────────────────────────────────────────────────────
369
370/// Apple ProRes encoding profile.
371///
372/// Controls quality and chroma sampling. 422 profiles use `yuv422p10le`;
373/// 4444 profiles use `yuva444p10le`.
374#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
375pub enum ProResProfile {
376    /// 422 Proxy — lowest data rate, offline editing.
377    Proxy,
378    /// 422 LT — lightweight, good for editing proxies.
379    Lt,
380    /// 422 Standard — production quality (default).
381    #[default]
382    Standard,
383    /// 422 HQ — high quality, recommended for mastering.
384    Hq,
385    /// 4444 — full chroma, supports alpha channel.
386    P4444,
387    /// 4444 XQ — maximum quality 4444 variant.
388    P4444Xq,
389}
390
391impl ProResProfile {
392    /// Returns the integer profile ID passed to `prores_ks` via `av_opt_set`.
393    pub(super) fn profile_id(self) -> u8 {
394        match self {
395            Self::Proxy => 0,
396            Self::Lt => 1,
397            Self::Standard => 2,
398            Self::Hq => 3,
399            Self::P4444 => 4,
400            Self::P4444Xq => 5,
401        }
402    }
403
404    /// Returns `true` for 4444 profiles that require `yuva444p10le` pixel format.
405    pub(super) fn is_4444(self) -> bool {
406        matches!(self, Self::P4444 | Self::P4444Xq)
407    }
408}
409
410/// Apple ProRes per-codec options.
411///
412/// Requires an FFmpeg build with `prores_ks` encoder support. Output should
413/// use a `.mov` container.
414#[derive(Debug, Clone)]
415pub struct ProResOptions {
416    /// ProRes encoding profile controlling quality and chroma sampling.
417    pub profile: ProResProfile,
418    /// Optional 4-byte FourCC vendor tag embedded in the stream.
419    ///
420    /// Set to `Some([b'a', b'p', b'p', b'l'])` to mimic Apple encoders.
421    /// `None` leaves the encoder default.
422    pub vendor: Option<[u8; 4]>,
423}
424
425impl Default for ProResOptions {
426    fn default() -> Self {
427        Self {
428            profile: ProResProfile::Standard,
429            vendor: None,
430        }
431    }
432}
433
434// ── Avid DNxHD / DNxHR ───────────────────────────────────────────────────────
435
436/// DNxHD / DNxHR encoding variant.
437///
438/// Legacy DNxHD variants (`Dnxhd*`) are constrained to 1920×1080 or 1280×720
439/// and require a fixed bitrate. DNxHR variants (`Dnxhr*`) work at any resolution.
440#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
441pub enum DnxhdVariant {
442    // ── DNxHD (legacy fixed-bitrate, 1920×1080 or 1280×720 only) ─────────────
443    /// 1080i/p 115 Mbps, 8-bit yuv422p.
444    Dnxhd115,
445    /// 1080i/p 145 Mbps, 8-bit yuv422p.
446    Dnxhd145,
447    /// 1080p 220 Mbps, 8-bit yuv422p.
448    Dnxhd220,
449    /// 1080p 220 Mbps, 10-bit yuv422p10le.
450    Dnxhd220x,
451    // ── DNxHR (resolution-agnostic) ───────────────────────────────────────────
452    /// Low Bandwidth, 8-bit yuv422p.
453    DnxhrLb,
454    /// Standard Quality, 8-bit yuv422p (default).
455    #[default]
456    DnxhrSq,
457    /// High Quality, 8-bit yuv422p.
458    DnxhrHq,
459    /// High Quality 10-bit, yuv422p10le.
460    DnxhrHqx,
461    /// 4:4:4 12-bit, yuv444p10le.
462    DnxhrR444,
463}
464
465impl DnxhdVariant {
466    /// Returns the `vprofile` string passed to the `dnxhd` encoder.
467    pub(super) fn vprofile_str(self) -> &'static str {
468        match self {
469            Self::Dnxhd115 | Self::Dnxhd145 | Self::Dnxhd220 | Self::Dnxhd220x => "dnxhd",
470            Self::DnxhrLb => "dnxhr_lb",
471            Self::DnxhrSq => "dnxhr_sq",
472            Self::DnxhrHq => "dnxhr_hq",
473            Self::DnxhrHqx => "dnxhr_hqx",
474            Self::DnxhrR444 => "dnxhr_444",
475        }
476    }
477
478    /// Returns the required pixel format for this variant.
479    pub(super) fn pixel_format(self) -> ff_format::PixelFormat {
480        use ff_format::PixelFormat;
481        match self {
482            Self::Dnxhd115
483            | Self::Dnxhd145
484            | Self::Dnxhd220
485            | Self::DnxhrLb
486            | Self::DnxhrSq
487            | Self::DnxhrHq => PixelFormat::Yuv422p,
488            Self::Dnxhd220x | Self::DnxhrHqx => PixelFormat::Yuv422p10le,
489            Self::DnxhrR444 => PixelFormat::Yuv444p10le,
490        }
491    }
492
493    /// For legacy DNxHD variants, returns the required fixed bitrate in bps.
494    ///
495    /// DNxHR variants return `None` — the encoder selects the bitrate automatically.
496    pub(super) fn fixed_bitrate_bps(self) -> Option<i64> {
497        match self {
498            Self::Dnxhd115 => Some(115_000_000),
499            Self::Dnxhd145 => Some(145_000_000),
500            Self::Dnxhd220 | Self::Dnxhd220x => Some(220_000_000),
501            _ => None,
502        }
503    }
504
505    /// Returns `true` for legacy DNxHD variants that require 1920×1080 or 1280×720.
506    pub(super) fn is_dnxhd(self) -> bool {
507        matches!(
508            self,
509            Self::Dnxhd115 | Self::Dnxhd145 | Self::Dnxhd220 | Self::Dnxhd220x
510        )
511    }
512}
513
514/// Avid DNxHD / DNxHR per-codec options.
515///
516/// Output should use a `.mxf` or `.mov` container. Legacy DNxHD variants
517/// (`Dnxhd*`) are validated in `build()` to require 1920×1080 or 1280×720.
518#[derive(Debug, Clone, Default)]
519pub struct DnxhdOptions {
520    /// DNxHD/DNxHR encoding variant.
521    pub variant: DnxhdVariant,
522}
523
524// ── Tests ─────────────────────────────────────────────────────────────────────
525
526#[cfg(test)]
527mod tests {
528    use super::*;
529
530    #[test]
531    fn h264_profile_should_return_correct_str() {
532        assert_eq!(H264Profile::Baseline.as_str(), "baseline");
533        assert_eq!(H264Profile::Main.as_str(), "main");
534        assert_eq!(H264Profile::High.as_str(), "high");
535        assert_eq!(H264Profile::High10.as_str(), "high10");
536    }
537
538    #[test]
539    fn h265_profile_should_return_correct_str() {
540        assert_eq!(H265Profile::Main.as_str(), "main");
541        assert_eq!(H265Profile::Main10.as_str(), "main10");
542    }
543
544    #[test]
545    fn h265_tier_should_return_correct_str() {
546        assert_eq!(H265Tier::Main.as_str(), "main");
547        assert_eq!(H265Tier::High.as_str(), "high");
548    }
549
550    #[test]
551    fn av1_usage_should_return_correct_str() {
552        assert_eq!(Av1Usage::VoD.as_str(), "vod");
553        assert_eq!(Av1Usage::RealTime.as_str(), "realtime");
554    }
555
556    #[test]
557    fn h264_options_default_should_have_high_profile() {
558        let opts = H264Options::default();
559        assert_eq!(opts.profile, H264Profile::High);
560        assert_eq!(opts.level, None);
561        assert_eq!(opts.bframes, 2);
562        assert_eq!(opts.gop_size, 250);
563        assert_eq!(opts.refs, 3);
564        assert!(opts.preset.is_none());
565        assert!(opts.tune.is_none());
566    }
567
568    #[test]
569    fn h264_preset_should_return_correct_str() {
570        assert_eq!(H264Preset::Ultrafast.as_str(), "ultrafast");
571        assert_eq!(H264Preset::Superfast.as_str(), "superfast");
572        assert_eq!(H264Preset::Veryfast.as_str(), "veryfast");
573        assert_eq!(H264Preset::Faster.as_str(), "faster");
574        assert_eq!(H264Preset::Fast.as_str(), "fast");
575        assert_eq!(H264Preset::Medium.as_str(), "medium");
576        assert_eq!(H264Preset::Slow.as_str(), "slow");
577        assert_eq!(H264Preset::Slower.as_str(), "slower");
578        assert_eq!(H264Preset::Veryslow.as_str(), "veryslow");
579        assert_eq!(H264Preset::Placebo.as_str(), "placebo");
580    }
581
582    #[test]
583    fn h264_tune_should_return_correct_str() {
584        assert_eq!(H264Tune::Film.as_str(), "film");
585        assert_eq!(H264Tune::Animation.as_str(), "animation");
586        assert_eq!(H264Tune::Grain.as_str(), "grain");
587        assert_eq!(H264Tune::Stillimage.as_str(), "stillimage");
588        assert_eq!(H264Tune::Psnr.as_str(), "psnr");
589        assert_eq!(H264Tune::Ssim.as_str(), "ssim");
590        assert_eq!(H264Tune::Fastdecode.as_str(), "fastdecode");
591        assert_eq!(H264Tune::Zerolatency.as_str(), "zerolatency");
592    }
593
594    #[test]
595    fn h265_options_default_should_have_main_profile() {
596        let opts = H265Options::default();
597        assert_eq!(opts.profile, H265Profile::Main);
598        assert_eq!(opts.tier, H265Tier::Main);
599        assert_eq!(opts.level, None);
600        assert!(opts.preset.is_none());
601        assert!(opts.x265_params.is_none());
602    }
603
604    #[test]
605    fn h265_preset_should_be_none_by_default() {
606        let opts = H265Options::default();
607        assert!(opts.preset.is_none());
608    }
609
610    #[test]
611    fn h265_x265_params_should_be_none_by_default() {
612        let opts = H265Options::default();
613        assert!(opts.x265_params.is_none());
614    }
615
616    #[test]
617    fn av1_options_default_should_have_vod_usage() {
618        let opts = Av1Options::default();
619        assert_eq!(opts.cpu_used, 4);
620        assert_eq!(opts.tile_rows, 0);
621        assert_eq!(opts.tile_cols, 0);
622        assert_eq!(opts.usage, Av1Usage::VoD);
623    }
624
625    #[test]
626    fn video_codec_options_enum_variants_are_accessible() {
627        let _h264 = VideoCodecOptions::H264(H264Options::default());
628        let _h265 = VideoCodecOptions::H265(H265Options::default());
629        let _av1 = VideoCodecOptions::Av1(Av1Options::default());
630        let _av1svt = VideoCodecOptions::Av1Svt(SvtAv1Options::default());
631        let _vp9 = VideoCodecOptions::Vp9(Vp9Options::default());
632        let _prores = VideoCodecOptions::ProRes(ProResOptions::default());
633        let _dnxhd = VideoCodecOptions::Dnxhd(DnxhdOptions::default());
634    }
635
636    #[test]
637    fn vp9_options_default_should_have_cpu_used_0() {
638        let opts = Vp9Options::default();
639        assert_eq!(opts.cpu_used, 0);
640        assert_eq!(opts.tile_columns, 0);
641        assert_eq!(opts.tile_rows, 0);
642        assert!(!opts.row_mt);
643    }
644
645    #[test]
646    fn vp9_options_default_should_have_no_cq_level() {
647        let opts = Vp9Options::default();
648        assert!(opts.cq_level.is_none());
649    }
650
651    #[test]
652    fn svtav1_options_default_should_have_preset_8() {
653        let opts = SvtAv1Options::default();
654        assert_eq!(opts.preset, 8);
655        assert_eq!(opts.tile_rows, 0);
656        assert_eq!(opts.tile_cols, 0);
657        assert!(opts.svtav1_params.is_none());
658    }
659
660    #[test]
661    fn prores_options_default_should_have_standard_profile() {
662        let opts = ProResOptions::default();
663        assert_eq!(opts.profile, ProResProfile::Standard);
664        assert!(opts.vendor.is_none());
665    }
666
667    #[test]
668    fn prores_profile_ids_should_match_spec() {
669        assert_eq!(ProResProfile::Proxy.profile_id(), 0);
670        assert_eq!(ProResProfile::Lt.profile_id(), 1);
671        assert_eq!(ProResProfile::Standard.profile_id(), 2);
672        assert_eq!(ProResProfile::Hq.profile_id(), 3);
673        assert_eq!(ProResProfile::P4444.profile_id(), 4);
674        assert_eq!(ProResProfile::P4444Xq.profile_id(), 5);
675    }
676
677    #[test]
678    fn prores_profile_is_4444_should_return_true_for_4444_variants() {
679        assert!(!ProResProfile::Proxy.is_4444());
680        assert!(!ProResProfile::Lt.is_4444());
681        assert!(!ProResProfile::Standard.is_4444());
682        assert!(!ProResProfile::Hq.is_4444());
683        assert!(ProResProfile::P4444.is_4444());
684        assert!(ProResProfile::P4444Xq.is_4444());
685    }
686
687    #[test]
688    fn dnxhd_options_default_should_have_dnxhr_sq_variant() {
689        let opts = DnxhdOptions::default();
690        assert_eq!(opts.variant, DnxhdVariant::DnxhrSq);
691    }
692
693    #[test]
694    fn dnxhd_variant_vprofile_str_should_match_spec() {
695        assert_eq!(DnxhdVariant::Dnxhd115.vprofile_str(), "dnxhd");
696        assert_eq!(DnxhdVariant::Dnxhd145.vprofile_str(), "dnxhd");
697        assert_eq!(DnxhdVariant::Dnxhd220.vprofile_str(), "dnxhd");
698        assert_eq!(DnxhdVariant::Dnxhd220x.vprofile_str(), "dnxhd");
699        assert_eq!(DnxhdVariant::DnxhrLb.vprofile_str(), "dnxhr_lb");
700        assert_eq!(DnxhdVariant::DnxhrSq.vprofile_str(), "dnxhr_sq");
701        assert_eq!(DnxhdVariant::DnxhrHq.vprofile_str(), "dnxhr_hq");
702        assert_eq!(DnxhdVariant::DnxhrHqx.vprofile_str(), "dnxhr_hqx");
703        assert_eq!(DnxhdVariant::DnxhrR444.vprofile_str(), "dnxhr_444");
704    }
705
706    #[test]
707    fn dnxhd_variant_pixel_format_should_match_spec() {
708        use ff_format::PixelFormat;
709        assert_eq!(DnxhdVariant::Dnxhd115.pixel_format(), PixelFormat::Yuv422p);
710        assert_eq!(DnxhdVariant::Dnxhd145.pixel_format(), PixelFormat::Yuv422p);
711        assert_eq!(DnxhdVariant::Dnxhd220.pixel_format(), PixelFormat::Yuv422p);
712        assert_eq!(
713            DnxhdVariant::Dnxhd220x.pixel_format(),
714            PixelFormat::Yuv422p10le
715        );
716        assert_eq!(DnxhdVariant::DnxhrLb.pixel_format(), PixelFormat::Yuv422p);
717        assert_eq!(DnxhdVariant::DnxhrSq.pixel_format(), PixelFormat::Yuv422p);
718        assert_eq!(DnxhdVariant::DnxhrHq.pixel_format(), PixelFormat::Yuv422p);
719        assert_eq!(
720            DnxhdVariant::DnxhrHqx.pixel_format(),
721            PixelFormat::Yuv422p10le
722        );
723        assert_eq!(
724            DnxhdVariant::DnxhrR444.pixel_format(),
725            PixelFormat::Yuv444p10le
726        );
727    }
728
729    #[test]
730    fn dnxhd_variant_fixed_bitrate_should_return_none_for_dnxhr() {
731        assert_eq!(
732            DnxhdVariant::Dnxhd115.fixed_bitrate_bps(),
733            Some(115_000_000)
734        );
735        assert_eq!(
736            DnxhdVariant::Dnxhd145.fixed_bitrate_bps(),
737            Some(145_000_000)
738        );
739        assert_eq!(
740            DnxhdVariant::Dnxhd220.fixed_bitrate_bps(),
741            Some(220_000_000)
742        );
743        assert_eq!(
744            DnxhdVariant::Dnxhd220x.fixed_bitrate_bps(),
745            Some(220_000_000)
746        );
747        assert!(DnxhdVariant::DnxhrLb.fixed_bitrate_bps().is_none());
748        assert!(DnxhdVariant::DnxhrSq.fixed_bitrate_bps().is_none());
749        assert!(DnxhdVariant::DnxhrHq.fixed_bitrate_bps().is_none());
750        assert!(DnxhdVariant::DnxhrHqx.fixed_bitrate_bps().is_none());
751        assert!(DnxhdVariant::DnxhrR444.fixed_bitrate_bps().is_none());
752    }
753
754    #[test]
755    fn dnxhd_variant_is_dnxhd_should_return_true_only_for_legacy_variants() {
756        assert!(DnxhdVariant::Dnxhd115.is_dnxhd());
757        assert!(DnxhdVariant::Dnxhd145.is_dnxhd());
758        assert!(DnxhdVariant::Dnxhd220.is_dnxhd());
759        assert!(DnxhdVariant::Dnxhd220x.is_dnxhd());
760        assert!(!DnxhdVariant::DnxhrLb.is_dnxhd());
761        assert!(!DnxhdVariant::DnxhrSq.is_dnxhd());
762        assert!(!DnxhdVariant::DnxhrHq.is_dnxhd());
763        assert!(!DnxhdVariant::DnxhrHqx.is_dnxhd());
764        assert!(!DnxhdVariant::DnxhrR444.is_dnxhd());
765    }
766}