1use crate::task::{Task, TaskPriority, TaskType};
4use crate::workflow::Workflow;
5use std::collections::HashMap;
6use std::path::PathBuf;
7use std::time::Duration;
8
9#[allow(dead_code)]
13#[must_use]
14pub fn watch_folder_transcode(
15 input_folder: PathBuf,
16 output_folder: PathBuf,
17 preset: String,
18) -> Workflow {
19 let mut workflow = Workflow::new("watch-folder-transcode")
20 .with_description("Auto-transcode files from watch folder");
21
22 let transcode_task = Task::new(
24 "transcode",
25 TaskType::Transcode {
26 input: input_folder.join("input.mp4"),
27 output: output_folder.join("output.mp4"),
28 preset,
29 params: HashMap::new(),
30 },
31 )
32 .with_priority(TaskPriority::High);
33
34 workflow.add_task(transcode_task);
35 workflow
36}
37
38#[allow(dead_code)]
42#[must_use]
43pub fn multi_pass_encoding(
44 source: PathBuf,
45 proxy_output: PathBuf,
46 final_output: PathBuf,
47 qc_profile: String,
48) -> Workflow {
49 let mut workflow = Workflow::new("multi-pass-encoding")
50 .with_description("Multi-pass encoding with QC validation");
51
52 let proxy_task = Task::new(
54 "create-proxy",
55 TaskType::Transcode {
56 input: source.clone(),
57 output: proxy_output.clone(),
58 preset: "proxy-lowres".to_string(),
59 params: HashMap::new(),
60 },
61 )
62 .with_priority(TaskPriority::High);
63
64 let proxy_id = workflow.add_task(proxy_task);
65
66 let qc_task = Task::new(
68 "qc-validation",
69 TaskType::QualityControl {
70 input: proxy_output.clone(),
71 profile: qc_profile,
72 rules: vec!["video_quality".to_string(), "audio_levels".to_string()],
73 },
74 );
75
76 let qc_id = workflow.add_task(qc_task);
77 workflow
78 .add_edge(proxy_id, qc_id)
79 .expect("invariant: task IDs are valid");
80
81 let final_task = Task::new(
83 "final-encode",
84 TaskType::Transcode {
85 input: source,
86 output: final_output,
87 preset: "high-quality".to_string(),
88 params: HashMap::new(),
89 },
90 )
91 .with_priority(TaskPriority::Critical)
92 .with_timeout(Duration::from_secs(7200)); let final_id = workflow.add_task(final_task);
95 workflow
96 .add_edge(qc_id, final_id)
97 .expect("invariant: task IDs are valid");
98
99 workflow
100}
101
102#[allow(dead_code)]
106#[must_use]
107pub fn validation_pipeline(
108 input_file: PathBuf,
109 archive_destination: String,
110 notification_email: Vec<String>,
111) -> Workflow {
112 let mut workflow = Workflow::new("validation-pipeline")
113 .with_description("Complete validation and archival pipeline");
114
115 let validation_task = Task::new(
117 "format-validation",
118 TaskType::Analysis {
119 input: input_file.clone(),
120 analyses: vec![
121 crate::task::AnalysisType::VideoQuality,
122 crate::task::AnalysisType::AudioLevels,
123 ],
124 output: None,
125 },
126 );
127
128 let validation_id = workflow.add_task(validation_task);
129
130 let qc_task = Task::new(
132 "quality-check",
133 TaskType::QualityControl {
134 input: input_file.clone(),
135 profile: "broadcast".to_string(),
136 rules: vec![
137 "video_bitrate".to_string(),
138 "audio_channels".to_string(),
139 "format_compliance".to_string(),
140 ],
141 },
142 );
143
144 let qc_id = workflow.add_task(qc_task);
145 workflow
146 .add_edge(validation_id, qc_id)
147 .expect("invariant: task IDs are valid");
148
149 let archive_task = Task::new(
151 "archive",
152 TaskType::Transfer {
153 source: input_file.to_string_lossy().to_string(),
154 destination: archive_destination,
155 protocol: crate::task::TransferProtocol::S3,
156 options: HashMap::new(),
157 },
158 )
159 .with_timeout(Duration::from_secs(3600));
160
161 let archive_id = workflow.add_task(archive_task);
162 workflow
163 .add_edge(qc_id, archive_id)
164 .expect("invariant: task IDs are valid");
165
166 let notify_task = Task::new(
168 "notify-completion",
169 TaskType::Notification {
170 channel: crate::task::NotificationChannel::Email {
171 to: notification_email,
172 subject: "Validation Pipeline Completed".to_string(),
173 },
174 message: "File successfully validated and archived".to_string(),
175 metadata: HashMap::new(),
176 },
177 );
178
179 let notify_id = workflow.add_task(notify_task);
180 workflow
181 .add_edge(archive_id, notify_id)
182 .expect("invariant: task IDs are valid");
183
184 workflow
185}
186
187#[allow(dead_code)]
191#[must_use]
192pub fn distribution_workflow(
193 source: PathBuf,
194 output_dir: PathBuf,
195 upload_destinations: Vec<String>,
196) -> Workflow {
197 let mut workflow = Workflow::new("distribution-workflow")
198 .with_description("Multi-format distribution pipeline");
199
200 let source_task = Task::new(
201 "source-analysis",
202 TaskType::Analysis {
203 input: source.clone(),
204 analyses: vec![crate::task::AnalysisType::VideoQuality],
205 output: None,
206 },
207 );
208
209 let source_id = workflow.add_task(source_task);
210
211 let formats = vec![
213 ("web-hd", "1080p-web"),
214 ("web-sd", "720p-web"),
215 ("mobile", "480p-mobile"),
216 ("broadcast", "broadcast-hd"),
217 ];
218
219 let mut encode_ids = Vec::new();
220
221 for (name, preset) in formats {
222 let encode_task = Task::new(
223 format!("encode-{name}"),
224 TaskType::Transcode {
225 input: source.clone(),
226 output: output_dir.join(format!("{name}.mp4")),
227 preset: preset.to_string(),
228 params: HashMap::new(),
229 },
230 )
231 .with_priority(TaskPriority::High);
232
233 let encode_id = workflow.add_task(encode_task);
234 workflow
235 .add_edge(source_id, encode_id)
236 .expect("invariant: task IDs are valid");
237 encode_ids.push(encode_id);
238 }
239
240 for (idx, destination) in upload_destinations.iter().enumerate() {
242 let upload_task = Task::new(
243 format!("upload-{idx}"),
244 TaskType::Transfer {
245 source: output_dir.to_string_lossy().to_string(),
246 destination: destination.clone(),
247 protocol: crate::task::TransferProtocol::S3,
248 options: HashMap::new(),
249 },
250 );
251
252 let upload_id = workflow.add_task(upload_task);
253
254 for &encode_id in &encode_ids {
256 workflow
257 .add_edge(encode_id, upload_id)
258 .expect("invariant: task IDs are valid");
259 }
260 }
261
262 workflow
263}
264
265#[allow(dead_code)]
269#[must_use]
270pub fn archive_workflow(
271 source: PathBuf,
272 proxy_dir: PathBuf,
273 archive_destination: String,
274) -> Workflow {
275 let mut workflow = Workflow::new("archive-workflow")
276 .with_description("Complete archival workflow with proxies");
277
278 let ingest_task = Task::new(
280 "ingest-validate",
281 TaskType::Analysis {
282 input: source.clone(),
283 analyses: vec![
284 crate::task::AnalysisType::VideoQuality,
285 crate::task::AnalysisType::AudioLevels,
286 crate::task::AnalysisType::Color,
287 ],
288 output: Some(proxy_dir.join("analysis.json")),
289 },
290 );
291
292 let ingest_id = workflow.add_task(ingest_task);
293
294 let proxy_formats = vec![
296 ("proxy-high", "proxy-1080p"),
297 ("proxy-medium", "proxy-720p"),
298 ("proxy-low", "proxy-480p"),
299 ("thumbnail", "thumbnail-grid"),
300 ];
301
302 let mut proxy_ids = Vec::new();
303
304 for (name, preset) in proxy_formats {
305 let proxy_task = Task::new(
306 format!("create-{name}"),
307 TaskType::Transcode {
308 input: source.clone(),
309 output: proxy_dir.join(format!("{name}.mp4")),
310 preset: preset.to_string(),
311 params: HashMap::new(),
312 },
313 );
314
315 let proxy_id = workflow.add_task(proxy_task);
316 workflow
317 .add_edge(ingest_id, proxy_id)
318 .expect("invariant: task IDs are valid");
319 proxy_ids.push(proxy_id);
320 }
321
322 let archive_task = Task::new(
324 "deep-archive",
325 TaskType::Transfer {
326 source: source.to_string_lossy().to_string(),
327 destination: archive_destination,
328 protocol: crate::task::TransferProtocol::S3,
329 options: {
330 let mut opts = HashMap::new();
331 opts.insert("storage_class".to_string(), "GLACIER".to_string());
332 opts
333 },
334 },
335 )
336 .with_timeout(Duration::from_secs(7200));
337
338 let archive_id = workflow.add_task(archive_task);
339
340 for proxy_id in proxy_ids {
342 workflow
343 .add_edge(proxy_id, archive_id)
344 .expect("invariant: task IDs are valid");
345 }
346
347 workflow
348}
349
350#[allow(dead_code)]
354#[must_use]
355pub fn parallel_processing(inputs: Vec<PathBuf>, output_dir: PathBuf, preset: String) -> Workflow {
356 let mut workflow =
357 Workflow::new("parallel-processing").with_description("Process multiple files in parallel");
358
359 for (idx, input) in inputs.iter().enumerate() {
360 let task = Task::new(
361 format!("process-{idx}"),
362 TaskType::Transcode {
363 input: input.clone(),
364 output: output_dir.join(format!("output_{idx}.mp4")),
365 preset: preset.clone(),
366 params: HashMap::new(),
367 },
368 )
369 .with_priority(TaskPriority::Normal);
370
371 workflow.add_task(task);
372 }
373
374 workflow
375}
376
377#[allow(dead_code)]
381#[must_use]
382pub fn sequential_processing(input: PathBuf, output_dir: PathBuf, stages: Vec<String>) -> Workflow {
383 let mut workflow = Workflow::new("sequential-processing")
384 .with_description("Sequential multi-stage processing");
385
386 let mut previous_id = None;
387 let mut current_input = input;
388
389 for (idx, stage) in stages.iter().enumerate() {
390 let output = output_dir.join(format!("stage_{idx}.mp4"));
391
392 let task = Task::new(
393 format!("stage-{idx}"),
394 TaskType::Transcode {
395 input: current_input.clone(),
396 output: output.clone(),
397 preset: stage.clone(),
398 params: HashMap::new(),
399 },
400 );
401
402 let task_id = workflow.add_task(task);
403
404 if let Some(prev_id) = previous_id {
405 workflow
406 .add_edge(prev_id, task_id)
407 .expect("invariant: task IDs are valid");
408 }
409
410 previous_id = Some(task_id);
411 current_input = output;
412 }
413
414 workflow
415}
416
417#[allow(dead_code)]
421#[must_use]
422pub fn conditional_workflow(input: PathBuf, output_hq: PathBuf, output_lq: PathBuf) -> Workflow {
423 let mut workflow = Workflow::new("conditional-workflow")
424 .with_description("Conditional processing based on input properties");
425
426 let analysis_task = Task::new(
428 "analyze-input",
429 TaskType::Analysis {
430 input: input.clone(),
431 analyses: vec![crate::task::AnalysisType::VideoQuality],
432 output: None,
433 },
434 );
435
436 let analysis_id = workflow.add_task(analysis_task);
437
438 let hq_task = Task::new(
440 "high-quality-encode",
441 TaskType::Transcode {
442 input: input.clone(),
443 output: output_hq,
444 preset: "ultra-hq".to_string(),
445 params: HashMap::new(),
446 },
447 )
448 .with_condition("input.bitrate > 10000000"); let hq_id = workflow.add_task(hq_task);
451 workflow
452 .add_edge(analysis_id, hq_id)
453 .expect("invariant: task IDs are valid");
454
455 let lq_task = Task::new(
457 "standard-quality-encode",
458 TaskType::Transcode {
459 input,
460 output: output_lq,
461 preset: "standard".to_string(),
462 params: HashMap::new(),
463 },
464 )
465 .with_condition("input.bitrate <= 10000000");
466
467 let lq_id = workflow.add_task(lq_task);
468 workflow
469 .add_edge(analysis_id, lq_id)
470 .expect("invariant: task IDs are valid");
471
472 workflow
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478
479 #[test]
480 fn test_watch_folder_transcode() {
481 let workflow = watch_folder_transcode(
482 PathBuf::from("/input"),
483 PathBuf::from("/output"),
484 "h264".to_string(),
485 );
486
487 assert_eq!(workflow.name, "watch-folder-transcode");
488 assert_eq!(workflow.tasks.len(), 1);
489 }
490
491 #[test]
492 fn test_multi_pass_encoding() {
493 let workflow = multi_pass_encoding(
494 PathBuf::from("/source.mp4"),
495 PathBuf::from("/proxy.mp4"),
496 PathBuf::from("/final.mp4"),
497 "broadcast".to_string(),
498 );
499
500 assert_eq!(workflow.name, "multi-pass-encoding");
501 assert_eq!(workflow.tasks.len(), 3);
502 assert_eq!(workflow.edges.len(), 2);
503 }
504
505 #[test]
506 fn test_validation_pipeline() {
507 let workflow = validation_pipeline(
508 PathBuf::from("/input.mp4"),
509 "s3://bucket/archive".to_string(),
510 vec!["admin@example.com".to_string()],
511 );
512
513 assert_eq!(workflow.name, "validation-pipeline");
514 assert_eq!(workflow.tasks.len(), 4);
515 assert!(workflow.validate().is_ok());
516 }
517
518 #[test]
519 fn test_distribution_workflow() {
520 let workflow = distribution_workflow(
521 PathBuf::from("/source.mp4"),
522 PathBuf::from("/output"),
523 vec!["s3://cdn1".to_string(), "s3://cdn2".to_string()],
524 );
525
526 assert_eq!(workflow.name, "distribution-workflow");
527 assert!(workflow.tasks.len() >= 5); }
529
530 #[test]
531 fn test_archive_workflow() {
532 let workflow = archive_workflow(
533 PathBuf::from("/source.mp4"),
534 PathBuf::from("/proxies"),
535 "s3://archive".to_string(),
536 );
537
538 assert_eq!(workflow.name, "archive-workflow");
539 assert!(workflow.tasks.len() >= 5); }
541
542 #[test]
543 fn test_parallel_processing() {
544 let inputs = vec![
545 PathBuf::from("/input1.mp4"),
546 PathBuf::from("/input2.mp4"),
547 PathBuf::from("/input3.mp4"),
548 ];
549
550 let workflow = parallel_processing(inputs, PathBuf::from("/output"), "h264".to_string());
551
552 assert_eq!(workflow.tasks.len(), 3);
553 assert_eq!(workflow.edges.len(), 0); }
555
556 #[test]
557 fn test_sequential_processing() {
558 let stages = vec![
559 "denoise".to_string(),
560 "color-correct".to_string(),
561 "stabilize".to_string(),
562 ];
563
564 let workflow = sequential_processing(
565 PathBuf::from("/input.mp4"),
566 PathBuf::from("/output"),
567 stages,
568 );
569
570 assert_eq!(workflow.tasks.len(), 3);
571 assert_eq!(workflow.edges.len(), 2); }
573
574 #[test]
575 fn test_conditional_workflow() {
576 let workflow = conditional_workflow(
577 PathBuf::from("/input.mp4"),
578 PathBuf::from("/output_hq.mp4"),
579 PathBuf::from("/output_lq.mp4"),
580 );
581
582 assert_eq!(workflow.tasks.len(), 3); assert!(workflow.validate().is_ok());
584 }
585}