oximedia_transcode/
builder.rs1use crate::{
4 AbrLadder, MultiPassMode, NormalizationConfig, PresetConfig, QualityConfig, QualityMode,
5 RateControlMode, Result, TranscodeConfig, TranscodeError,
6};
7
8pub struct TranscodeBuilder {
10 config: TranscodeConfig,
11}
12
13impl TranscodeBuilder {
14 #[must_use]
16 pub fn new() -> Self {
17 Self {
18 config: TranscodeConfig::default(),
19 }
20 }
21
22 #[must_use]
24 pub fn input(mut self, path: impl Into<String>) -> Self {
25 self.config.input = Some(path.into());
26 self
27 }
28
29 #[must_use]
31 pub fn output(mut self, path: impl Into<String>) -> Self {
32 self.config.output = Some(path.into());
33 self
34 }
35
36 #[must_use]
38 pub fn video_codec(mut self, codec: impl Into<String>) -> Self {
39 self.config.video_codec = Some(codec.into());
40 self
41 }
42
43 #[must_use]
45 pub fn audio_codec(mut self, codec: impl Into<String>) -> Self {
46 self.config.audio_codec = Some(codec.into());
47 self
48 }
49
50 #[must_use]
52 pub fn video_bitrate(mut self, bitrate: u64) -> Self {
53 self.config.video_bitrate = Some(bitrate);
54 self
55 }
56
57 #[must_use]
59 pub fn audio_bitrate(mut self, bitrate: u64) -> Self {
60 self.config.audio_bitrate = Some(bitrate);
61 self
62 }
63
64 #[must_use]
66 pub fn resolution(mut self, width: u32, height: u32) -> Self {
67 self.config.width = Some(width);
68 self.config.height = Some(height);
69 self
70 }
71
72 #[must_use]
74 pub fn frame_rate(mut self, num: u32, den: u32) -> Self {
75 self.config.frame_rate = Some((num, den));
76 self
77 }
78
79 #[must_use]
81 pub fn multi_pass(mut self, mode: MultiPassMode) -> Self {
82 self.config.multi_pass = Some(mode);
83 self
84 }
85
86 #[must_use]
88 pub fn quality(mut self, mode: QualityMode) -> Self {
89 self.config.quality_mode = Some(mode);
90 self
91 }
92
93 #[must_use]
95 pub fn normalize_audio(mut self) -> Self {
96 self.config.normalize_audio = true;
97 self
98 }
99
100 #[must_use]
102 pub fn loudness_standard(mut self, standard: crate::LoudnessStandard) -> Self {
103 self.config.loudness_standard = Some(standard);
104 self.config.normalize_audio = true;
105 self
106 }
107
108 #[must_use]
110 pub fn hw_accel(mut self, enable: bool) -> Self {
111 self.config.hw_accel = enable;
112 self
113 }
114
115 #[must_use]
117 pub fn preserve_metadata(mut self, enable: bool) -> Self {
118 self.config.preserve_metadata = enable;
119 self
120 }
121
122 #[must_use]
124 pub fn subtitles(mut self, mode: crate::SubtitleMode) -> Self {
125 self.config.subtitle_mode = Some(mode);
126 self
127 }
128
129 #[must_use]
131 pub fn chapters(mut self, mode: crate::ChapterMode) -> Self {
132 self.config.chapter_mode = Some(mode);
133 self
134 }
135
136 #[must_use]
138 pub fn preset(mut self, preset: PresetConfig) -> Self {
139 if let Some(codec) = preset.video_codec {
140 self.config.video_codec = Some(codec);
141 }
142 if let Some(codec) = preset.audio_codec {
143 self.config.audio_codec = Some(codec);
144 }
145 if let Some(bitrate) = preset.video_bitrate {
146 self.config.video_bitrate = Some(bitrate);
147 }
148 if let Some(bitrate) = preset.audio_bitrate {
149 self.config.audio_bitrate = Some(bitrate);
150 }
151 if let Some(width) = preset.width {
152 self.config.width = Some(width);
153 }
154 if let Some(height) = preset.height {
155 self.config.height = Some(height);
156 }
157 if let Some(fps) = preset.frame_rate {
158 self.config.frame_rate = Some(fps);
159 }
160 if let Some(mode) = preset.quality_mode {
161 self.config.quality_mode = Some(mode);
162 }
163 self
164 }
165
166 pub fn build(self) -> Result<TranscodeConfig> {
172 if self.config.input.is_none() {
174 return Err(TranscodeError::InvalidInput(
175 "Input path is required".to_string(),
176 ));
177 }
178
179 if self.config.output.is_none() {
180 return Err(TranscodeError::InvalidOutput(
181 "Output path is required".to_string(),
182 ));
183 }
184
185 Ok(self.config)
186 }
187
188 pub fn validate(self) -> Result<TranscodeConfig> {
194 let config = self.build()?;
195
196 use crate::validation::{InputValidator, OutputValidator};
198
199 if let Some(ref input) = config.input {
200 InputValidator::validate_path(input)?;
201 }
202
203 if let Some(ref output) = config.output {
204 OutputValidator::validate_path(output, true)?;
205 }
206
207 if let Some(ref codec) = config.video_codec {
208 OutputValidator::validate_codec(codec)?;
209 }
210
211 if let Some(ref codec) = config.audio_codec {
212 OutputValidator::validate_codec(codec)?;
213 }
214
215 if let (Some(width), Some(height)) = (config.width, config.height) {
216 OutputValidator::validate_resolution(width, height)?;
217 }
218
219 if let Some((num, den)) = config.frame_rate {
220 OutputValidator::validate_frame_rate(num, den)?;
221 }
222
223 Ok(config)
224 }
225}
226
227impl Default for TranscodeBuilder {
228 fn default() -> Self {
229 Self::new()
230 }
231}
232
233pub struct AdvancedTranscodeBuilder {
235 #[allow(dead_code)]
236 builder: TranscodeBuilder,
237 quality_config: Option<QualityConfig>,
238 #[allow(dead_code)]
239 normalization_config: Option<NormalizationConfig>,
240 #[allow(dead_code)]
241 abr_ladder: Option<AbrLadder>,
242}
243
244#[allow(dead_code)]
245impl AdvancedTranscodeBuilder {
246 #[must_use]
248 pub fn new() -> Self {
249 Self {
250 builder: TranscodeBuilder::new(),
251 quality_config: None,
252 normalization_config: None,
253 abr_ladder: None,
254 }
255 }
256
257 #[must_use]
259 pub fn input(mut self, path: impl Into<String>) -> Self {
260 self.builder = self.builder.input(path);
261 self
262 }
263
264 #[must_use]
266 pub fn output(mut self, path: impl Into<String>) -> Self {
267 self.builder = self.builder.output(path);
268 self
269 }
270
271 #[must_use]
273 #[allow(dead_code)]
274 pub fn quality_config(mut self, config: QualityConfig) -> Self {
275 self.quality_config = Some(config);
276 self
277 }
278
279 #[allow(dead_code)]
281 #[must_use]
282 pub fn normalization_config(mut self, config: NormalizationConfig) -> Self {
283 self.normalization_config = Some(config);
284 self
285 }
286
287 #[allow(dead_code)]
289 #[must_use]
290 pub fn abr_ladder(mut self, ladder: AbrLadder) -> Self {
291 self.abr_ladder = Some(ladder);
292 self
293 }
294
295 #[must_use]
297 pub fn crf(mut self, value: u8) -> Self {
298 if let Some(ref mut config) = self.quality_config {
299 config.rate_control = RateControlMode::Crf(value);
300 } else {
301 let mut config = QualityConfig::default();
302 config.rate_control = RateControlMode::Crf(value);
303 self.quality_config = Some(config);
304 }
305 self
306 }
307
308 #[must_use]
310 pub fn cbr(mut self, bitrate: u64) -> Self {
311 if let Some(ref mut config) = self.quality_config {
312 config.rate_control = RateControlMode::Cbr(bitrate);
313 } else {
314 let mut config = QualityConfig::default();
315 config.rate_control = RateControlMode::Cbr(bitrate);
316 self.quality_config = Some(config);
317 }
318 self.builder = self.builder.video_bitrate(bitrate);
319 self
320 }
321
322 #[must_use]
324 pub fn vbr(mut self, target: u64, max: u64) -> Self {
325 if let Some(ref mut config) = self.quality_config {
326 config.rate_control = RateControlMode::Vbr { target, max };
327 } else {
328 let mut config = QualityConfig::default();
329 config.rate_control = RateControlMode::Vbr { target, max };
330 self.quality_config = Some(config);
331 }
332 self.builder = self.builder.video_bitrate(target);
333 self
334 }
335
336 pub fn build(self) -> Result<TranscodeConfig> {
342 self.builder.build()
343 }
344}
345
346impl Default for AdvancedTranscodeBuilder {
347 fn default() -> Self {
348 Self::new()
349 }
350}
351
352#[cfg(test)]
353mod tests {
354 use super::*;
355
356 #[test]
357 fn test_builder_basic() {
358 let config = TranscodeBuilder::new()
359 .input("/tmp/input.mp4")
360 .output("/tmp/output.mp4")
361 .video_codec("vp9")
362 .audio_codec("opus")
363 .build()
364 .expect("should succeed in test");
365
366 assert_eq!(config.input, Some("/tmp/input.mp4".to_string()));
367 assert_eq!(config.output, Some("/tmp/output.mp4".to_string()));
368 assert_eq!(config.video_codec, Some("vp9".to_string()));
369 assert_eq!(config.audio_codec, Some("opus".to_string()));
370 }
371
372 #[test]
373 fn test_builder_missing_input() {
374 let result = TranscodeBuilder::new().output("/tmp/output.mp4").build();
375
376 assert!(result.is_err());
377 }
378
379 #[test]
380 fn test_builder_missing_output() {
381 let result = TranscodeBuilder::new().input("/tmp/input.mp4").build();
382
383 assert!(result.is_err());
384 }
385
386 #[test]
387 fn test_builder_with_resolution() {
388 let config = TranscodeBuilder::new()
389 .input("/tmp/input.mp4")
390 .output("/tmp/output.mp4")
391 .resolution(1920, 1080)
392 .build()
393 .expect("should succeed in test");
394
395 assert_eq!(config.width, Some(1920));
396 assert_eq!(config.height, Some(1080));
397 }
398
399 #[test]
400 fn test_builder_with_quality() {
401 let config = TranscodeBuilder::new()
402 .input("/tmp/input.mp4")
403 .output("/tmp/output.mp4")
404 .quality(QualityMode::High)
405 .build()
406 .expect("should succeed in test");
407
408 assert_eq!(config.quality_mode, Some(QualityMode::High));
409 }
410
411 #[test]
412 fn test_builder_with_multipass() {
413 let config = TranscodeBuilder::new()
414 .input("/tmp/input.mp4")
415 .output("/tmp/output.mp4")
416 .multi_pass(MultiPassMode::TwoPass)
417 .build()
418 .expect("should succeed in test");
419
420 assert_eq!(config.multi_pass, Some(MultiPassMode::TwoPass));
421 }
422
423 #[test]
424 fn test_builder_with_normalization() {
425 let config = TranscodeBuilder::new()
426 .input("/tmp/input.mp4")
427 .output("/tmp/output.mp4")
428 .normalize_audio()
429 .build()
430 .expect("should succeed in test");
431
432 assert!(config.normalize_audio);
433 }
434
435 #[test]
436 fn test_advanced_builder_crf() {
437 let config = AdvancedTranscodeBuilder::new()
438 .input("/tmp/input.mp4")
439 .output("/tmp/output.mp4")
440 .crf(23)
441 .build()
442 .expect("should succeed in test");
443
444 assert_eq!(config.input, Some("/tmp/input.mp4".to_string()));
445 assert_eq!(config.output, Some("/tmp/output.mp4".to_string()));
446 }
447
448 #[test]
449 fn test_advanced_builder_cbr() {
450 let config = AdvancedTranscodeBuilder::new()
451 .input("/tmp/input.mp4")
452 .output("/tmp/output.mp4")
453 .cbr(5_000_000)
454 .build()
455 .expect("should succeed in test");
456
457 assert_eq!(config.video_bitrate, Some(5_000_000));
458 }
459
460 #[test]
461 fn test_advanced_builder_vbr() {
462 let config = AdvancedTranscodeBuilder::new()
463 .input("/tmp/input.mp4")
464 .output("/tmp/output.mp4")
465 .vbr(5_000_000, 8_000_000)
466 .build()
467 .expect("should succeed in test");
468
469 assert_eq!(config.video_bitrate, Some(5_000_000));
470 }
471}