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 /// Apple's hardware encoder (VideoToolbox) for this codec, if it has one.
30 pub fn hardware_encoder(self) -> Option<&'static str> {
31 match self {
32 VideoCodec::H264 => Some("h264_videotoolbox"),
33 VideoCodec::H265 => Some("hevc_videotoolbox"),
34 VideoCodec::Av1 => None,
35 }
36 }
37
38 pub fn fallback_encoder(self) -> Option<&'static str> {
39 match self {
40 VideoCodec::Av1 => Some("libaom-av1"),
41 _ => None,
42 }
43 }
44
45 /// The `-tag:v` value needed for MP4 compatibility, if any.
46 pub fn mp4_tag(self) -> Option<&'static str> {
47 match self {
48 VideoCodec::H264 => None,
49 // Without hvc1, HEVC in MP4 won't play in QuickTime/Safari.
50 VideoCodec::H265 => Some("hvc1"),
51 VideoCodec::Av1 => Some("av01"),
52 }
53 }
54
55 /// Human-readable label for output.
56 pub fn label(self) -> &'static str {
57 match self {
58 VideoCodec::H264 => "H.264",
59 VideoCodec::H265 => "H.265",
60 VideoCodec::Av1 => "AV1",
61 }
62 }
63
64 /// Inclusive CRF range to search when targeting a VMAF score, best quality
65 /// (lowest CRF) first. x265's CRF scale is shifted ~+6 vs x264 for the same
66 /// perceptual quality, and AV1's runs 0..63 — so the bounds differ per codec.
67 pub fn crf_search_bounds(self) -> (u8, u8) {
68 match self {
69 VideoCodec::H264 => (18, 32),
70 VideoCodec::H265 => (22, 36),
71 VideoCodec::Av1 => (25, 50),
72 }
73 }
74}
75
76/// Audio codec choice (also used for pure-audio in session 003).
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum AudioCodec {
79 Aac,
80 Opus,
81 Mp3,
82}
83
84impl AudioCodec {
85 pub fn encoder(self) -> &'static str {
86 match self {
87 AudioCodec::Aac => "aac",
88 AudioCodec::Opus => "libopus",
89 AudioCodec::Mp3 => "libmp3lame",
90 }
91 }
92
93 /// Output file extension for a pure-audio result.
94 pub fn extension(self) -> &'static str {
95 match self {
96 AudioCodec::Aac => "m4a",
97 AudioCodec::Opus => "opus",
98 AudioCodec::Mp3 => "mp3",
99 }
100 }
101
102 /// Human-readable label for output.
103 pub fn label(self) -> &'static str {
104 match self {
105 AudioCodec::Aac => "AAC",
106 AudioCodec::Opus => "Opus",
107 AudioCodec::Mp3 => "MP3",
108 }
109 }
110}
111
112/// What to do with the audio track inside a video.
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum AudioChoice {
115 /// Keep the track, re-encoding at a sensible (budget-aware) bitrate.
116 Keep,
117 /// Keep the track at an explicit bitrate in bits/s.
118 Bitrate(u64),
119 /// Drop the audio entirely.
120 Drop,
121}
122
123/// Target resolution.
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub enum ResolutionOpt {
126 /// Let the engine pick (downscale only if bitrate is too low).
127 Auto,
128 /// Force a target height (width follows aspect ratio).
129 Height(u32),
130}
131
132/// Frame-rate cap.
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
134pub enum FpsOpt {
135 Auto,
136 Cap(u32),
137}
138
139/// Speed/quality trade-off, mapped to the encoder preset.
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub enum QualityPreset {
142 Fast,
143 Balanced,
144 Max,
145}
146
147impl QualityPreset {
148 /// x264/x265 `-preset` value.
149 pub fn encoder_preset(self) -> &'static str {
150 match self {
151 QualityPreset::Fast => "veryfast",
152 QualityPreset::Balanced => "medium",
153 QualityPreset::Max => "slow",
154 }
155 }
156
157 /// The speed/quality knob for a specific *encoder*, as `(flag, value)`.
158 ///
159 /// Encoders disagree on both the flag and its scale: x264/x265 take named
160 /// `-preset`s, SVT-AV1 takes a numeric `-preset` (0 slowest … 13 fastest),
161 /// and libaom takes `-cpu-used`. Passing an x264 preset name to SVT-AV1 is
162 /// a hard ffmpeg error, so the argv builder must ask per encoder.
163 pub fn speed_flags(self, encoder: &str) -> (&'static str, &'static str) {
164 match encoder {
165 "libsvtav1" => (
166 "-preset",
167 match self {
168 QualityPreset::Fast => "9",
169 QualityPreset::Balanced => "7",
170 QualityPreset::Max => "5",
171 },
172 ),
173 "libaom-av1" => (
174 "-cpu-used",
175 match self {
176 QualityPreset::Fast => "8",
177 QualityPreset::Balanced => "5",
178 QualityPreset::Max => "3",
179 },
180 ),
181 _ => ("-preset", self.encoder_preset()),
182 }
183 }
184
185 /// Default quality-mode CRF for a codec. x265 needs a higher CRF than x264
186 /// for comparable quality, and AV1 higher still, so the numbers are
187 /// codec-specific. These defaults aim for roughly VMAF ~93 (visually
188 /// near-transparent) on typical content.
189 /// VideoToolbox constant quality (`-q:v`, 1–100) giving about the same
190 /// VMAF as [`Self::default_crf`] with the software encoder — calibrated on
191 /// a 4K iPhone clip. Apple H.264 lands near x264's size; Apple HEVC is ~50 %
192 /// larger than x265 at that quality. `None`: no hardware encoder (AV1).
193 pub fn default_hw_quality(self, codec: VideoCodec) -> Option<u8> {
194 match codec {
195 VideoCodec::H264 => Some(match self {
196 QualityPreset::Fast => 55,
197 QualityPreset::Balanced => 66,
198 QualityPreset::Max => 72,
199 }),
200 VideoCodec::H265 => Some(match self {
201 QualityPreset::Fast => 52,
202 QualityPreset::Balanced => 57,
203 QualityPreset::Max => 69,
204 }),
205 VideoCodec::Av1 => None,
206 }
207 }
208
209 pub fn default_crf(self, codec: VideoCodec) -> u8 {
210 match codec {
211 VideoCodec::H264 => match self {
212 QualityPreset::Fast => 25,
213 QualityPreset::Balanced => 23,
214 QualityPreset::Max => 20,
215 },
216 VideoCodec::H265 => match self {
217 QualityPreset::Fast => 30,
218 QualityPreset::Balanced => 28,
219 QualityPreset::Max => 24,
220 },
221 VideoCodec::Av1 => match self {
222 QualityPreset::Fast => 38,
223 QualityPreset::Balanced => 35,
224 QualityPreset::Max => 30,
225 },
226 }
227 }
228}