oximedia_proxy/generate/
batch.rs1use super::{encoder::ProxyEncodeResult, settings::ProxyGenerationSettings, ProxyEncoder};
4use crate::Result;
5use rayon::prelude::*;
6use std::path::{Path, PathBuf};
7
8pub struct BatchProxyGenerator {
10 settings: ProxyGenerationSettings,
11 max_parallel: usize,
12}
13
14impl BatchProxyGenerator {
15 pub fn new(settings: ProxyGenerationSettings) -> Self {
17 Self {
18 settings,
19 max_parallel: num_cpus::get(),
20 }
21 }
22
23 #[must_use]
25 pub fn with_max_parallel(mut self, max_parallel: usize) -> Self {
26 self.max_parallel = max_parallel.max(1);
27 self
28 }
29
30 pub async fn generate_batch(&self, inputs: &[(PathBuf, PathBuf)]) -> Result<Vec<BatchResult>> {
36 tracing::info!("Starting batch generation for {} files", inputs.len());
37
38 let settings = self.settings.clone();
40 let results: Vec<BatchResult> = inputs
41 .par_iter()
42 .map(|(input, output)| {
43 let encoder = ProxyEncoder::new(settings.clone())?;
44 let result =
45 tokio::runtime::Handle::current().block_on(encoder.encode(input, output));
46
47 match result {
48 Ok(encode_result) => Ok(BatchResult::Success {
49 input: input.clone(),
50 output: output.clone(),
51 result: encode_result,
52 }),
53 Err(e) => Ok(BatchResult::Failed {
54 input: input.clone(),
55 output: output.clone(),
56 error: e.to_string(),
57 }),
58 }
59 })
60 .collect::<Result<Vec<_>>>()?;
61
62 let success_count = results
63 .iter()
64 .filter(|r| matches!(r, BatchResult::Success { .. }))
65 .count();
66 let failed_count = results.len() - success_count;
67
68 tracing::info!(
69 "Batch generation complete: {} succeeded, {} failed",
70 success_count,
71 failed_count
72 );
73
74 Ok(results)
75 }
76
77 pub async fn generate_batch_with_progress<F>(
79 &self,
80 inputs: &[(PathBuf, PathBuf)],
81 mut progress_callback: F,
82 ) -> Result<Vec<BatchResult>>
83 where
84 F: FnMut(usize, usize) + Send,
85 {
86 let total = inputs.len();
87 let mut completed = 0;
88
89 let settings = self.settings.clone();
90 let mut results = Vec::with_capacity(total);
91
92 for (input, output) in inputs {
93 let encoder = ProxyEncoder::new(settings.clone())?;
94 let result = encoder.encode(input, output).await;
95
96 let batch_result = match result {
97 Ok(encode_result) => BatchResult::Success {
98 input: input.clone(),
99 output: output.clone(),
100 result: encode_result,
101 },
102 Err(e) => BatchResult::Failed {
103 input: input.clone(),
104 output: output.clone(),
105 error: e.to_string(),
106 },
107 };
108
109 results.push(batch_result);
110 completed += 1;
111 progress_callback(completed, total);
112 }
113
114 Ok(results)
115 }
116}
117
118#[derive(Debug, Clone)]
120pub enum BatchResult {
121 Success {
123 input: PathBuf,
125 output: PathBuf,
127 result: ProxyEncodeResult,
129 },
130 Failed {
132 input: PathBuf,
134 output: PathBuf,
136 error: String,
138 },
139}
140
141impl BatchResult {
142 #[must_use]
144 pub const fn is_success(&self) -> bool {
145 matches!(self, Self::Success { .. })
146 }
147
148 #[must_use]
150 pub const fn is_failed(&self) -> bool {
151 matches!(self, Self::Failed { .. })
152 }
153
154 #[must_use]
156 pub fn input(&self) -> &Path {
157 match self {
158 Self::Success { input, .. } | Self::Failed { input, .. } => input,
159 }
160 }
161
162 #[must_use]
164 pub fn output(&self) -> &Path {
165 match self {
166 Self::Success { output, .. } | Self::Failed { output, .. } => output,
167 }
168 }
169}
170
171#[allow(dead_code)]
173fn num_cpus() -> usize {
174 std::thread::available_parallelism()
175 .map(|n| n.get())
176 .unwrap_or(1)
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn test_batch_generator_creation() {
185 let settings = ProxyGenerationSettings::quarter_res_h264();
186 let generator = BatchProxyGenerator::new(settings);
187 assert!(generator.max_parallel > 0);
188 }
189
190 #[test]
191 fn test_max_parallel() {
192 let settings = ProxyGenerationSettings::quarter_res_h264();
193 let generator = BatchProxyGenerator::new(settings).with_max_parallel(4);
194 assert_eq!(generator.max_parallel, 4);
195 }
196
197 #[test]
198 fn test_batch_result() {
199 let result = BatchResult::Failed {
200 input: PathBuf::from("input.mov"),
201 output: PathBuf::from("output.mp4"),
202 error: "test error".to_string(),
203 };
204
205 assert!(result.is_failed());
206 assert!(!result.is_success());
207 assert_eq!(result.input(), Path::new("input.mov"));
208 assert_eq!(result.output(), Path::new("output.mp4"));
209 }
210}