1#![allow(dead_code)]
9
10use std::collections::HashMap;
11use std::path::{Path, PathBuf};
12
13pub type Resolution = (u32, u32);
15
16#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct ProxyVariantRecord {
19 pub path: PathBuf,
21 pub resolution: Resolution,
23 pub codec: String,
25 pub video_kbps: u32,
27}
28
29impl ProxyVariantRecord {
30 pub fn new(
32 path: impl Into<PathBuf>,
33 resolution: Resolution,
34 codec: impl Into<String>,
35 video_kbps: u32,
36 ) -> Self {
37 Self {
38 path: path.into(),
39 resolution,
40 codec: codec.into(),
41 video_kbps,
42 }
43 }
44
45 pub fn pixel_count(&self) -> u64 {
47 self.resolution.0 as u64 * self.resolution.1 as u64
48 }
49}
50
51#[derive(Debug, Clone)]
53pub struct ProxyRecord {
54 pub source_path: PathBuf,
56 pub variants: Vec<ProxyVariantRecord>,
58}
59
60impl ProxyRecord {
61 pub fn new(source_path: impl Into<PathBuf>) -> Self {
63 Self {
64 source_path: source_path.into(),
65 variants: Vec::new(),
66 }
67 }
68
69 pub fn add_variant(&mut self, variant: ProxyVariantRecord) {
71 self.variants.push(variant);
72 }
73
74 pub fn has_proxies(&self) -> bool {
76 !self.variants.is_empty()
77 }
78
79 pub fn best_variant_for(&self, target: Resolution) -> Option<&ProxyVariantRecord> {
84 if self.variants.is_empty() {
85 return None;
86 }
87 let target_px = target.0 as i64 * target.1 as i64;
88 self.variants.iter().min_by(|a, b| {
89 let da = (a.pixel_count() as i64 - target_px).unsigned_abs();
90 let db = (b.pixel_count() as i64 - target_px).unsigned_abs();
91 da.cmp(&db).then_with(|| b.video_kbps.cmp(&a.video_kbps))
92 })
93 }
94}
95
96#[derive(Debug, Default)]
98pub struct ProxyRegistryExt {
99 records: HashMap<PathBuf, ProxyRecord>,
100}
101
102impl ProxyRegistryExt {
103 pub fn new() -> Self {
105 Self::default()
106 }
107
108 pub fn insert(&mut self, record: ProxyRecord) {
110 self.records.insert(record.source_path.clone(), record);
111 }
112
113 pub fn add_variant(&mut self, source_path: impl Into<PathBuf>, variant: ProxyVariantRecord) {
117 let path = source_path.into();
118 self.records
119 .entry(path.clone())
120 .or_insert_with(|| ProxyRecord::new(path))
121 .add_variant(variant);
122 }
123
124 pub fn get(&self, source_path: &Path) -> Option<&ProxyRecord> {
126 self.records.get(source_path)
127 }
128
129 pub fn remove(&mut self, source_path: &Path) -> Option<ProxyRecord> {
131 self.records.remove(source_path)
132 }
133
134 pub fn len(&self) -> usize {
136 self.records.len()
137 }
138
139 pub fn is_empty(&self) -> bool {
141 self.records.is_empty()
142 }
143
144 pub fn find_best_proxy(
148 &self,
149 source_path: &Path,
150 target_resolution: Resolution,
151 ) -> Option<&ProxyVariantRecord> {
152 self.records
153 .get(source_path)?
154 .best_variant_for(target_resolution)
155 }
156
157 pub fn sources_with_proxies(&self) -> Vec<&Path> {
159 self.records
160 .values()
161 .filter(|r| r.has_proxies())
162 .map(|r| r.source_path.as_path())
163 .collect()
164 }
165
166 pub fn total_variants(&self) -> usize {
168 self.records.values().map(|r| r.variants.len()).sum()
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 fn make_variant(path: &str, w: u32, h: u32, codec: &str, kbps: u32) -> ProxyVariantRecord {
177 ProxyVariantRecord::new(path, (w, h), codec, kbps)
178 }
179
180 #[test]
181 fn variant_record_pixel_count() {
182 let v = make_variant("p.mp4", 1920, 1080, "h264", 8000);
183 assert_eq!(v.pixel_count(), 1920 * 1080);
184 }
185
186 #[test]
187 fn proxy_record_add_variant() {
188 let mut rec = ProxyRecord::new("/src/clip.mov");
189 assert!(!rec.has_proxies());
190 rec.add_variant(make_variant("/proxy/clip_360.mp4", 640, 360, "h264", 500));
191 assert!(rec.has_proxies());
192 assert_eq!(rec.variants.len(), 1);
193 }
194
195 #[test]
196 fn proxy_record_best_variant_exact_match() {
197 let mut rec = ProxyRecord::new("/src/clip.mov");
198 rec.add_variant(make_variant("/proxy/360p.mp4", 640, 360, "h264", 500));
199 rec.add_variant(make_variant("/proxy/720p.mp4", 1280, 720, "h264", 2000));
200 let best = rec
201 .best_variant_for((1280, 720))
202 .expect("should succeed in test");
203 assert_eq!(best.resolution, (1280, 720));
204 }
205
206 #[test]
207 fn proxy_record_best_variant_closest_pixel_count() {
208 let mut rec = ProxyRecord::new("/src/clip.mov");
209 rec.add_variant(make_variant("/proxy/360p.mp4", 640, 360, "h264", 500));
210 rec.add_variant(make_variant("/proxy/1080p.mp4", 1920, 1080, "h264", 8000));
211 let best = rec
216 .best_variant_for((1280, 720))
217 .expect("should succeed in test");
218 assert_eq!(best.resolution, (640, 360));
219 }
220
221 #[test]
222 fn proxy_record_best_variant_empty_returns_none() {
223 let rec = ProxyRecord::new("/src/clip.mov");
224 assert!(rec.best_variant_for((1280, 720)).is_none());
225 }
226
227 #[test]
228 fn registry_ext_insert_and_get() {
229 let mut reg = ProxyRegistryExt::new();
230 let rec = ProxyRecord::new("/src/a.mov");
231 reg.insert(rec);
232 assert!(reg.get(Path::new("/src/a.mov")).is_some());
233 }
234
235 #[test]
236 fn registry_ext_add_variant_creates_record() {
237 let mut reg = ProxyRegistryExt::new();
238 reg.add_variant(
239 "/src/b.mov",
240 make_variant("/p/b_360.mp4", 640, 360, "h264", 500),
241 );
242 assert!(reg.get(Path::new("/src/b.mov")).is_some());
243 }
244
245 #[test]
246 fn registry_ext_remove() {
247 let mut reg = ProxyRegistryExt::new();
248 reg.insert(ProxyRecord::new("/src/c.mov"));
249 let removed = reg.remove(Path::new("/src/c.mov"));
250 assert!(removed.is_some());
251 assert!(reg.get(Path::new("/src/c.mov")).is_none());
252 }
253
254 #[test]
255 fn registry_ext_len_and_empty() {
256 let mut reg = ProxyRegistryExt::new();
257 assert!(reg.is_empty());
258 reg.insert(ProxyRecord::new("/src/d.mov"));
259 assert_eq!(reg.len(), 1);
260 }
261
262 #[test]
263 fn registry_ext_find_best_proxy() {
264 let mut reg = ProxyRegistryExt::new();
265 reg.add_variant(
266 "/src/e.mov",
267 make_variant("/p/e_360.mp4", 640, 360, "h264", 500),
268 );
269 reg.add_variant(
270 "/src/e.mov",
271 make_variant("/p/e_1080.mp4", 1920, 1080, "h264", 8000),
272 );
273 let best = reg.find_best_proxy(Path::new("/src/e.mov"), (1920, 1080));
274 assert!(best.is_some());
275 assert_eq!(
276 best.expect("should succeed in test").resolution,
277 (1920, 1080)
278 );
279 }
280
281 #[test]
282 fn registry_ext_find_best_proxy_not_found() {
283 let reg = ProxyRegistryExt::new();
284 assert!(reg
285 .find_best_proxy(Path::new("/nonexistent.mov"), (1280, 720))
286 .is_none());
287 }
288
289 #[test]
290 fn registry_ext_sources_with_proxies() {
291 let mut reg = ProxyRegistryExt::new();
292 reg.add_variant(
293 "/src/f.mov",
294 make_variant("/p/f.mp4", 640, 360, "h264", 500),
295 );
296 reg.insert(ProxyRecord::new("/src/g.mov")); let sources = reg.sources_with_proxies();
298 assert_eq!(sources.len(), 1);
299 }
300
301 #[test]
302 fn registry_ext_total_variants() {
303 let mut reg = ProxyRegistryExt::new();
304 reg.add_variant(
305 "/src/h.mov",
306 make_variant("/p/h1.mp4", 640, 360, "h264", 500),
307 );
308 reg.add_variant(
309 "/src/h.mov",
310 make_variant("/p/h2.mp4", 1280, 720, "h264", 2000),
311 );
312 reg.add_variant(
313 "/src/i.mov",
314 make_variant("/p/i1.mp4", 640, 360, "h264", 500),
315 );
316 assert_eq!(reg.total_variants(), 3);
317 }
318
319 #[test]
320 fn registry_ext_prefer_higher_bitrate_on_tie() {
321 let mut rec = ProxyRecord::new("/src/j.mov");
322 rec.add_variant(make_variant("/p/j_low.mp4", 1280, 720, "h264", 1000));
324 rec.add_variant(make_variant("/p/j_high.mp4", 1280, 720, "h264", 5000));
325 let best = rec
326 .best_variant_for((1280, 720))
327 .expect("should succeed in test");
328 assert_eq!(best.video_kbps, 5000);
329 }
330}