Skip to main content

deepshrink_core/
options.rs

1//! Encoding option enums shared between the CLI and the engine.
2//!
3//! These are the decoded, engine-facing forms of the user's flags (the CLI maps
4//! its clap types onto these). Keeping them here lets `plan` stay a pure
5//! function over well-typed inputs.
6
7/// Video codec choice.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum VideoCodec {
10    H264,
11    H265,
12    Av1,
13}
14
15impl VideoCodec {
16    /// The libav encoder name passed to `ffmpeg -c:v`.
17    pub fn encoder(self) -> &'static str {
18        match self {
19            VideoCodec::H264 => "libx264",
20            VideoCodec::H265 => "libx265",
21            // SVT-AV1 is the practical default: far faster than libaom at
22            // comparable quality, and present in mainstream ffmpeg builds.
23            VideoCodec::Av1 => "libsvtav1",
24        }
25    }
26
27    /// A second encoder to try when [`encoder`](Self::encoder) is missing from
28    /// the local ffmpeg build. Only AV1 has one — x264/x265 are universal.
29    pub fn fallback_encoder(self) -> Option<&'static str> {
30        match self {
31            VideoCodec::Av1 => Some("libaom-av1"),
32            _ => None,
33        }
34    }
35
36    /// The `-tag:v` value needed for MP4 compatibility, if any.
37    pub fn mp4_tag(self) -> Option<&'static str> {
38        match self {
39            VideoCodec::H264 => None,
40            // Without hvc1, HEVC in MP4 won't play in QuickTime/Safari.
41            VideoCodec::H265 => Some("hvc1"),
42            VideoCodec::Av1 => Some("av01"),
43        }
44    }
45
46    /// Human-readable label for output.
47    pub fn label(self) -> &'static str {
48        match self {
49            VideoCodec::H264 => "H.264",
50            VideoCodec::H265 => "H.265",
51            VideoCodec::Av1 => "AV1",
52        }
53    }
54
55    /// Inclusive CRF range to search when targeting a VMAF score, best quality
56    /// (lowest CRF) first. x265's CRF scale is shifted ~+6 vs x264 for the same
57    /// perceptual quality, and AV1's runs 0..63 — so the bounds differ per codec.
58    pub fn crf_search_bounds(self) -> (u8, u8) {
59        match self {
60            VideoCodec::H264 => (18, 32),
61            VideoCodec::H265 => (22, 36),
62            VideoCodec::Av1 => (25, 50),
63        }
64    }
65}
66
67/// Audio codec choice (also used for pure-audio in session 003).
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum AudioCodec {
70    Aac,
71    Opus,
72    Mp3,
73}
74
75impl AudioCodec {
76    pub fn encoder(self) -> &'static str {
77        match self {
78            AudioCodec::Aac => "aac",
79            AudioCodec::Opus => "libopus",
80            AudioCodec::Mp3 => "libmp3lame",
81        }
82    }
83
84    /// Output file extension for a pure-audio result.
85    pub fn extension(self) -> &'static str {
86        match self {
87            AudioCodec::Aac => "m4a",
88            AudioCodec::Opus => "opus",
89            AudioCodec::Mp3 => "mp3",
90        }
91    }
92
93    /// Human-readable label for output.
94    pub fn label(self) -> &'static str {
95        match self {
96            AudioCodec::Aac => "AAC",
97            AudioCodec::Opus => "Opus",
98            AudioCodec::Mp3 => "MP3",
99        }
100    }
101}
102
103/// What to do with the audio track inside a video.
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub enum AudioChoice {
106    /// Keep the track, re-encoding at a sensible (budget-aware) bitrate.
107    Keep,
108    /// Keep the track at an explicit bitrate in bits/s.
109    Bitrate(u64),
110    /// Drop the audio entirely.
111    Drop,
112}
113
114/// Target resolution.
115#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub enum ResolutionOpt {
117    /// Let the engine pick (downscale only if bitrate is too low).
118    Auto,
119    /// Force a target height (width follows aspect ratio).
120    Height(u32),
121}
122
123/// Frame-rate cap.
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub enum FpsOpt {
126    Auto,
127    Cap(u32),
128}
129
130/// Speed/quality trade-off, mapped to the encoder preset.
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132pub enum QualityPreset {
133    Fast,
134    Balanced,
135    Max,
136}
137
138impl QualityPreset {
139    /// x264/x265 `-preset` value.
140    pub fn encoder_preset(self) -> &'static str {
141        match self {
142            QualityPreset::Fast => "veryfast",
143            QualityPreset::Balanced => "medium",
144            QualityPreset::Max => "slow",
145        }
146    }
147
148    /// The speed/quality knob for a specific *encoder*, as `(flag, value)`.
149    ///
150    /// Encoders disagree on both the flag and its scale: x264/x265 take named
151    /// `-preset`s, SVT-AV1 takes a numeric `-preset` (0 slowest … 13 fastest),
152    /// and libaom takes `-cpu-used`. Passing an x264 preset name to SVT-AV1 is
153    /// a hard ffmpeg error, so the argv builder must ask per encoder.
154    pub fn speed_flags(self, encoder: &str) -> (&'static str, &'static str) {
155        match encoder {
156            "libsvtav1" => (
157                "-preset",
158                match self {
159                    QualityPreset::Fast => "9",
160                    QualityPreset::Balanced => "7",
161                    QualityPreset::Max => "5",
162                },
163            ),
164            "libaom-av1" => (
165                "-cpu-used",
166                match self {
167                    QualityPreset::Fast => "8",
168                    QualityPreset::Balanced => "5",
169                    QualityPreset::Max => "3",
170                },
171            ),
172            _ => ("-preset", self.encoder_preset()),
173        }
174    }
175
176    /// Default quality-mode CRF for a codec. x265 needs a higher CRF than x264
177    /// for comparable quality, and AV1 higher still, so the numbers are
178    /// codec-specific. These defaults aim for roughly VMAF ~93 (visually
179    /// near-transparent) on typical content.
180    pub fn default_crf(self, codec: VideoCodec) -> u8 {
181        match codec {
182            VideoCodec::H264 => match self {
183                QualityPreset::Fast => 25,
184                QualityPreset::Balanced => 23,
185                QualityPreset::Max => 20,
186            },
187            VideoCodec::H265 => match self {
188                QualityPreset::Fast => 30,
189                QualityPreset::Balanced => 28,
190                QualityPreset::Max => 24,
191            },
192            VideoCodec::Av1 => match self {
193                QualityPreset::Fast => 38,
194                QualityPreset::Balanced => 35,
195                QualityPreset::Max => 30,
196            },
197        }
198    }
199}