1#![allow(dead_code)]
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum QualityPreset {
12 Mobile,
14 Web,
16 Editorial,
18 ColorGrade,
20 Archive,
22}
23
24impl QualityPreset {
25 pub fn name(self) -> &'static str {
27 match self {
28 Self::Mobile => "Mobile",
29 Self::Web => "Web",
30 Self::Editorial => "Editorial",
31 Self::ColorGrade => "Color Grade",
32 Self::Archive => "Archive",
33 }
34 }
35
36 pub fn upgrade(self) -> Option<Self> {
38 match self {
39 Self::Mobile => Some(Self::Web),
40 Self::Web => Some(Self::Editorial),
41 Self::Editorial => Some(Self::ColorGrade),
42 Self::ColorGrade => Some(Self::Archive),
43 Self::Archive => None,
44 }
45 }
46
47 pub fn downgrade(self) -> Option<Self> {
49 match self {
50 Self::Mobile => None,
51 Self::Web => Some(Self::Mobile),
52 Self::Editorial => Some(Self::Web),
53 Self::ColorGrade => Some(Self::Editorial),
54 Self::Archive => Some(Self::ColorGrade),
55 }
56 }
57
58 pub fn all() -> &'static [QualityPreset] {
60 &[
61 Self::Mobile,
62 Self::Web,
63 Self::Editorial,
64 Self::ColorGrade,
65 Self::Archive,
66 ]
67 }
68}
69
70impl std::fmt::Display for QualityPreset {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 f.write_str(self.name())
73 }
74}
75
76#[derive(Debug, Clone)]
78pub struct ProxyFormatConfig {
79 pub preset: QualityPreset,
81 pub video_kbps: u32,
83 pub audio_kbps: u32,
85 pub max_width: u32,
87 pub max_height: u32,
89 pub container: &'static str,
91 pub codec: &'static str,
93}
94
95impl ProxyFormatConfig {
96 pub fn for_preset(preset: QualityPreset) -> Self {
98 match preset {
99 QualityPreset::Mobile => Self {
100 preset,
101 video_kbps: 400,
102 audio_kbps: 64,
103 max_width: 640,
104 max_height: 360,
105 container: "mp4",
106 codec: "h264",
107 },
108 QualityPreset::Web => Self {
109 preset,
110 video_kbps: 1_500,
111 audio_kbps: 128,
112 max_width: 1_280,
113 max_height: 720,
114 container: "mp4",
115 codec: "h264",
116 },
117 QualityPreset::Editorial => Self {
118 preset,
119 video_kbps: 8_000,
120 audio_kbps: 192,
121 max_width: 1_920,
122 max_height: 1_080,
123 container: "mp4",
124 codec: "h264",
125 },
126 QualityPreset::ColorGrade => Self {
127 preset,
128 video_kbps: 45_000,
129 audio_kbps: 320,
130 max_width: 3_840,
131 max_height: 2_160,
132 container: "mov",
133 codec: "prores",
134 },
135 QualityPreset::Archive => Self {
136 preset,
137 video_kbps: 185_000,
138 audio_kbps: 320,
139 max_width: 3_840,
140 max_height: 2_160,
141 container: "mxf",
142 codec: "dnxhd",
143 },
144 }
145 }
146
147 pub fn total_kbps(&self) -> u32 {
149 self.video_kbps + self.audio_kbps
150 }
151
152 pub fn fits_budget(&self, budget_kbps: u32) -> bool {
154 self.total_kbps() <= budget_kbps
155 }
156
157 pub fn supports_resolution(&self, width: u32, height: u32) -> bool {
159 width <= self.max_width && height <= self.max_height
160 }
161}
162
163#[derive(Default)]
165pub struct FormatSelector {
166 configs: Vec<ProxyFormatConfig>,
167}
168
169impl FormatSelector {
170 pub fn new() -> Self {
172 let configs = QualityPreset::all()
173 .iter()
174 .map(|&p| ProxyFormatConfig::for_preset(p))
175 .collect();
176 Self { configs }
177 }
178
179 pub fn with_configs(configs: Vec<ProxyFormatConfig>) -> Self {
181 Self { configs }
182 }
183
184 pub fn select_for_budget(&self, budget_kbps: u32) -> Option<&ProxyFormatConfig> {
188 let mut sorted: Vec<&ProxyFormatConfig> = self.configs.iter().collect();
190 sorted.sort_by(|a, b| b.video_kbps.cmp(&a.video_kbps));
191 sorted.into_iter().find(|c| c.fits_budget(budget_kbps))
192 }
193
194 pub fn get(&self, preset: QualityPreset) -> Option<&ProxyFormatConfig> {
196 self.configs.iter().find(|c| c.preset == preset)
197 }
198
199 pub fn all_ascending(&self) -> Vec<&ProxyFormatConfig> {
201 let mut v: Vec<&ProxyFormatConfig> = self.configs.iter().collect();
202 v.sort_by_key(|c| c.video_kbps);
203 v
204 }
205
206 pub fn len(&self) -> usize {
208 self.configs.len()
209 }
210
211 pub fn is_empty(&self) -> bool {
213 self.configs.is_empty()
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn quality_preset_name() {
223 assert_eq!(QualityPreset::Mobile.name(), "Mobile");
224 assert_eq!(QualityPreset::Archive.name(), "Archive");
225 }
226
227 #[test]
228 fn quality_preset_display() {
229 assert_eq!(format!("{}", QualityPreset::Editorial), "Editorial");
230 }
231
232 #[test]
233 fn quality_preset_upgrade_chain() {
234 assert_eq!(QualityPreset::Mobile.upgrade(), Some(QualityPreset::Web));
235 assert_eq!(QualityPreset::Archive.upgrade(), None);
236 }
237
238 #[test]
239 fn quality_preset_downgrade_chain() {
240 assert_eq!(QualityPreset::Web.downgrade(), Some(QualityPreset::Mobile));
241 assert_eq!(QualityPreset::Mobile.downgrade(), None);
242 }
243
244 #[test]
245 fn quality_preset_all_count() {
246 assert_eq!(QualityPreset::all().len(), 5);
247 }
248
249 #[test]
250 fn proxy_format_config_total_kbps() {
251 let cfg = ProxyFormatConfig::for_preset(QualityPreset::Mobile);
252 assert_eq!(cfg.total_kbps(), 400 + 64);
253 }
254
255 #[test]
256 fn proxy_format_config_fits_budget_true() {
257 let cfg = ProxyFormatConfig::for_preset(QualityPreset::Mobile);
258 assert!(cfg.fits_budget(1_000));
259 }
260
261 #[test]
262 fn proxy_format_config_fits_budget_false() {
263 let cfg = ProxyFormatConfig::for_preset(QualityPreset::Archive);
264 assert!(!cfg.fits_budget(1_000));
265 }
266
267 #[test]
268 fn proxy_format_config_supports_resolution_ok() {
269 let cfg = ProxyFormatConfig::for_preset(QualityPreset::Editorial);
270 assert!(cfg.supports_resolution(1_920, 1_080));
271 }
272
273 #[test]
274 fn proxy_format_config_supports_resolution_fail() {
275 let cfg = ProxyFormatConfig::for_preset(QualityPreset::Mobile);
276 assert!(!cfg.supports_resolution(1_920, 1_080));
277 }
278
279 #[test]
280 fn proxy_format_config_codec_preset_mobile() {
281 let cfg = ProxyFormatConfig::for_preset(QualityPreset::Mobile);
282 assert_eq!(cfg.codec, "h264");
283 assert_eq!(cfg.container, "mp4");
284 }
285
286 #[test]
287 fn proxy_format_config_codec_preset_archive() {
288 let cfg = ProxyFormatConfig::for_preset(QualityPreset::Archive);
289 assert_eq!(cfg.codec, "dnxhd");
290 assert_eq!(cfg.container, "mxf");
291 }
292
293 #[test]
294 fn format_selector_new_has_all_presets() {
295 let sel = FormatSelector::new();
296 assert_eq!(sel.len(), QualityPreset::all().len());
297 }
298
299 #[test]
300 fn format_selector_get_by_preset() {
301 let sel = FormatSelector::new();
302 let cfg = sel.get(QualityPreset::Web);
303 assert!(cfg.is_some());
304 assert_eq!(
305 cfg.expect("should succeed in test").preset,
306 QualityPreset::Web
307 );
308 }
309
310 #[test]
311 fn format_selector_select_for_budget_returns_none_if_too_low() {
312 let sel = FormatSelector::new();
313 assert!(sel.select_for_budget(1).is_none());
315 }
316
317 #[test]
318 fn format_selector_select_for_budget_returns_mobile_for_small_budget() {
319 let sel = FormatSelector::new();
320 let cfg = sel.select_for_budget(500);
321 assert!(cfg.is_some());
322 assert_eq!(
323 cfg.expect("should succeed in test").preset,
324 QualityPreset::Mobile
325 );
326 }
327
328 #[test]
329 fn format_selector_all_ascending_ordered() {
330 let sel = FormatSelector::new();
331 let ascending = sel.all_ascending();
332 for pair in ascending.windows(2) {
333 assert!(pair[0].video_kbps <= pair[1].video_kbps);
334 }
335 }
336
337 #[test]
338 fn format_selector_is_empty_false() {
339 let sel = FormatSelector::new();
340 assert!(!sel.is_empty());
341 }
342}