Skip to main content

oximedia_proxy/
proxy_quality.rs

1//! Proxy quality tier definitions and bandwidth-adaptive selection.
2//!
3//! Provides `ProxyQualityTier`, `ProxyQualityConfig`, and `ProxyQualitySelector`
4//! for selecting the appropriate proxy tier for a given network or storage context.
5
6#![allow(dead_code)]
7
8/// Proxy quality tier for different stages of the offline/online workflow.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
10pub enum ProxyQualityTier {
11    /// Rough-cut editing — smallest files, lowest resolution.
12    Draft,
13    /// Client review — medium files, visually acceptable quality.
14    Review,
15    /// Delivery / conform — highest quality proxy allowed.
16    Delivery,
17}
18
19impl ProxyQualityTier {
20    /// Maximum resolution cap for this tier (width × height).
21    pub fn resolution_cap(self) -> (u32, u32) {
22        match self {
23            Self::Draft => (640, 360),
24            Self::Review => (1280, 720),
25            Self::Delivery => (1920, 1080),
26        }
27    }
28
29    /// Return a short human-readable label.
30    pub fn label(self) -> &'static str {
31        match self {
32            Self::Draft => "Draft",
33            Self::Review => "Review",
34            Self::Delivery => "Delivery",
35        }
36    }
37
38    /// Return the next higher tier, or `None` if already at `Delivery`.
39    pub fn upgrade(self) -> Option<Self> {
40        match self {
41            Self::Draft => Some(Self::Review),
42            Self::Review => Some(Self::Delivery),
43            Self::Delivery => None,
44        }
45    }
46
47    /// Return the next lower tier, or `None` if already at `Draft`.
48    pub fn downgrade(self) -> Option<Self> {
49        match self {
50            Self::Draft => None,
51            Self::Review => Some(Self::Draft),
52            Self::Delivery => Some(Self::Review),
53        }
54    }
55}
56
57/// Configuration for a specific proxy quality tier.
58#[derive(Debug, Clone)]
59pub struct ProxyQualityConfig {
60    /// The tier this config describes.
61    pub tier: ProxyQualityTier,
62    /// Target video bitrate in kbps.
63    pub video_bitrate_kbps: u32,
64    /// Target audio bitrate in kbps.
65    pub audio_bitrate_kbps: u32,
66    /// Container/codec hint (e.g. "h264", "vp9").
67    pub codec_hint: String,
68    /// Frames per second (0 = match source).
69    pub fps_cap: f32,
70}
71
72impl ProxyQualityConfig {
73    /// Create a new config for the given tier with default parameters.
74    pub fn new(tier: ProxyQualityTier) -> Self {
75        let (video_bitrate_kbps, audio_bitrate_kbps, codec_hint) = match tier {
76            ProxyQualityTier::Draft => (500, 64, "h264"),
77            ProxyQualityTier::Review => (2000, 128, "h264"),
78            ProxyQualityTier::Delivery => (8000, 320, "h264"),
79        };
80        Self {
81            tier,
82            video_bitrate_kbps,
83            audio_bitrate_kbps,
84            codec_hint: codec_hint.to_string(),
85            fps_cap: 0.0,
86        }
87    }
88
89    /// Return total bitrate in kbps (video + audio).
90    pub fn bitrate_kbps(&self) -> u32 {
91        self.video_bitrate_kbps + self.audio_bitrate_kbps
92    }
93
94    /// Return `true` if total bitrate fits within `budget_kbps`.
95    pub fn fits_budget(&self, budget_kbps: u32) -> bool {
96        self.bitrate_kbps() <= budget_kbps
97    }
98
99    /// Effective resolution cap for this config.
100    pub fn resolution_cap(&self) -> (u32, u32) {
101        self.tier.resolution_cap()
102    }
103}
104
105/// Selects the best `ProxyQualityTier` for a given available bandwidth.
106pub struct ProxyQualitySelector {
107    configs: Vec<ProxyQualityConfig>,
108}
109
110impl ProxyQualitySelector {
111    /// Create a selector populated with default configs for all three tiers.
112    pub fn new() -> Self {
113        Self {
114            configs: vec![
115                ProxyQualityConfig::new(ProxyQualityTier::Draft),
116                ProxyQualityConfig::new(ProxyQualityTier::Review),
117                ProxyQualityConfig::new(ProxyQualityTier::Delivery),
118            ],
119        }
120    }
121
122    /// Create a selector with custom configs.
123    pub fn with_configs(configs: Vec<ProxyQualityConfig>) -> Self {
124        Self { configs }
125    }
126
127    /// Select the highest-quality tier that fits `available_bandwidth_kbps`.
128    /// Returns `None` if even the lowest tier exceeds the budget.
129    pub fn select_for_bandwidth(
130        &self,
131        available_bandwidth_kbps: u32,
132    ) -> Option<&ProxyQualityConfig> {
133        // Sort descending by quality (tier), pick first that fits
134        let mut sorted: Vec<&ProxyQualityConfig> = self.configs.iter().collect();
135        sorted.sort_by(|a, b| b.tier.cmp(&a.tier));
136        sorted
137            .into_iter()
138            .find(|c| c.fits_budget(available_bandwidth_kbps))
139    }
140
141    /// Return all configs sorted from lowest to highest quality.
142    pub fn all_tiers(&self) -> Vec<&ProxyQualityConfig> {
143        let mut v: Vec<&ProxyQualityConfig> = self.configs.iter().collect();
144        v.sort_by_key(|c| c.tier);
145        v
146    }
147}
148
149impl Default for ProxyQualitySelector {
150    fn default() -> Self {
151        Self::new()
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn test_draft_resolution_cap() {
161        assert_eq!(ProxyQualityTier::Draft.resolution_cap(), (640, 360));
162    }
163
164    #[test]
165    fn test_review_resolution_cap() {
166        assert_eq!(ProxyQualityTier::Review.resolution_cap(), (1280, 720));
167    }
168
169    #[test]
170    fn test_delivery_resolution_cap() {
171        assert_eq!(ProxyQualityTier::Delivery.resolution_cap(), (1920, 1080));
172    }
173
174    #[test]
175    fn test_tier_upgrade() {
176        assert_eq!(
177            ProxyQualityTier::Draft.upgrade(),
178            Some(ProxyQualityTier::Review)
179        );
180        assert_eq!(ProxyQualityTier::Delivery.upgrade(), None);
181    }
182
183    #[test]
184    fn test_tier_downgrade() {
185        assert_eq!(
186            ProxyQualityTier::Delivery.downgrade(),
187            Some(ProxyQualityTier::Review)
188        );
189        assert_eq!(ProxyQualityTier::Draft.downgrade(), None);
190    }
191
192    #[test]
193    fn test_tier_ordering() {
194        assert!(ProxyQualityTier::Draft < ProxyQualityTier::Review);
195        assert!(ProxyQualityTier::Review < ProxyQualityTier::Delivery);
196    }
197
198    #[test]
199    fn test_config_bitrate_kbps() {
200        let cfg = ProxyQualityConfig::new(ProxyQualityTier::Draft);
201        assert_eq!(cfg.bitrate_kbps(), 564); // 500 + 64
202    }
203
204    #[test]
205    fn test_config_fits_budget_true() {
206        let cfg = ProxyQualityConfig::new(ProxyQualityTier::Draft);
207        assert!(cfg.fits_budget(1000));
208    }
209
210    #[test]
211    fn test_config_fits_budget_false() {
212        let cfg = ProxyQualityConfig::new(ProxyQualityTier::Delivery);
213        assert!(!cfg.fits_budget(100));
214    }
215
216    #[test]
217    fn test_selector_selects_draft_for_low_bandwidth() {
218        let sel = ProxyQualitySelector::new();
219        let result = sel.select_for_bandwidth(600);
220        assert!(result.is_some());
221        assert_eq!(
222            result.expect("should succeed in test").tier,
223            ProxyQualityTier::Draft
224        );
225    }
226
227    #[test]
228    fn test_selector_selects_delivery_for_high_bandwidth() {
229        let sel = ProxyQualitySelector::new();
230        let result = sel.select_for_bandwidth(50_000);
231        assert!(result.is_some());
232        assert_eq!(
233            result.expect("should succeed in test").tier,
234            ProxyQualityTier::Delivery
235        );
236    }
237
238    #[test]
239    fn test_selector_returns_none_if_too_low() {
240        let sel = ProxyQualitySelector::new();
241        let result = sel.select_for_bandwidth(10); // below all tiers
242        assert!(result.is_none());
243    }
244
245    #[test]
246    fn test_selector_all_tiers_sorted() {
247        let sel = ProxyQualitySelector::new();
248        let tiers: Vec<ProxyQualityTier> = sel.all_tiers().iter().map(|c| c.tier).collect();
249        assert_eq!(
250            tiers,
251            vec![
252                ProxyQualityTier::Draft,
253                ProxyQualityTier::Review,
254                ProxyQualityTier::Delivery
255            ]
256        );
257    }
258
259    #[test]
260    fn test_tier_label() {
261        assert_eq!(ProxyQualityTier::Draft.label(), "Draft");
262        assert_eq!(ProxyQualityTier::Review.label(), "Review");
263        assert_eq!(ProxyQualityTier::Delivery.label(), "Delivery");
264    }
265}