ffmpeg_light/
filter.rs

1//! Video and audio filter definitions. Filters can be composed into chains for complex processing.
2
3use std::fmt;
4
5use crate::types::Time;
6
7/// Video filters for common editing tasks.
8#[derive(Clone, Debug, PartialEq)]
9pub enum VideoFilter {
10    /// Scale video to the provided width/height. Use -1 for one dimension to preserve aspect ratio.
11    Scale {
12        width: u32,
13        height: u32,
14    },
15    /// Trim video between start and optional end timestamps.
16    Trim {
17        start: Time,
18        end: Option<Time>,
19    },
20    /// Crop video to specified dimensions at given offset.
21    Crop {
22        width: u32,
23        height: u32,
24        x: u32,
25        y: u32,
26    },
27    /// Rotate video by angle in degrees. Typically 90, 180, or 270.
28    Rotate {
29        degrees: f64,
30    },
31    /// Flip video horizontally or vertically. 'h' for horizontal, 'v' for vertical.
32    Flip {
33        direction: char,
34    },
35    /// Adjust brightness and contrast. Brightness range: -1.0 to 1.0, Contrast: 0.0 to 2.0.
36    BrightnessContrast {
37        brightness: Option<f32>,
38        contrast: Option<f32>,
39    },
40    /// Remove noise with denoise filter (light, medium, heavy).
41    Denoise {
42        strength: DenoiseStrength,
43    },
44    /// Deinterlace interlaced video (useful for old TV recordings).
45    Deinterlace,
46    /// Custom filter string for advanced use-cases. FFmpeg syntax.
47    Custom(String),
48}
49
50/// Denoise filter strength options.
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub enum DenoiseStrength {
53    Light,
54    Medium,
55    Heavy,
56}
57
58impl DenoiseStrength {
59    fn to_filter_value(&self) -> &'static str {
60        match self {
61            DenoiseStrength::Light => "hqdn3d=1.5:1.5:6:6",
62            DenoiseStrength::Medium => "hqdn3d=3:3:6:6",
63            DenoiseStrength::Heavy => "hqdn3d=5:5:6:6",
64        }
65    }
66}
67
68impl VideoFilter {
69    /// Convert filter to FFmpeg `-vf` format string.
70    pub fn to_filter_string(&self) -> String {
71        match self {
72            VideoFilter::Scale { width, height } => format!("scale={width}:{height}"),
73            VideoFilter::Trim { start, end } => match end {
74                Some(end) => format!("trim=start={start}:end={end}"),
75                None => format!("trim=start={start}"),
76            },
77            VideoFilter::Crop { width, height, x, y } => {
78                format!("crop={width}:{height}:{x}:{y}")
79            }
80            VideoFilter::Rotate { degrees } => {
81                // FFmpeg rotate expects radians, but we use degrees for API simplicity
82                let radians = degrees * std::f64::consts::PI / 180.0;
83                format!("rotate={radians}")
84            }
85            VideoFilter::Flip { direction } => match direction {
86                'h' => "hflip".to_string(),
87                'v' => "vflip".to_string(),
88                _ => "hflip".to_string(), // default to horizontal
89            },
90            VideoFilter::BrightnessContrast {
91                brightness,
92                contrast,
93            } => {
94                let mut parts = Vec::new();
95                if let Some(b) = brightness {
96                    parts.push(format!("brightness={b}"));
97                }
98                if let Some(c) = contrast {
99                    parts.push(format!("contrast={c}"));
100                }
101                if parts.is_empty() {
102                    "eq".to_string()
103                } else {
104                    format!("eq={}", parts.join(":"))
105                }
106            }
107            VideoFilter::Denoise { strength } => strength.to_filter_value().to_string(),
108            VideoFilter::Deinterlace => "yadif".to_string(),
109            VideoFilter::Custom(raw) => raw.clone(),
110        }
111    }
112}
113
114impl fmt::Display for VideoFilter {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        write!(f, "{}", self.to_filter_string())
117    }
118}
119
120/// Audio filters for sound processing.
121#[derive(Clone, Debug, PartialEq)]
122pub enum AudioFilter {
123    /// Adjust volume. 1.0 = no change, 0.5 = half volume, 2.0 = double.
124    Volume(f32),
125    /// Equalization with bass, mid, and treble adjustments in dB.
126    Equalizer {
127        bass: Option<f32>,
128        mid: Option<f32>,
129        treble: Option<f32>,
130    },
131    /// Normalize audio to prevent clipping. Target level in dBFS.
132    Normalization {
133        target_level: f32,
134    },
135    /// High-pass filter to remove low frequencies. Frequency in Hz.
136    HighPass {
137        frequency: f32,
138    },
139    /// Low-pass filter to remove high frequencies. Frequency in Hz.
140    LowPass {
141        frequency: f32,
142    },
143    /// Custom audio filter for advanced use-cases. FFmpeg syntax.
144    Custom(String),
145}
146
147impl AudioFilter {
148    /// Convert filter to FFmpeg `-af` format string.
149    pub fn to_filter_string(&self) -> String {
150        match self {
151            AudioFilter::Volume(vol) => format!("volume={vol}"),
152            AudioFilter::Equalizer { bass, mid, treble } => {
153                let mut parts = Vec::new();
154                if let Some(b) = bass {
155                    parts.push(format!("b={b}"));
156                }
157                if let Some(m) = mid {
158                    parts.push(format!("m={m}"));
159                }
160                if let Some(t) = treble {
161                    parts.push(format!("t={t}"));
162                }
163                if parts.is_empty() {
164                    "superequalizer".to_string()
165                } else {
166                    format!("superequalizer={}", parts.join(":"))
167                }
168            }
169            AudioFilter::Normalization { target_level } => {
170                format!("anlmdn=m=I:p=0.05,loudnorm=I={target_level}")
171            }
172            AudioFilter::HighPass { frequency } => {
173                format!("highpass=f={frequency}")
174            }
175            AudioFilter::LowPass { frequency } => {
176                format!("lowpass=f={frequency}")
177            }
178            AudioFilter::Custom(raw) => raw.clone(),
179        }
180    }
181}
182
183impl fmt::Display for AudioFilter {
184    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185        write!(f, "{}", self.to_filter_string())
186    }
187}