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::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 filters: Vec<VideoFilter>,
24 extra_args: Vec<OsString>,
25 overwrite: bool,
26}
27
28impl TranscodeBuilder {
29 pub fn new() -> Self {
31 Self {
32 overwrite: true,
33 ..Self::default()
34 }
35 }
36
37 pub fn with_binaries(mut self, binaries: &FfmpegBinaryPaths) -> Self {
39 self.binaries = Some(binaries.clone());
40 self
41 }
42
43 pub fn with_locator(mut self, locator: &FfmpegLocator) -> Self {
45 self.binaries = Some(locator.binaries().clone());
46 self
47 }
48
49 pub fn input<P: AsRef<Path>>(mut self, path: P) -> Self {
51 self.input = Some(path.as_ref().to_path_buf());
52 self
53 }
54
55 pub fn output<P: AsRef<Path>>(mut self, path: P) -> Self {
57 self.output = Some(path.as_ref().to_path_buf());
58 self
59 }
60
61 pub fn video_codec(mut self, codec: impl Into<String>) -> Self {
63 self.video_codec = Some(codec.into());
64 self
65 }
66
67 pub fn audio_codec(mut self, codec: impl Into<String>) -> Self {
69 self.audio_codec = Some(codec.into());
70 self
71 }
72
73 pub fn video_bitrate(mut self, kbps: u32) -> Self {
75 self.video_bitrate = Some(kbps);
76 self
77 }
78
79 pub fn audio_bitrate(mut self, kbps: u32) -> Self {
81 self.audio_bitrate = Some(kbps);
82 self
83 }
84
85 pub fn frame_rate(mut self, fps: f64) -> Self {
87 self.frame_rate = Some(fps);
88 self
89 }
90
91 pub fn preset(mut self, preset: impl Into<String>) -> Self {
93 self.preset = Some(preset.into());
94 self
95 }
96
97 pub fn size(self, width: u32, height: u32) -> Self {
99 self.add_filter(VideoFilter::Scale { width, height })
100 }
101
102 pub fn add_filter(mut self, filter: VideoFilter) -> Self {
104 self.filters.push(filter);
105 self
106 }
107
108 pub fn extra_arg(mut self, arg: impl Into<OsString>) -> Self {
110 self.extra_args.push(arg.into());
111 self
112 }
113
114 pub fn overwrite(mut self, enabled: bool) -> Self {
116 self.overwrite = enabled;
117 self
118 }
119
120 fn resolve_binaries(binaries: Option<FfmpegBinaryPaths>) -> Result<FfmpegBinaryPaths> {
121 if let Some(paths) = binaries {
122 return Ok(paths);
123 }
124 Ok(FfmpegLocator::system()?.binaries().clone())
125 }
126
127 fn validate(self) -> Result<ValidatedTranscode> {
128 let Self {
129 binaries,
130 input,
131 output,
132 video_codec,
133 audio_codec,
134 video_bitrate,
135 audio_bitrate,
136 frame_rate,
137 preset,
138 filters,
139 extra_args,
140 overwrite,
141 } = self;
142
143 let input = input.ok_or_else(|| Error::InvalidInput("input path is required".into()))?;
144 let output = output.ok_or_else(|| Error::InvalidInput("output path is required".into()))?;
145
146 Ok(ValidatedTranscode {
147 binaries: Self::resolve_binaries(binaries)?,
148 input,
149 output,
150 video_codec,
151 audio_codec,
152 video_bitrate,
153 audio_bitrate,
154 frame_rate,
155 preset,
156 filters,
157 extra_args,
158 overwrite,
159 })
160 }
161
162 pub fn run(self) -> Result<()> {
164 let validated = self.validate()?;
165 validated.run()
166 }
167}
168
169struct ValidatedTranscode {
170 binaries: FfmpegBinaryPaths,
171 input: PathBuf,
172 output: PathBuf,
173 video_codec: Option<String>,
174 audio_codec: Option<String>,
175 video_bitrate: Option<u32>,
176 audio_bitrate: Option<u32>,
177 frame_rate: Option<f64>,
178 preset: Option<String>,
179 filters: Vec<VideoFilter>,
180 extra_args: Vec<OsString>,
181 overwrite: bool,
182}
183
184impl ValidatedTranscode {
185 fn run(self) -> Result<()> {
186 let mut cmd = FfmpegCommand::new(self.binaries.ffmpeg());
187 cmd.arg(if self.overwrite { "-y" } else { "-n" });
188 cmd.arg("-i").arg(&self.input);
189
190 if let Some(codec) = self.video_codec {
191 cmd.arg("-c:v").arg(codec);
192 }
193 if let Some(codec) = self.audio_codec {
194 cmd.arg("-c:a").arg(codec);
195 }
196 if let Some(kbps) = self.video_bitrate {
197 cmd.arg("-b:v").arg(format!("{kbps}k"));
198 }
199 if let Some(kbps) = self.audio_bitrate {
200 cmd.arg("-b:a").arg(format!("{kbps}k"));
201 }
202 if let Some(fps) = self.frame_rate {
203 cmd.arg("-r").arg(format!("{fps}"));
204 }
205 if let Some(preset) = self.preset {
206 cmd.arg("-preset").arg(preset);
207 }
208
209 let mut filter_strings: Vec<String> = Vec::new();
210 for filter in self.filters {
211 filter_strings.push(filter.to_filter_string());
212 }
213 if !filter_strings.is_empty() {
214 cmd.arg("-vf").arg(filter_strings.join(","));
215 }
216
217 for arg in self.extra_args {
218 cmd.arg(arg);
219 }
220
221 cmd.arg(&self.output);
222 cmd.run()
223 }
224}