oximedia_proxy/
format_compat.rs1#![allow(dead_code)]
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6pub enum ProxyFormatType {
7 H264Mp4,
9 H264Mxf,
11 ProResProxy,
13 DnxHd,
15 Vp9Webm,
17 Jpeg2000,
19}
20
21impl ProxyFormatType {
22 pub fn is_edit_ready(self) -> bool {
25 matches!(
26 self,
27 Self::H264Mxf | Self::ProResProxy | Self::DnxHd | Self::Jpeg2000
28 )
29 }
30
31 pub fn label(self) -> &'static str {
33 match self {
34 Self::H264Mp4 => "H.264 / MP4",
35 Self::H264Mxf => "H.264 / MXF",
36 Self::ProResProxy => "Apple ProRes 422 Proxy",
37 Self::DnxHd => "Avid DNxHD/DNxHR",
38 Self::Vp9Webm => "VP9 / WebM",
39 Self::Jpeg2000 => "JPEG 2000",
40 }
41 }
42
43 pub fn extension(self) -> &'static str {
45 match self {
46 Self::H264Mp4 => "mp4",
47 Self::H264Mxf | Self::DnxHd | Self::Jpeg2000 => "mxf",
48 Self::ProResProxy => "mov",
49 Self::Vp9Webm => "webm",
50 }
51 }
52}
53
54#[derive(Debug, Clone)]
56pub struct FormatCompat {
57 pub format: ProxyFormatType,
59 pub max_width: u32,
61 pub max_height: u32,
63 pub preferred: bool,
65}
66
67impl FormatCompat {
68 pub fn new(format: ProxyFormatType, max_width: u32, max_height: u32) -> Self {
70 Self {
71 format,
72 max_width,
73 max_height,
74 preferred: false,
75 }
76 }
77
78 pub fn as_preferred(mut self) -> Self {
80 self.preferred = true;
81 self
82 }
83
84 pub fn resolution_ok(&self, width: u32, height: u32) -> bool {
86 width <= self.max_width && height <= self.max_height
87 }
88}
89
90#[derive(Debug, Default)]
92pub struct ProxyFormatMatcher {
93 entries: Vec<FormatCompat>,
94}
95
96impl ProxyFormatMatcher {
97 pub fn new() -> Self {
99 Self::default()
100 }
101
102 pub fn standard() -> Self {
104 let mut m = Self::new();
105 m.add(FormatCompat::new(ProxyFormatType::ProResProxy, 3840, 2160).as_preferred());
106 m.add(FormatCompat::new(ProxyFormatType::DnxHd, 3840, 2160));
107 m.add(FormatCompat::new(ProxyFormatType::H264Mp4, 1920, 1080));
108 m.add(FormatCompat::new(ProxyFormatType::Vp9Webm, 1920, 1080));
109 m
110 }
111
112 pub fn add(&mut self, compat: FormatCompat) {
114 self.entries.push(compat);
115 }
116
117 pub fn find_compatible(&self, width: u32, height: u32) -> Vec<&FormatCompat> {
119 self.entries
120 .iter()
121 .filter(|e| e.resolution_ok(width, height))
122 .collect()
123 }
124
125 pub fn best_match(&self, width: u32, height: u32) -> Option<&FormatCompat> {
127 let compatible = self.find_compatible(width, height);
128 if let Some(pref) = compatible.iter().find(|e| e.preferred) {
130 return Some(pref);
131 }
132 compatible
134 .into_iter()
135 .max_by_key(|e| e.max_width * e.max_height)
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn test_format_type_is_edit_ready_true() {
145 assert!(ProxyFormatType::ProResProxy.is_edit_ready());
146 assert!(ProxyFormatType::DnxHd.is_edit_ready());
147 assert!(ProxyFormatType::H264Mxf.is_edit_ready());
148 assert!(ProxyFormatType::Jpeg2000.is_edit_ready());
149 }
150
151 #[test]
152 fn test_format_type_is_edit_ready_false() {
153 assert!(!ProxyFormatType::H264Mp4.is_edit_ready());
154 assert!(!ProxyFormatType::Vp9Webm.is_edit_ready());
155 }
156
157 #[test]
158 fn test_format_type_label_not_empty() {
159 for fmt in [
160 ProxyFormatType::H264Mp4,
161 ProxyFormatType::H264Mxf,
162 ProxyFormatType::ProResProxy,
163 ProxyFormatType::DnxHd,
164 ProxyFormatType::Vp9Webm,
165 ProxyFormatType::Jpeg2000,
166 ] {
167 assert!(!fmt.label().is_empty());
168 }
169 }
170
171 #[test]
172 fn test_format_type_extension() {
173 assert_eq!(ProxyFormatType::H264Mp4.extension(), "mp4");
174 assert_eq!(ProxyFormatType::ProResProxy.extension(), "mov");
175 assert_eq!(ProxyFormatType::Vp9Webm.extension(), "webm");
176 }
177
178 #[test]
179 fn test_format_compat_resolution_ok() {
180 let fc = FormatCompat::new(ProxyFormatType::H264Mp4, 1920, 1080);
181 assert!(fc.resolution_ok(1920, 1080));
182 assert!(fc.resolution_ok(1280, 720));
183 assert!(!fc.resolution_ok(3840, 2160));
184 }
185
186 #[test]
187 fn test_format_compat_preferred_flag() {
188 let fc = FormatCompat::new(ProxyFormatType::ProResProxy, 3840, 2160).as_preferred();
189 assert!(fc.preferred);
190 }
191
192 #[test]
193 fn test_matcher_find_compatible_all() {
194 let matcher = ProxyFormatMatcher::standard();
195 let results = matcher.find_compatible(1280, 720);
196 assert!(!results.is_empty());
198 }
199
200 #[test]
201 fn test_matcher_find_compatible_4k_filters_hd_only() {
202 let matcher = ProxyFormatMatcher::standard();
203 let results = matcher.find_compatible(3840, 2160);
204 for entry in &results {
206 assert!(entry.resolution_ok(3840, 2160));
207 }
208 }
209
210 #[test]
211 fn test_matcher_best_match_prefers_preferred() {
212 let matcher = ProxyFormatMatcher::standard();
213 let best = matcher
214 .best_match(1280, 720)
215 .expect("should succeed in test");
216 assert!(best.preferred);
217 }
218
219 #[test]
220 fn test_matcher_best_match_4k() {
221 let matcher = ProxyFormatMatcher::standard();
222 let best = matcher
223 .best_match(3840, 2160)
224 .expect("should succeed in test");
225 assert!(best.resolution_ok(3840, 2160));
226 }
227
228 #[test]
229 fn test_matcher_best_match_empty_returns_none() {
230 let matcher = ProxyFormatMatcher::new();
231 assert!(matcher.best_match(1920, 1080).is_none());
232 }
233
234 #[test]
235 fn test_matcher_add_single_entry() {
236 let mut matcher = ProxyFormatMatcher::new();
237 matcher.add(FormatCompat::new(ProxyFormatType::DnxHd, 1920, 1080));
238 let best = matcher
239 .best_match(1920, 1080)
240 .expect("should succeed in test");
241 assert_eq!(best.format, ProxyFormatType::DnxHd);
242 }
243
244 #[test]
245 fn test_matcher_no_compatible_for_oversized() {
246 let mut matcher = ProxyFormatMatcher::new();
247 matcher.add(FormatCompat::new(ProxyFormatType::H264Mp4, 640, 480));
248 let results = matcher.find_compatible(1920, 1080);
249 assert!(results.is_empty());
250 }
251}