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}