ffmpeg_light/
transcode.rs1use std::ffi::OsString;
4use std::path::{Path, PathBuf};
5
6use crate::command::{FfmpegBinaryPaths, FfmpegCommand};
7use crate::config::FfmpegLocator;
8use crate::error::{Error, Result};
9use crate::filter::{AudioFilter, VideoFilter};
10
11#[derive(Debug, Default)]
13pub struct TranscodeBuilder {
14 binaries: Option<FfmpegBinaryPaths>,
15 input: Option<PathBuf>,
16 output: Option<PathBuf>,
17 video_codec: Option<String>,
18 audio_codec: Option<String>,
19 video_bitrate: Option<u32>,
20 audio_bitrate: Option<u32>,
21 frame_rate: Option<f64>,
22 preset: Option<String>,
23 video_filters: Vec<VideoFilter>,
24 audio_filters: Vec<AudioFilter>,
25 extra_args: Vec<OsString>,
26 overwrite: bool,
27}
28
29impl TranscodeBuilder {
30 pub fn new() -> Self {
32 Self {
33 overwrite: true,
34 ..Self::default()
35 }
36 }
37
38 pub fn with_binaries(mut self, binaries: &FfmpegBinaryPaths) -> Self {
40 self.binaries = Some(binaries.clone());
41 self
42 }
43
44 pub fn with_locator(mut self, locator: &FfmpegLocator) -> Self {
46 self.binaries = Some(locator.binaries().clone());
47 self
48 }
49
50 pub fn input<P: AsRef<Path>>(mut self, path: P) -> Self {
52 self.input = Some(path.as_ref().to_path_buf());
53 self
54 }
55
56 pub fn output<P: AsRef<Path>>(mut self, path: P) -> Self {
58 self.output = Some(path.as_ref().to_path_buf());
59 self
60 }
61
62 pub fn video_codec(mut self, codec: impl Into<String>) -> Self {
64 self.video_codec = Some(codec.into());
65 self
66 }
67
68 pub fn audio_codec(mut self, codec: impl Into<String>) -> Self {
70 self.audio_codec = Some(codec.into());
71 self
72 }
73
74 pub fn video_bitrate(mut self, kbps: u32) -> Self {
76 self.video_bitrate = Some(kbps);
77 self
78 }
79
80 pub fn audio_bitrate(mut self, kbps: u32) -> Self {
82 self.audio_bitrate = Some(kbps);
83 self
84 }
85
86 pub fn frame_rate(mut self, fps: f64) -> Self {
88 self.frame_rate = Some(fps);
89 self
90 }
91
92 pub fn preset(mut self, preset: impl Into<String>) -> Self {
94 self.preset = Some(preset.into());
95 self
96 }
97
98 pub fn size(self, width: u32, height: u32) -> Self {
100 self.add_video_filter(VideoFilter::Scale { width, height })
101 }
102
103 pub fn add_video_filter(mut self, filter: VideoFilter) -> Self {
105 self.video_filters.push(filter);
106 self
107 }
108
109 pub fn add_audio_filter(mut self, filter: AudioFilter) -> Self {
111 self.audio_filters.push(filter);
112 self
113 }
114
115 #[deprecated(since = "0.2.0", note = "use add_video_filter() instead")]
117 pub fn add_filter(self, filter: VideoFilter) -> Self {
118 self.add_video_filter(filter)
119 }
120
121 pub fn extra_arg(mut self, arg: impl Into<OsString>) -> Self {
123 self.extra_args.push(arg.into());
124 self
125 }
126
127 pub fn overwrite(mut self, enabled: bool) -> Self {
129 self.overwrite = enabled;
130 self
131 }
132
133 pub fn input_path(&self) -> Option<&Path> {
135 self.input.as_deref()
136 }
137
138 pub fn output_path(&self) -> Option<&Path> {
140 self.output.as_deref()
141 }
142
143 pub fn video_codec_ref(&self) -> Option<&str> {
145 self.video_codec.as_deref()
146 }
147
148 pub fn audio_codec_ref(&self) -> Option<&str> {
150 self.audio_codec.as_deref()
151 }
152
153 pub fn video_bitrate_value(&self) -> Option<u32> {
155 self.video_bitrate
156 }
157
158 pub fn audio_bitrate_value(&self) -> Option<u32> {
160 self.audio_bitrate
161 }
162
163 pub fn frame_rate_value(&self) -> Option<f64> {
165 self.frame_rate
166 }
167
168 pub fn preset_value(&self) -> Option<&str> {
170 self.preset.as_deref()
171 }
172
173 pub fn overwrite_enabled(&self) -> bool {
175 self.overwrite
176 }
177
178 pub fn video_filters(&self) -> &[VideoFilter] {
180 &self.video_filters
181 }
182
183 pub fn audio_filters(&self) -> &[AudioFilter] {
185 &self.audio_filters
186 }
187
188 fn resolve_binaries(binaries: Option<FfmpegBinaryPaths>) -> Result<FfmpegBinaryPaths> {
189 if let Some(paths) = binaries {
190 return Ok(paths);
191 }
192 Ok(FfmpegLocator::system()?.binaries().clone())
193 }
194
195 fn validate(self) -> Result<ValidatedTranscode> {
196 let Self {
197 binaries,
198 input,
199 output,
200 video_codec,
201 audio_codec,
202 video_bitrate,
203 audio_bitrate,
204 frame_rate,
205 preset,
206 video_filters,
207 audio_filters,
208 extra_args,
209 overwrite,
210 } = self;
211
212 let input = input.ok_or_else(|| Error::InvalidInput("input path is required".into()))?;
213 let output = output.ok_or_else(|| Error::InvalidInput("output path is required".into()))?;
214
215 Ok(ValidatedTranscode {
216 binaries: Self::resolve_binaries(binaries)?,
217 input,
218 output,
219 video_codec,
220 audio_codec,
221 video_bitrate,
222 audio_bitrate,
223 frame_rate,
224 preset,
225 video_filters,
226 audio_filters,
227 extra_args,
228 overwrite,
229 })
230 }
231
232 pub fn run(self) -> Result<()> {
234 let validated = self.validate()?;
235 validated.run()
236 }
237}
238
239struct ValidatedTranscode {
240 binaries: FfmpegBinaryPaths,
241 input: PathBuf,
242 output: PathBuf,
243 video_codec: Option<String>,
244 audio_codec: Option<String>,
245 video_bitrate: Option<u32>,
246 audio_bitrate: Option<u32>,
247 frame_rate: Option<f64>,
248 preset: Option<String>,
249 video_filters: Vec<VideoFilter>,
250 audio_filters: Vec<AudioFilter>,
251 extra_args: Vec<OsString>,
252 overwrite: bool,
253}
254
255impl ValidatedTranscode {
256 fn run(self) -> Result<()> {
257 let mut cmd = FfmpegCommand::new(self.binaries.ffmpeg());
258 cmd.arg(if self.overwrite { "-y" } else { "-n" });
259 cmd.arg("-i").arg(&self.input);
260
261 if let Some(codec) = self.video_codec {
262 cmd.arg("-c:v").arg(codec);
263 }
264 if let Some(codec) = self.audio_codec {
265 cmd.arg("-c:a").arg(codec);
266 }
267 if let Some(kbps) = self.video_bitrate {
268 cmd.arg("-b:v").arg(format!("{kbps}k"));
269 }
270 if let Some(kbps) = self.audio_bitrate {
271 cmd.arg("-b:a").arg(format!("{kbps}k"));
272 }
273 if let Some(fps) = self.frame_rate {
274 cmd.arg("-r").arg(format!("{fps}"));
275 }
276 if let Some(preset) = self.preset {
277 cmd.arg("-preset").arg(preset);
278 }
279
280 let mut vf_strings: Vec<String> = Vec::new();
282 for filter in self.video_filters {
283 vf_strings.push(filter.to_filter_string());
284 }
285 if !vf_strings.is_empty() {
286 cmd.arg("-vf").arg(vf_strings.join(","));
287 }
288
289 let mut af_strings: Vec<String> = Vec::new();
291 for filter in self.audio_filters {
292 af_strings.push(filter.to_filter_string());
293 }
294 if !af_strings.is_empty() {
295 cmd.arg("-af").arg(af_strings.join(","));
296 }
297
298 for arg in self.extra_args {
299 cmd.arg(arg);
300 }
301
302 cmd.arg(&self.output);
303 cmd.run()
304 }
305}
306