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