1#![allow(dead_code)]
7#![allow(missing_docs)]
8
9#[derive(Debug, Clone, PartialEq)]
15pub struct ProxyProfile {
16 pub name: String,
18 pub width: u32,
20 pub height: u32,
22 pub bitrate_kbps: u32,
24 pub codec: String,
26 pub frame_rate: f32,
28}
29
30impl ProxyProfile {
31 #[must_use]
33 pub fn new(
34 name: impl Into<String>,
35 width: u32,
36 height: u32,
37 bitrate_kbps: u32,
38 codec: impl Into<String>,
39 frame_rate: f32,
40 ) -> Self {
41 Self {
42 name: name.into(),
43 width,
44 height,
45 bitrate_kbps,
46 codec: codec.into(),
47 frame_rate,
48 }
49 }
50
51 #[must_use]
53 pub fn offline_edit() -> Self {
54 Self::new("offline_edit", 1920, 1080, 8_000, "h264", 25.0)
55 }
56
57 #[must_use]
59 pub fn web_preview() -> Self {
60 Self::new("web_preview", 1280, 720, 2_000, "h264", 25.0)
61 }
62
63 #[must_use]
65 pub fn mobile() -> Self {
66 Self::new("mobile", 854, 480, 800, "h264", 25.0)
67 }
68}
69
70#[derive(Debug, Clone, PartialEq)]
76pub enum ProxyStatus {
77 Queued,
79 Processing(u8),
81 Done,
83 Failed(String),
85}
86
87impl ProxyStatus {
88 #[must_use]
90 pub fn is_complete(&self) -> bool {
91 matches!(self, Self::Done | Self::Failed(_))
92 }
93
94 #[must_use]
101 pub fn progress_pct(&self) -> u8 {
102 match self {
103 Self::Queued => 0,
104 Self::Processing(p) => *p,
105 Self::Done => 100,
106 Self::Failed(_) => 0,
107 }
108 }
109}
110
111#[derive(Debug, Clone)]
117pub struct ProxyTask {
118 pub source_path: String,
120 pub output_path: String,
122 pub profile: ProxyProfile,
124 pub status: ProxyStatus,
126}
127
128impl ProxyTask {
129 #[must_use]
131 pub fn new(
132 source_path: impl Into<String>,
133 output_path: impl Into<String>,
134 profile: ProxyProfile,
135 ) -> Self {
136 Self {
137 source_path: source_path.into(),
138 output_path: output_path.into(),
139 profile,
140 status: ProxyStatus::Queued,
141 }
142 }
143}
144
145#[derive(Debug, Default)]
151pub struct ProxyGenerator {
152 pub tasks: Vec<ProxyTask>,
154}
155
156impl ProxyGenerator {
157 #[must_use]
159 pub fn new() -> Self {
160 Self::default()
161 }
162
163 pub fn queue(&mut self, source: &str, output: &str, profile: ProxyProfile) {
165 self.tasks.push(ProxyTask::new(source, output, profile));
166 }
167
168 #[must_use]
170 pub fn pending_count(&self) -> usize {
171 self.tasks
172 .iter()
173 .filter(|t| !t.status.is_complete())
174 .count()
175 }
176
177 #[must_use]
179 pub fn complete_count(&self) -> usize {
180 self.tasks
181 .iter()
182 .filter(|t| matches!(t.status, ProxyStatus::Done))
183 .count()
184 }
185
186 #[must_use]
188 pub fn failed_tasks(&self) -> Vec<&ProxyTask> {
189 self.tasks
190 .iter()
191 .filter(|t| matches!(t.status, ProxyStatus::Failed(_)))
192 .collect()
193 }
194}
195
196#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn test_proxy_profile_offline_edit() {
206 let p = ProxyProfile::offline_edit();
207 assert_eq!(p.width, 1920);
208 assert_eq!(p.height, 1080);
209 assert_eq!(p.codec, "h264");
210 }
211
212 #[test]
213 fn test_proxy_profile_web_preview() {
214 let p = ProxyProfile::web_preview();
215 assert_eq!(p.width, 1280);
216 assert_eq!(p.height, 720);
217 }
218
219 #[test]
220 fn test_proxy_profile_mobile() {
221 let p = ProxyProfile::mobile();
222 assert_eq!(p.width, 854);
223 assert_eq!(p.height, 480);
224 assert!(p.bitrate_kbps < 1_000);
225 }
226
227 #[test]
228 fn test_proxy_status_is_complete_queued() {
229 assert!(!ProxyStatus::Queued.is_complete());
230 }
231
232 #[test]
233 fn test_proxy_status_is_complete_processing() {
234 assert!(!ProxyStatus::Processing(50).is_complete());
235 }
236
237 #[test]
238 fn test_proxy_status_is_complete_done() {
239 assert!(ProxyStatus::Done.is_complete());
240 }
241
242 #[test]
243 fn test_proxy_status_is_complete_failed() {
244 assert!(ProxyStatus::Failed("err".to_string()).is_complete());
245 }
246
247 #[test]
248 fn test_proxy_status_progress_queued() {
249 assert_eq!(ProxyStatus::Queued.progress_pct(), 0);
250 }
251
252 #[test]
253 fn test_proxy_status_progress_processing() {
254 assert_eq!(ProxyStatus::Processing(75).progress_pct(), 75);
255 }
256
257 #[test]
258 fn test_proxy_status_progress_done() {
259 assert_eq!(ProxyStatus::Done.progress_pct(), 100);
260 }
261
262 #[test]
263 fn test_proxy_generator_queue_pending() {
264 let mut gen = ProxyGenerator::new();
265 gen.queue("src.mov", "out.mp4", ProxyProfile::offline_edit());
266 gen.queue("src2.mov", "out2.mp4", ProxyProfile::mobile());
267 assert_eq!(gen.pending_count(), 2);
268 assert_eq!(gen.complete_count(), 0);
269 }
270
271 #[test]
272 fn test_proxy_generator_complete_count() {
273 let mut gen = ProxyGenerator::new();
274 gen.queue("src.mov", "out.mp4", ProxyProfile::offline_edit());
275 gen.tasks[0].status = ProxyStatus::Done;
276 assert_eq!(gen.complete_count(), 1);
277 assert_eq!(gen.pending_count(), 0);
278 }
279
280 #[test]
281 fn test_proxy_generator_failed_tasks() {
282 let mut gen = ProxyGenerator::new();
283 gen.queue("a.mov", "a.mp4", ProxyProfile::mobile());
284 gen.queue("b.mov", "b.mp4", ProxyProfile::mobile());
285 gen.tasks[0].status = ProxyStatus::Failed("codec error".to_string());
286 let failed = gen.failed_tasks();
287 assert_eq!(failed.len(), 1);
288 assert_eq!(failed[0].source_path, "a.mov");
289 }
290}