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}