1use ffmpeg_common::{utils, Result};
2use std::fmt;
3
4#[derive(Debug, Clone)]
6pub struct VideoFilter {
7 name: String,
8 params: Vec<(String, String)>,
9}
10
11impl VideoFilter {
12 pub fn new(name: impl Into<String>) -> Self {
14 Self {
15 name: name.into(),
16 params: Vec::new(),
17 }
18 }
19
20 pub fn param(mut self, key: impl Into<String>, value: impl ToString) -> Self {
22 self.params.push((key.into(), value.to_string()));
23 self
24 }
25
26 pub fn scale(width: i32, height: i32) -> Self {
28 Self::new("scale")
29 .param("w", width)
30 .param("h", height)
31 }
32
33 pub fn scale_aspect(width: i32) -> Self {
35 Self::new("scale")
36 .param("w", width)
37 .param("h", -1)
38 }
39
40 pub fn crop(width: u32, height: u32, x: u32, y: u32) -> Self {
42 Self::new("crop")
43 .param("w", width)
44 .param("h", height)
45 .param("x", x)
46 .param("y", y)
47 }
48
49 pub fn pad(width: u32, height: u32) -> Self {
51 Self::new("pad")
52 .param("width", width)
53 .param("height", height)
54 .param("x", "(ow-iw)/2")
55 .param("y", "(oh-ih)/2")
56 }
57
58 pub fn rotate(angle: f64) -> Self {
60 Self::new("rotate").param("angle", angle)
61 }
62
63 pub fn transpose(direction: TransposeDirection) -> Self {
65 Self::new("transpose").param("dir", direction as u8)
66 }
67
68 pub fn hflip() -> Self {
70 Self::new("hflip")
71 }
72
73 pub fn vflip() -> Self {
75 Self::new("vflip")
76 }
77
78 pub fn fps(framerate: f64) -> Self {
80 Self::new("fps").param("fps", framerate)
81 }
82
83 pub fn deinterlace() -> Self {
85 Self::new("yadif")
86 }
87
88 pub fn denoise(strength: f64) -> Self {
90 Self::new("hqdn3d").param("luma_spatial", strength)
91 }
92
93 pub fn sharpen() -> Self {
95 Self::new("unsharp")
96 }
97
98 pub fn blur(radius: u32) -> Self {
100 Self::new("boxblur").param("lr", radius).param("lp", 1)
101 }
102
103 pub fn overlay(x: impl Into<String>, y: impl Into<String>) -> Self {
105 Self::new("overlay")
106 .param("x", x.into())
107 .param("y", y.into())
108 }
109
110 pub fn drawtext(text: impl Into<String>) -> Self {
112 Self::new("drawtext")
113 .param("text", utils::escape_filter_string(&text.into()))
114 }
115
116 pub fn fade_in(duration: f64) -> Self {
118 Self::new("fade")
119 .param("type", "in")
120 .param("duration", duration)
121 }
122
123 pub fn fade_out(duration: f64, start_time: f64) -> Self {
125 Self::new("fade")
126 .param("type", "out")
127 .param("duration", duration)
128 .param("start_time", start_time)
129 }
130
131 pub fn setpts(expr: impl Into<String>) -> Self {
133 Self::new("setpts").param("expr", expr.into())
134 }
135
136 pub fn select(expr: impl Into<String>) -> Self {
138 Self::new("select").param("expr", expr.into())
139 }
140
141 pub fn eq() -> Self {
143 Self::new("eq")
144 }
145
146 pub fn brightness(mut self, value: f64) -> Self {
148 if self.name == "eq" {
149 self.params.push(("brightness".to_string(), value.to_string()));
150 }
151 self
152 }
153
154 pub fn contrast(mut self, value: f64) -> Self {
156 if self.name == "eq" {
157 self.params.push(("contrast".to_string(), value.to_string()));
158 }
159 self
160 }
161
162 pub fn saturation(mut self, value: f64) -> Self {
164 if self.name == "eq" {
165 self.params.push(("saturation".to_string(), value.to_string()));
166 }
167 self
168 }
169
170 pub fn format(pix_fmt: impl Into<String>) -> Self {
172 Self::new("format").param("pix_fmts", pix_fmt.into())
173 }
174}
175
176impl fmt::Display for VideoFilter {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 write!(f, "{}", self.name)?;
179 if !self.params.is_empty() {
180 write!(f, "=")?;
181 let params: Vec<String> = self.params
182 .iter()
183 .map(|(k, v)| format!("{}={}", k, v))
184 .collect();
185 write!(f, "{}", params.join(":"))?;
186 }
187 Ok(())
188 }
189}
190
191#[derive(Debug, Clone)]
193pub struct AudioFilter {
194 name: String,
195 params: Vec<(String, String)>,
196}
197
198impl AudioFilter {
199 pub fn new(name: impl Into<String>) -> Self {
201 Self {
202 name: name.into(),
203 params: Vec::new(),
204 }
205 }
206
207 pub fn param(mut self, key: impl Into<String>, value: impl ToString) -> Self {
209 self.params.push((key.into(), value.to_string()));
210 self
211 }
212
213 pub fn volume(level: f64) -> Self {
215 Self::new("volume").param("volume", level)
216 }
217
218 pub fn loudnorm() -> Self {
220 Self::new("loudnorm")
221 .param("I", -16)
222 .param("TP", -1.5)
223 .param("LRA", 11)
224 }
225
226 pub fn dynaudnorm() -> Self {
228 Self::new("dynaudnorm")
229 }
230
231 pub fn highpass(frequency: u32) -> Self {
233 Self::new("highpass").param("f", frequency)
234 }
235
236 pub fn lowpass(frequency: u32) -> Self {
238 Self::new("lowpass").param("f", frequency)
239 }
240
241 pub fn afade_in(duration: f64) -> Self {
243 Self::new("afade")
244 .param("type", "in")
245 .param("duration", duration)
246 }
247
248 pub fn afade_out(duration: f64, start_time: f64) -> Self {
250 Self::new("afade")
251 .param("type", "out")
252 .param("duration", duration)
253 .param("start_time", start_time)
254 }
255
256 pub fn aresample(sample_rate: u32) -> Self {
258 Self::new("aresample").param("sample_rate", sample_rate)
259 }
260
261 pub fn atempo(tempo: f64) -> Self {
263 Self::new("atempo").param("tempo", tempo)
264 }
265
266 pub fn adelay(delays: impl Into<String>) -> Self {
268 Self::new("adelay").param("delays", delays.into())
269 }
270
271 pub fn aecho(delays: impl Into<String>, decays: impl Into<String>) -> Self {
273 Self::new("aecho")
274 .param("delays", delays.into())
275 .param("decays", decays.into())
276 }
277
278 pub fn acompressor() -> Self {
280 Self::new("acompressor")
281 }
282
283 pub fn alimiter() -> Self {
285 Self::new("alimiter")
286 }
287
288 pub fn agate() -> Self {
290 Self::new("agate")
291 }
292
293 pub fn anequalizer(frequency: u32, width: f64, gain: f64) -> Self {
295 Self::new("anequalizer")
296 .param("frequency", frequency)
297 .param("width", width)
298 .param("gain", gain)
299 }
300
301 pub fn channelmap(map: impl Into<String>) -> Self {
303 Self::new("channelmap").param("map", map.into())
304 }
305
306 pub fn amerge() -> Self {
308 Self::new("amerge")
309 }
310
311 pub fn channelsplit() -> Self {
313 Self::new("channelsplit")
314 }
315}
316
317impl fmt::Display for AudioFilter {
318 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319 write!(f, "{}", self.name)?;
320 if !self.params.is_empty() {
321 write!(f, "=")?;
322 let params: Vec<String> = self.params
323 .iter()
324 .map(|(k, v)| format!("{}={}", k, v))
325 .collect();
326 write!(f, "{}", params.join(":"))?;
327 }
328 Ok(())
329 }
330}
331
332#[derive(Debug, Clone, Copy)]
334#[repr(u8)]
335pub enum TransposeDirection {
336 CounterClockwiseFlip = 0,
337 Clockwise = 1,
338 CounterClockwise = 2,
339 ClockwiseFlip = 3,
340}
341
342#[derive(Debug, Clone, Default)]
344pub struct FilterGraph {
345 nodes: Vec<FilterNode>,
346 edges: Vec<FilterEdge>,
347}
348
349#[derive(Debug, Clone)]
350struct FilterNode {
351 id: String,
352 filter: String,
353 inputs: Vec<String>,
354 outputs: Vec<String>,
355}
356
357#[derive(Debug, Clone)]
358struct FilterEdge {
359 from: String,
360 to: String,
361}
362
363impl FilterGraph {
364 pub fn new() -> Self {
366 Self::default()
367 }
368
369 pub fn add_filter(
371 mut self,
372 filter: impl Into<String>,
373 inputs: Vec<String>,
374 outputs: Vec<String>,
375 ) -> Self {
376 let id = format!("f{}", self.nodes.len());
377 self.nodes.push(FilterNode {
378 id: id.clone(),
379 filter: filter.into(),
380 inputs,
381 outputs,
382 });
383 self
384 }
385
386 pub fn connect(mut self, from: impl Into<String>, to: impl Into<String>) -> Self {
388 self.edges.push(FilterEdge {
389 from: from.into(),
390 to: to.into(),
391 });
392 self
393 }
394
395 pub fn build(&self) -> String {
397 let mut parts = Vec::new();
398
399 for node in &self.nodes {
400 let mut part = String::new();
401
402 if !node.inputs.is_empty() {
404 part.push_str(&node.inputs.join(""));
405 }
406
407 part.push_str(&node.filter);
409
410 if !node.outputs.is_empty() {
412 part.push_str(&node.outputs.join(""));
413 }
414
415 parts.push(part);
416 }
417
418 parts.join(";")
419 }
420}
421
422pub mod chains {
424 use super::*;
425
426 pub fn thumbnail() -> Vec<VideoFilter> {
428 vec![
429 VideoFilter::select("eq(pict_type\\,I)"),
430 VideoFilter::scale(320, -1),
431 ]
432 }
433
434 pub fn gif_optimize(width: u32, fps: f64) -> Vec<VideoFilter> {
436 vec![
437 VideoFilter::fps(fps),
438 VideoFilter::scale(width as i32, -1),
439 VideoFilter::new("split").param("outputs", 2),
440 VideoFilter::new("palettegen"),
441 VideoFilter::new("paletteuse"),
442 ]
443 }
444
445 pub fn stabilize() -> Vec<VideoFilter> {
447 vec![
448 VideoFilter::new("vidstabdetect").param("shakiness", 5),
449 VideoFilter::new("vidstabtransform"),
450 ]
451 }
452
453 pub fn cinematic() -> Vec<VideoFilter> {
455 vec![
456 VideoFilter::eq()
457 .contrast(1.2)
458 .brightness(-0.05)
459 .saturation(0.8),
460 VideoFilter::new("curves")
461 .param("preset", "vintage"),
462 VideoFilter::new("vignette"),
463 ]
464 }
465
466 pub fn upscale_enhance(scale: u32) -> Vec<VideoFilter> {
468 vec![
469 VideoFilter::scale(scale as i32, -1),
470 VideoFilter::sharpen(),
471 VideoFilter::new("hqdn3d").param("luma_spatial", 4),
472 ]
473 }
474
475 pub fn audio_master() -> Vec<AudioFilter> {
477 vec![
478 AudioFilter::highpass(80),
479 AudioFilter::acompressor()
480 .param("threshold", -20)
481 .param("ratio", 4)
482 .param("attack", 5)
483 .param("release", 50),
484 AudioFilter::anequalizer(100, 100.0, 2.0),
485 AudioFilter::anequalizer(1000, 500.0, -1.0),
486 AudioFilter::anequalizer(10000, 2000.0, 1.0),
487 AudioFilter::alimiter()
488 .param("limit", -0.5),
489 AudioFilter::loudnorm(),
490 ]
491 }
492
493 pub fn podcast_audio() -> Vec<AudioFilter> {
495 vec![
496 AudioFilter::highpass(100),
497 AudioFilter::lowpass(15000),
498 AudioFilter::agate()
499 .param("threshold", -35)
500 .param("range", -40),
501 AudioFilter::acompressor()
502 .param("threshold", -15)
503 .param("ratio", 3),
504 AudioFilter::loudnorm(),
505 ]
506 }
507}
508
509#[cfg(test)]
510mod tests {
511 use super::*;
512
513 #[test]
514 fn test_video_filters() {
515 let scale = VideoFilter::scale(1920, 1080);
516 assert_eq!(scale.to_string(), "scale=w=1920:h=1080");
517
518 let crop = VideoFilter::crop(640, 480, 100, 50);
519 assert_eq!(crop.to_string(), "crop=w=640:h=480:x=100:y=50");
520
521 let text = VideoFilter::drawtext("Hello, World!");
522 assert!(text.to_string().contains("drawtext=text="));
523 }
524
525 #[test]
526 fn test_audio_filters() {
527 let volume = AudioFilter::volume(0.5);
528 assert_eq!(volume.to_string(), "volume=volume=0.5");
529
530 let tempo = AudioFilter::atempo(1.5);
531 assert_eq!(tempo.to_string(), "atempo=tempo=1.5");
532 }
533
534 #[test]
535 fn test_filter_graph() {
536 let graph = FilterGraph::new()
537 .add_filter("scale=640:480", vec!["[0:v]".to_string()], vec!["[scaled]".to_string()])
538 .add_filter("overlay", vec!["[scaled]".to_string(), "[1:v]".to_string()], vec!["[out]".to_string()]);
539
540 let result = graph.build();
541 assert!(result.contains("[0:v]scale=640:480[scaled]"));
542 assert!(result.contains("[scaled][1:v]overlay[out]"));
543 }
544}