ffmpeg_cli/lib.rs
1//! Wraps the ffpmeg cli, using `-progress` to report progress
2//!
3//! Sometimes you just want a simple way to use ffmpeg. Most crates just use ffi, leading to
4//! complicated interfaces. `ffmpeg_cli` avoids this by wrapping the cli, for when you don't need
5//! the flexibility the real ffmpeg api gives you.
6//!
7//! ```no_run
8//! use std::process::Stdio;
9//!
10//! use ffmpeg_cli::{FfmpegBuilder, File, Parameter};
11//! use futures::{future::ready, StreamExt};
12//!
13//! #[tokio::main]
14//! async fn main() {
15//! let builder = FfmpegBuilder::new()
16//! .stderr(Stdio::piped())
17//! .option(Parameter::Single("nostdin"))
18//! .option(Parameter::Single("y"))
19//! .input(File::new("input.mkv"))
20//! .output(
21//! File::new("output.mp4")
22//! .option(Parameter::KeyValue("vcodec", "libx265"))
23//! .option(Parameter::KeyValue("crf", "28")),
24//! );
25//!
26//! let ffmpeg = builder.run().await.unwrap();
27//!
28//! ffmpeg
29//! .progress
30//! .for_each(|x| {
31//! dbg!(x.unwrap());
32//! ready(())
33//! })
34//! .await;
35//!
36//! let output = ffmpeg.process.wait_with_output().unwrap();
37//!
38//! println!(
39//! "{}\nstderr:\n{}",
40//! output.status,
41//! std::str::from_utf8(&output.stderr).unwrap()
42//! );
43//! }
44//! ```
45#![warn(missing_docs)]
46
47use std::process::{Command, Stdio};
48
49mod runner;
50
51#[doc(inline)]
52pub use runner::*;
53
54/// The main struct which is used to set up ffmpeg.
55#[derive(Debug)]
56pub struct FfmpegBuilder<'a> {
57 /// The global options.
58 pub options: Vec<Parameter<'a>>,
59 /// The input files.
60 pub inputs: Vec<File<'a>>,
61 /// The output files.
62 pub outputs: Vec<File<'a>>,
63
64 /// The command that's run for ffmpeg. Usually just `ffmpeg`
65 pub ffmpeg_command: &'a str,
66 /// Passed as [Command::stdin]
67 pub stdin: Stdio,
68 /// Passed as [Command::stdout]
69 pub stdout: Stdio,
70 /// Passed as [Command::stderr]
71 pub stderr: Stdio,
72}
73
74/// A file that ffmpeg operates on.
75///
76/// This can be an input or output, it depends on what you add it as.
77#[derive(Debug)]
78pub struct File<'a> {
79 /// The url of the file.
80 ///
81 /// As with ffmpeg, just a normal path works.
82 pub url: &'a str,
83 /// The options corresponding to this file.
84 pub options: Vec<Parameter<'a>>,
85}
86
87/// A global or file option to be passed to ffmpeg.
88#[derive(Debug)]
89pub enum Parameter<'a> {
90 /// An option which does not take a value, ex. `-autorotate`.
91 ///
92 /// `-autorotate` would be represented as `Single("autorotate")`,
93 /// as the `-` is inserted automatically.
94 Single(&'a str),
95 /// An option that takes a key and a value, ex. `-t 10`.
96 ///
97 /// `-t 10` would be represented as `KeyValue("t", "10")`, as
98 /// the `-` is inserted automatically.
99 KeyValue(&'a str, &'a str),
100}
101
102impl<'a> FfmpegBuilder<'a> {
103 /// Gets a [FfmpegBuilder] with nothing set
104 pub fn new() -> FfmpegBuilder<'a> {
105 FfmpegBuilder {
106 options: Vec::new(),
107 inputs: Vec::new(),
108 outputs: Vec::new(),
109 ffmpeg_command: "ffmpeg",
110 stdin: Stdio::null(),
111 stdout: Stdio::null(),
112 stderr: Stdio::null(),
113 }
114 }
115
116 /// Adds an option.
117 pub fn option(mut self, option: Parameter<'a>) -> Self {
118 self.options.push(option);
119
120 self
121 }
122
123 /// Adds an input.
124 pub fn input(mut self, input: File<'a>) -> Self {
125 self.inputs.push(input);
126
127 self
128 }
129
130 /// Adds an output.
131 pub fn output(mut self, output: File<'a>) -> Self {
132 self.outputs.push(output);
133
134 self
135 }
136
137 /// Sets stdin.
138 pub fn stdin(mut self, stdin: Stdio) -> Self {
139 self.stdin = stdin;
140
141 self
142 }
143
144 /// Sets stdout.
145 pub fn stdout(mut self, stdout: Stdio) -> Self {
146 self.stdout = stdout;
147
148 self
149 }
150
151 /// Sets stderr.
152 pub fn stderr(mut self, stderr: Stdio) -> Self {
153 self.stderr = stderr;
154
155 self
156 }
157
158 /// Turns it into a command, consuming the builder.
159 ///
160 /// This has to consume the builder for stdin, etc to work
161 /// Note that usually you want to use [`Self::run()`], not call this directly
162 pub fn to_command(self) -> Command {
163 let mut command = Command::new(self.ffmpeg_command);
164
165 for option in self.options {
166 option.push_to(&mut command);
167 }
168 for input in self.inputs {
169 input.push_to(&mut command, true);
170 }
171 for output in self.outputs {
172 output.push_to(&mut command, false)
173 }
174
175 command.stdin(self.stdin);
176 command.stdout(self.stdout);
177 command.stderr(self.stderr);
178
179 command
180 }
181}
182
183impl<'a> File<'a> {
184 /// Gets a file without any options set.
185 pub fn new(url: &'a str) -> File {
186 File {
187 url,
188 options: Vec::new(),
189 }
190 }
191
192 /// Adds an option.
193 pub fn option(mut self, option: Parameter<'a>) -> Self {
194 self.options.push(option);
195
196 self
197 }
198
199 fn push_to(&self, command: &mut Command, input: bool) {
200 for option in &self.options {
201 option.push_to(command);
202 }
203
204 if input {
205 command.arg("-i");
206 }
207 command.arg(&self.url);
208 }
209}
210
211impl<'a> Parameter<'a> {
212 fn push_to(&self, command: &mut Command) {
213 match &self {
214 Parameter::Single(arg) => command.arg("-".to_owned() + arg),
215 Parameter::KeyValue(key, value) => {
216 command.arg("-".to_owned() + key);
217 command.arg(value)
218 }
219 };
220 }
221}