Skip to main content

oximedia_proxy/
format_compat.rs

1//! Format compatibility matching between proxy and original media formats.
2#![allow(dead_code)]
3
4/// Supported proxy format types.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6pub enum ProxyFormatType {
7    /// H.264 in an MP4 container — widely compatible edit-ready format.
8    H264Mp4,
9    /// H.264 in an MXF container — broadcast edit-ready format.
10    H264Mxf,
11    /// Apple ProRes 422 Proxy — edit-ready for Final Cut Pro workflows.
12    ProResProxy,
13    /// DNxHD/DNxHR — edit-ready for Avid workflows.
14    DnxHd,
15    /// VP9 in WebM — web-delivery proxy format.
16    Vp9Webm,
17    /// JPEG 2000 — high-quality proxy for DCP and broadcast.
18    Jpeg2000,
19}
20
21impl ProxyFormatType {
22    /// Return `true` if this format is considered edit-ready (fast random access,
23    /// intra-only or low-GOP).
24    pub fn is_edit_ready(self) -> bool {
25        matches!(
26            self,
27            Self::H264Mxf | Self::ProResProxy | Self::DnxHd | Self::Jpeg2000
28        )
29    }
30
31    /// Return a human-readable label for this format.
32    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    /// Return a typical file extension for this format.
44    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/// Describes format compatibility constraints for a proxy.
55#[derive(Debug, Clone)]
56pub struct FormatCompat {
57    /// The proxy format type.
58    pub format: ProxyFormatType,
59    /// Maximum width this format/config can handle.
60    pub max_width: u32,
61    /// Maximum height this format/config can handle.
62    pub max_height: u32,
63    /// Whether this entry is preferred for new projects.
64    pub preferred: bool,
65}
66
67impl FormatCompat {
68    /// Create a new `FormatCompat` entry.
69    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    /// Mark this entry as preferred.
79    pub fn as_preferred(mut self) -> Self {
80        self.preferred = true;
81        self
82    }
83
84    /// Return `true` if the given resolution fits within this format's limits.
85    pub fn resolution_ok(&self, width: u32, height: u32) -> bool {
86        width <= self.max_width && height <= self.max_height
87    }
88}
89
90/// Matcher that selects the best compatible proxy format for a given resolution.
91#[derive(Debug, Default)]
92pub struct ProxyFormatMatcher {
93    entries: Vec<FormatCompat>,
94}
95
96impl ProxyFormatMatcher {
97    /// Create an empty matcher.
98    pub fn new() -> Self {
99        Self::default()
100    }
101
102    /// Create a matcher pre-populated with common broadcast/editing formats.
103    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    /// Add a format compatibility entry.
113    pub fn add(&mut self, compat: FormatCompat) {
114        self.entries.push(compat);
115    }
116
117    /// Return all entries whose resolution limit accommodates the given dimensions.
118    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    /// Return the best match: preferred first, then highest-resolution limit.
126    pub fn best_match(&self, width: u32, height: u32) -> Option<&FormatCompat> {
127        let compatible = self.find_compatible(width, height);
128        // Preferred formats come first.
129        if let Some(pref) = compatible.iter().find(|e| e.preferred) {
130            return Some(pref);
131        }
132        // Otherwise pick the one with the largest max area (most capable).
133        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        // All standard entries support at least 1920x1080 or 3840x2160
197        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        // Only entries with max >= 3840x2160 should pass
205        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}