1use std::fmt;
4
5#[derive(Debug, Clone)]
7pub struct VideoFilter {
8 filters: Vec<FilterNode>,
9}
10
11#[derive(Debug, Clone)]
13pub struct AudioFilter {
14 filters: Vec<FilterNode>,
15}
16
17#[derive(Debug, Clone)]
19pub struct FilterNode {
20 pub name: String,
22 pub params: Vec<(String, String)>,
24}
25
26impl VideoFilter {
27 #[must_use]
29 pub fn new() -> Self {
30 Self {
31 filters: Vec::new(),
32 }
33 }
34
35 #[must_use]
37 pub fn scale(mut self, width: u32, height: u32) -> Self {
38 self.filters.push(FilterNode {
39 name: "scale".to_string(),
40 params: vec![
41 ("width".to_string(), width.to_string()),
42 ("height".to_string(), height.to_string()),
43 ],
44 });
45 self
46 }
47
48 #[must_use]
50 pub fn deinterlace(mut self) -> Self {
51 self.filters.push(FilterNode {
52 name: "yadif".to_string(),
53 params: vec![("mode".to_string(), "1".to_string())],
54 });
55 self
56 }
57
58 #[must_use]
60 pub fn crop(mut self, width: u32, height: u32, x: u32, y: u32) -> Self {
61 self.filters.push(FilterNode {
62 name: "crop".to_string(),
63 params: vec![
64 ("width".to_string(), width.to_string()),
65 ("height".to_string(), height.to_string()),
66 ("x".to_string(), x.to_string()),
67 ("y".to_string(), y.to_string()),
68 ],
69 });
70 self
71 }
72
73 #[must_use]
75 pub fn pad(mut self, width: u32, height: u32, x: u32, y: u32, color: &str) -> Self {
76 self.filters.push(FilterNode {
77 name: "pad".to_string(),
78 params: vec![
79 ("width".to_string(), width.to_string()),
80 ("height".to_string(), height.to_string()),
81 ("x".to_string(), x.to_string()),
82 ("y".to_string(), y.to_string()),
83 ("color".to_string(), color.to_string()),
84 ],
85 });
86 self
87 }
88
89 #[must_use]
91 pub fn denoise(mut self, strength: f32) -> Self {
92 self.filters.push(FilterNode {
93 name: "hqdn3d".to_string(),
94 params: vec![("luma_spatial".to_string(), strength.to_string())],
95 });
96 self
97 }
98
99 #[must_use]
101 pub fn sharpen(mut self, amount: f32) -> Self {
102 self.filters.push(FilterNode {
103 name: "unsharp".to_string(),
104 params: vec![("luma_amount".to_string(), amount.to_string())],
105 });
106 self
107 }
108
109 #[must_use]
111 pub fn color_correct(mut self, brightness: f32, contrast: f32, saturation: f32) -> Self {
112 self.filters.push(FilterNode {
113 name: "eq".to_string(),
114 params: vec![
115 ("brightness".to_string(), brightness.to_string()),
116 ("contrast".to_string(), contrast.to_string()),
117 ("saturation".to_string(), saturation.to_string()),
118 ],
119 });
120 self
121 }
122
123 #[must_use]
125 pub fn framerate(mut self, fps: f64) -> Self {
126 self.filters.push(FilterNode {
127 name: "fps".to_string(),
128 params: vec![("fps".to_string(), fps.to_string())],
129 });
130 self
131 }
132
133 #[must_use]
135 pub fn rotate(mut self, degrees: f64) -> Self {
136 self.filters.push(FilterNode {
137 name: "rotate".to_string(),
138 params: vec![("angle".to_string(), degrees.to_string())],
139 });
140 self
141 }
142
143 #[must_use]
145 pub fn vflip(mut self) -> Self {
146 self.filters.push(FilterNode {
147 name: "vflip".to_string(),
148 params: Vec::new(),
149 });
150 self
151 }
152
153 #[must_use]
155 pub fn hflip(mut self) -> Self {
156 self.filters.push(FilterNode {
157 name: "hflip".to_string(),
158 params: Vec::new(),
159 });
160 self
161 }
162
163 #[must_use]
165 pub fn custom(mut self, name: impl Into<String>, params: Vec<(String, String)>) -> Self {
166 self.filters.push(FilterNode {
167 name: name.into(),
168 params,
169 });
170 self
171 }
172
173 #[must_use]
175 pub fn to_string(&self) -> String {
176 self.filters
177 .iter()
178 .map(std::string::ToString::to_string)
179 .collect::<Vec<_>>()
180 .join(",")
181 }
182
183 #[must_use]
185 pub fn len(&self) -> usize {
186 self.filters.len()
187 }
188
189 #[must_use]
191 pub fn is_empty(&self) -> bool {
192 self.filters.is_empty()
193 }
194}
195
196impl Default for VideoFilter {
197 fn default() -> Self {
198 Self::new()
199 }
200}
201
202impl AudioFilter {
203 #[must_use]
205 pub fn new() -> Self {
206 Self {
207 filters: Vec::new(),
208 }
209 }
210
211 #[must_use]
213 pub fn volume(mut self, volume: f32) -> Self {
214 self.filters.push(FilterNode {
215 name: "volume".to_string(),
216 params: vec![("volume".to_string(), volume.to_string())],
217 });
218 self
219 }
220
221 #[must_use]
223 pub fn resample(mut self, sample_rate: u32) -> Self {
224 self.filters.push(FilterNode {
225 name: "aresample".to_string(),
226 params: vec![("sample_rate".to_string(), sample_rate.to_string())],
227 });
228 self
229 }
230
231 #[must_use]
233 pub fn channel_layout(mut self, layout: &str) -> Self {
234 self.filters.push(FilterNode {
235 name: "aformat".to_string(),
236 params: vec![("channel_layouts".to_string(), layout.to_string())],
237 });
238 self
239 }
240
241 #[must_use]
243 pub fn normalize(mut self, target_lufs: f64) -> Self {
244 self.filters.push(FilterNode {
245 name: "loudnorm".to_string(),
246 params: vec![("I".to_string(), target_lufs.to_string())],
247 });
248 self
249 }
250
251 #[must_use]
253 pub fn lowpass(mut self, frequency: f64) -> Self {
254 self.filters.push(FilterNode {
255 name: "lowpass".to_string(),
256 params: vec![("frequency".to_string(), frequency.to_string())],
257 });
258 self
259 }
260
261 #[must_use]
263 pub fn highpass(mut self, frequency: f64) -> Self {
264 self.filters.push(FilterNode {
265 name: "highpass".to_string(),
266 params: vec![("frequency".to_string(), frequency.to_string())],
267 });
268 self
269 }
270
271 #[must_use]
273 pub fn equalizer(mut self, frequency: f64, width: f64, gain: f64) -> Self {
274 self.filters.push(FilterNode {
275 name: "equalizer".to_string(),
276 params: vec![
277 ("frequency".to_string(), frequency.to_string()),
278 ("width_type".to_string(), "h".to_string()),
279 ("width".to_string(), width.to_string()),
280 ("gain".to_string(), gain.to_string()),
281 ],
282 });
283 self
284 }
285
286 #[must_use]
288 pub fn compress(mut self, threshold: f64, ratio: f64) -> Self {
289 self.filters.push(FilterNode {
290 name: "acompressor".to_string(),
291 params: vec![
292 ("threshold".to_string(), threshold.to_string()),
293 ("ratio".to_string(), ratio.to_string()),
294 ],
295 });
296 self
297 }
298
299 #[must_use]
301 pub fn delay(mut self, milliseconds: u32) -> Self {
302 self.filters.push(FilterNode {
303 name: "adelay".to_string(),
304 params: vec![("delays".to_string(), format!("{milliseconds}ms"))],
305 });
306 self
307 }
308
309 #[must_use]
311 pub fn fade_in(mut self, duration: f64) -> Self {
312 self.filters.push(FilterNode {
313 name: "afade".to_string(),
314 params: vec![
315 ("type".to_string(), "in".to_string()),
316 ("duration".to_string(), duration.to_string()),
317 ],
318 });
319 self
320 }
321
322 #[must_use]
324 pub fn fade_out(mut self, start: f64, duration: f64) -> Self {
325 self.filters.push(FilterNode {
326 name: "afade".to_string(),
327 params: vec![
328 ("type".to_string(), "out".to_string()),
329 ("start_time".to_string(), start.to_string()),
330 ("duration".to_string(), duration.to_string()),
331 ],
332 });
333 self
334 }
335
336 #[must_use]
338 pub fn custom(mut self, name: impl Into<String>, params: Vec<(String, String)>) -> Self {
339 self.filters.push(FilterNode {
340 name: name.into(),
341 params,
342 });
343 self
344 }
345
346 #[must_use]
348 pub fn to_string(&self) -> String {
349 self.filters
350 .iter()
351 .map(std::string::ToString::to_string)
352 .collect::<Vec<_>>()
353 .join(",")
354 }
355
356 #[must_use]
358 pub fn len(&self) -> usize {
359 self.filters.len()
360 }
361
362 #[must_use]
364 pub fn is_empty(&self) -> bool {
365 self.filters.is_empty()
366 }
367}
368
369impl Default for AudioFilter {
370 fn default() -> Self {
371 Self::new()
372 }
373}
374
375impl FilterNode {
376 #[must_use]
378 pub fn new(name: impl Into<String>) -> Self {
379 Self {
380 name: name.into(),
381 params: Vec::new(),
382 }
383 }
384
385 #[must_use]
387 pub fn param(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
388 self.params.push((key.into(), value.into()));
389 self
390 }
391}
392
393impl fmt::Display for FilterNode {
394 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
395 if self.params.is_empty() {
396 write!(f, "{}", self.name)
397 } else {
398 let params = self
399 .params
400 .iter()
401 .map(|(k, v)| format!("{k}={v}"))
402 .collect::<Vec<_>>()
403 .join(":");
404 write!(f, "{}={}", self.name, params)
405 }
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412
413 #[test]
414 fn test_video_filter_scale() {
415 let filter = VideoFilter::new().scale(1920, 1080);
416 assert_eq!(filter.len(), 1);
417 assert!(!filter.is_empty());
418 }
419
420 #[test]
421 fn test_video_filter_chain() {
422 let filter = VideoFilter::new()
423 .scale(1920, 1080)
424 .deinterlace()
425 .denoise(3.0)
426 .sharpen(1.5);
427
428 assert_eq!(filter.len(), 4);
429 let filter_str = filter.to_string();
430 assert!(filter_str.contains("scale"));
431 assert!(filter_str.contains("yadif"));
432 }
433
434 #[test]
435 fn test_video_filter_crop() {
436 let filter = VideoFilter::new().crop(1920, 1080, 0, 0);
437 assert_eq!(filter.len(), 1);
438 }
439
440 #[test]
441 fn test_video_filter_color_correct() {
442 let filter = VideoFilter::new().color_correct(0.1, 1.2, 1.0);
443 assert_eq!(filter.len(), 1);
444 }
445
446 #[test]
447 fn test_video_filter_rotate() {
448 let filter = VideoFilter::new().rotate(90.0);
449 assert_eq!(filter.len(), 1);
450 }
451
452 #[test]
453 fn test_video_filter_flip() {
454 let filter = VideoFilter::new().vflip().hflip();
455 assert_eq!(filter.len(), 2);
456 }
457
458 #[test]
459 fn test_audio_filter_volume() {
460 let filter = AudioFilter::new().volume(1.5);
461 assert_eq!(filter.len(), 1);
462 }
463
464 #[test]
465 fn test_audio_filter_resample() {
466 let filter = AudioFilter::new().resample(48000);
467 assert_eq!(filter.len(), 1);
468 }
469
470 #[test]
471 fn test_audio_filter_normalize() {
472 let filter = AudioFilter::new().normalize(-23.0);
473 assert_eq!(filter.len(), 1);
474 }
475
476 #[test]
477 fn test_audio_filter_chain() {
478 let filter = AudioFilter::new()
479 .volume(1.0)
480 .resample(48000)
481 .normalize(-23.0)
482 .compress(-20.0, 3.0);
483
484 assert_eq!(filter.len(), 4);
485 }
486
487 #[test]
488 fn test_audio_filter_eq() {
489 let filter = AudioFilter::new()
490 .equalizer(100.0, 200.0, 5.0)
491 .equalizer(1000.0, 200.0, -3.0);
492
493 assert_eq!(filter.len(), 2);
494 }
495
496 #[test]
497 fn test_audio_filter_fade() {
498 let filter = AudioFilter::new().fade_in(2.0).fade_out(58.0, 2.0);
499
500 assert_eq!(filter.len(), 2);
501 }
502
503 #[test]
504 fn test_filter_node_display() {
505 let node = FilterNode {
506 name: "scale".to_string(),
507 params: vec![
508 ("width".to_string(), "1920".to_string()),
509 ("height".to_string(), "1080".to_string()),
510 ],
511 };
512
513 let display = format!("{node}");
514 assert!(display.contains("scale"));
515 assert!(display.contains("width=1920"));
516 assert!(display.contains("height=1080"));
517 }
518
519 #[test]
520 fn test_empty_filter_chain() {
521 let video_filter = VideoFilter::new();
522 assert!(video_filter.is_empty());
523 assert_eq!(video_filter.len(), 0);
524
525 let audio_filter = AudioFilter::new();
526 assert!(audio_filter.is_empty());
527 assert_eq!(audio_filter.len(), 0);
528 }
529}