1use std::fmt;
4
5use crate::types::Time;
6
7#[derive(Clone, Debug, PartialEq)]
9pub enum VideoFilter {
10 Scale {
12 width: u32,
13 height: u32,
14 },
15 Trim {
17 start: Time,
18 end: Option<Time>,
19 },
20 Crop {
22 width: u32,
23 height: u32,
24 x: u32,
25 y: u32,
26 },
27 Rotate {
29 degrees: f64,
30 },
31 Flip {
33 direction: char,
34 },
35 BrightnessContrast {
37 brightness: Option<f32>,
38 contrast: Option<f32>,
39 },
40 Denoise {
42 strength: DenoiseStrength,
43 },
44 Deinterlace,
46 Custom(String),
48}
49
50#[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 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 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(), },
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#[derive(Clone, Debug, PartialEq)]
122pub enum AudioFilter {
123 Volume(f32),
125 Equalizer {
127 bass: Option<f32>,
128 mid: Option<f32>,
129 treble: Option<f32>,
130 },
131 Normalization {
133 target_level: f32,
134 },
135 HighPass {
137 frequency: f32,
138 },
139 LowPass {
141 frequency: f32,
142 },
143 Custom(String),
145}
146
147impl AudioFilter {
148 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}