pub struct FfmpegChild { /* private fields */ }
Expand description
A wrapper around std::process::Child
containing a spawned FFmpeg command.
Provides interfaces for reading parsed metadata, progress updates, warnings and errors, and
piped output frames if applicable.
Implementations§
Source§impl FfmpegChild
impl FfmpegChild
Sourcepub fn iter(&mut self) -> Result<FfmpegIterator>
pub fn iter(&mut self) -> Result<FfmpegIterator>
Creates an iterator over events emitted by FFmpeg. Functions similarly to
Lines
from std::io::BufReader
, but providing a variety of parsed
events:
- Log messages
- Parsed metadata
- Progress updates
- Errors and warnings
- Raw output frames
Examples found in repository?
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
fn main() {
let fps = 60;
let duration = 10;
let total_frames = fps * duration;
let arg_string = format!(
"-f lavfi -i testsrc=duration={}:size=1920x1080:rate={} -y output/test.mp4",
duration, fps
);
FfmpegCommand::new()
.args(arg_string.split(' '))
.spawn()
.unwrap()
.iter()
.unwrap()
.filter_progress()
.for_each(|progress| println!("{}%", (progress.frame * 100) / total_frames));
}
More examples
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
fn main() {
let mut ffmpeg_runner = FfmpegCommand::new()
.testsrc()
.args(["-metadata", "title=some cool title"])
.overwrite() // -y
.output("output/metadata.mp4")
.print_command()
.spawn()
.unwrap();
ffmpeg_runner
.iter()
.unwrap()
.for_each(|e| {
match e {
FfmpegEvent::Progress(FfmpegProgress { frame, .. }) =>
println!("Current frame: {frame}"),
FfmpegEvent::Log(_level, msg) =>
println!("[ffmpeg] {msg}"),
_ => {}
}
});
}
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
fn main() -> anyhow::Result<()> {
// Run an FFmpeg command that generates a test video
let iter = FfmpegCommand::new() // <- Builder API like `std::process::Command`
.testsrc() // <- Discoverable aliases for FFmpeg args
.rawvideo() // <- Convenient argument presets
.spawn()? // <- Ordinary `std::process::Child`
.iter()?; // <- Blocking iterator over logs and output
// Use a regular "for" loop to read decoded video data
for frame in iter.filter_frames() {
println!("frame: {}x{}", frame.width, frame.height);
let _pixels: Vec<u8> = frame.data; // <- raw RGB pixels! 🎨
}
Ok(())
}
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
fn main() -> Result<()> {
let iter = FfmpegCommand::new()
.format("lavfi")
.arg("-re") // "realtime"
.input(format!(
"testsrc=size={OUTPUT_WIDTH}x{OUTPUT_HEIGHT}:rate={OUTPUT_FRAMERATE}"
))
.rawvideo()
.spawn()?
.iter()?
.filter_frames();
for frame in iter {
// clear the previous frame
if frame.frame_num > 0 {
for _ in 0..frame.height {
print!("\x1B[{}A", 1);
}
}
// Print the pixels colored with ANSI codes
for y in 0..frame.height {
for x in 0..frame.width {
let idx = (y * frame.width + x) as usize * 3;
let r = frame.data[idx] as u32;
let g = frame.data[idx + 1] as u32;
let b = frame.data[idx + 2] as u32;
print!("\x1B[48;2;{r};{g};{b}m ");
}
println!("\x1B[0m");
}
}
Ok(())
}
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
fn main() -> Result<()> {
// Set up a TCP listener
const TCP_PORT: u32 = 3000;
let (exit_sender, exit_receiver) = channel::<()>();
let listener_thread = thread::spawn(|| listen_for_connections(TCP_PORT, exit_receiver));
// Wait for the listener to start
thread::sleep(Duration::from_millis(1000));
// Prepare an FFmpeg command with separate outputs for video, audio, and subtitles.
FfmpegCommand::new()
// Global flags
.hide_banner()
.overwrite() // <- overwrite required on windows
// Generate test video
.format("lavfi")
.input("testsrc=size=1920x1080:rate=60:duration=10")
// Generate test audio
.format("lavfi")
.input("sine=frequency=1000:duration=10")
// Generate test subtitles
.format("srt")
.input(
"data:text/plain;base64,MQ0KMDA6MDA6MDAsMDAwIC0tPiAwMDowMDoxMCw1MDANCkhlbGxvIFdvcmxkIQ==",
)
// Video output
.map("0:v")
.format("rawvideo")
.pix_fmt("rgb24")
.output(format!("tcp://127.0.0.1:{TCP_PORT}"))
// Audio output
.map("1:a")
.format("s16le")
.output(format!("tcp://127.0.0.1:{TCP_PORT}"))
// Subtitles output
.map("2:s")
.format("srt")
.output(format!("tcp://127.0.0.1:{TCP_PORT}"))
.print_command()
.spawn()?
.iter()?
.for_each(|event| match event {
// Verify output size from FFmpeg logs (video/audio KiB)
FfmpegEvent::Log(LogLevel::Info, msg) if msg.starts_with("[out#") => {
println!("{msg}");
}
// Log any unexpected errors
FfmpegEvent::Log(LogLevel::Warning | LogLevel::Error | LogLevel::Fatal, msg) => {
eprintln!("{msg}");
}
// _ => {}
e => {
println!("{:?}", e);
}
});
exit_sender.send(())?;
listener_thread.join().unwrap()?;
Ok(())
}
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
fn main() {
// Create an H265 source video as a starting point
let input_path = "output/h265.mp4";
if !Path::new(input_path).exists() {
create_h265_source(input_path);
}
// One instance decodes H265 to raw frames
let mut input = FfmpegCommand::new()
.input(input_path)
.rawvideo()
.spawn()
.unwrap();
// Frames can be transformed by Iterator `.map()`.
// This example is a no-op, with frames passed through unaltered.
let transformed_frames = input.iter().unwrap().filter_frames();
// You could easily add some "middleware" processing here:
// - overlay or composite another RGB image (or even another Ffmpeg Iterator)
// - apply a filter like blur or convolution
// Note: some of these operations are also possible with FFmpeg's (somewhat arcane)
// `filtergraph` API, but doing it in Rust gives you much finer-grained
// control, debuggability, and modularity -- you can pull in any Rust crate
// you need.
// A second instance encodes the updated frames back to H265
let mut output = FfmpegCommand::new()
.args([
"-f", "rawvideo", "-pix_fmt", "rgb24", "-s", "600x800", "-r", "30",
]) // note: should be possible to infer these params from the source input stream
.input("-")
.args(["-c:v", "libx265"])
.args(["-y", "output/h265_overlay.mp4"])
.spawn()
.unwrap();
// Connect the two instances
let mut stdin = output.take_stdin().unwrap();
thread::spawn(move || {
// `for_each` blocks through the end of the iterator,
// so we run it in another thread.
transformed_frames.for_each(|f| {
stdin.write_all(&f.data).ok();
});
});
// On the main thread, run the output instance to completion
output.iter().unwrap().for_each(|e| match e {
FfmpegEvent::Log(LogLevel::Error, e) => println!("Error: {}", e),
FfmpegEvent::Progress(p) => println!("Progress: {} / 00:00:15", p.time),
_ => {}
});
}
/// Create a H265 source video from scratch
fn create_h265_source(path_str: &str) {
println!("Creating H265 source video: {}", path_str);
FfmpegCommand::new()
.args("-f lavfi -i testsrc=size=600x800:rate=30:duration=15 -c:v libx265".split(' '))
.arg(path_str)
.spawn()
.unwrap()
.iter()
.unwrap()
.for_each(|e| match e {
FfmpegEvent::Log(LogLevel::Error, e) => println!("Error: {}", e),
FfmpegEvent::Progress(p) => println!("Progress: {} / 00:00:15", p.time),
_ => {}
});
println!("Created H265 source video: {}", path_str);
}
Sourcepub fn take_stdout(&mut self) -> Option<ChildStdout>
pub fn take_stdout(&mut self) -> Option<ChildStdout>
Escape hatch to manually control the process’ stdout channel. Calling this method takes ownership of the stdout channel, so the iterator will no longer include output frames in the stream of events.
Examples found in repository?
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
fn main() {
let mut ffmpeg = FfmpegCommand::new()
.realtime()
.format("lavfi")
.input("testsrc=size=1920x1080:rate=60")
.codec_video("rawvideo")
.format("avi")
.output("-")
.spawn()
.unwrap();
let mut ffplay = Command::new("ffplay")
.args("-i -".split(' '))
.stdin(Stdio::piped())
.spawn()
.unwrap();
let mut ffmpeg_stdout = ffmpeg.take_stdout().unwrap();
let mut ffplay_stdin = ffplay.stdin.take().unwrap();
// pipe from ffmpeg stdout to ffplay stdin
let buf = &mut [0u8; 4096];
loop {
let n = ffmpeg_stdout.read(buf).unwrap();
if n == 0 {
break;
}
ffplay_stdin.write_all(&buf[..n]).unwrap();
}
}
Sourcepub fn take_stderr(&mut self) -> Option<ChildStderr>
pub fn take_stderr(&mut self) -> Option<ChildStderr>
Escape hatch to manually control the process’ stderr channel.
This method is mutually exclusive with events_iter
, which relies on
the stderr channel to parse events.
Sourcepub fn take_stdin(&mut self) -> Option<ChildStdin>
pub fn take_stdin(&mut self) -> Option<ChildStdin>
Escape hatch to manually control the process’ stdin channel.
This method is mutually exclusive with send_stdin_command
and quit
,
which use the stdin channel to send commands to ffmpeg.
Examples found in repository?
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
fn main() {
// Create an H265 source video as a starting point
let input_path = "output/h265.mp4";
if !Path::new(input_path).exists() {
create_h265_source(input_path);
}
// One instance decodes H265 to raw frames
let mut input = FfmpegCommand::new()
.input(input_path)
.rawvideo()
.spawn()
.unwrap();
// Frames can be transformed by Iterator `.map()`.
// This example is a no-op, with frames passed through unaltered.
let transformed_frames = input.iter().unwrap().filter_frames();
// You could easily add some "middleware" processing here:
// - overlay or composite another RGB image (or even another Ffmpeg Iterator)
// - apply a filter like blur or convolution
// Note: some of these operations are also possible with FFmpeg's (somewhat arcane)
// `filtergraph` API, but doing it in Rust gives you much finer-grained
// control, debuggability, and modularity -- you can pull in any Rust crate
// you need.
// A second instance encodes the updated frames back to H265
let mut output = FfmpegCommand::new()
.args([
"-f", "rawvideo", "-pix_fmt", "rgb24", "-s", "600x800", "-r", "30",
]) // note: should be possible to infer these params from the source input stream
.input("-")
.args(["-c:v", "libx265"])
.args(["-y", "output/h265_overlay.mp4"])
.spawn()
.unwrap();
// Connect the two instances
let mut stdin = output.take_stdin().unwrap();
thread::spawn(move || {
// `for_each` blocks through the end of the iterator,
// so we run it in another thread.
transformed_frames.for_each(|f| {
stdin.write_all(&f.data).ok();
});
});
// On the main thread, run the output instance to completion
output.iter().unwrap().for_each(|e| match e {
FfmpegEvent::Log(LogLevel::Error, e) => println!("Error: {}", e),
FfmpegEvent::Progress(p) => println!("Progress: {} / 00:00:15", p.time),
_ => {}
});
}
Sourcepub fn send_stdin_command(&mut self, command: &[u8]) -> Result<()>
pub fn send_stdin_command(&mut self, command: &[u8]) -> Result<()>
Send a command to ffmpeg over stdin, used during interactive mode.
This method does not validate that the command is expected or handled
correctly by ffmpeg. The returned io::Result
indicates only whether the
command was successfully sent or not.
In a typical ffmpeg build, these are the supported commands:
? show this help
+ increase verbosity
- decrease verbosity
c Send command to first matching filter supporting it
C Send/Queue command to all matching filters
D cycle through available debug modes
h dump packets/hex press to cycle through the 3 states
q quit
s Show QP histogram
Sourcepub fn quit(&mut self) -> Result<()>
pub fn quit(&mut self) -> Result<()>
Send a q
command to ffmpeg over stdin,
requesting a graceful shutdown as soon as possible.
This method returns after the command has been sent; the actual shut down may take a few more frames as ffmpeg flushes its buffers and writes the trailer, if applicable.
Sourcepub fn kill(&mut self) -> Result<()>
pub fn kill(&mut self) -> Result<()>
Forcibly terminate the inner child process.
Alternatively, you may choose to gracefully stop the child process by
sending a command over stdin, using the quit
method.
Identical to kill
in std::process::Child
.
Sourcepub fn wait(&mut self) -> Result<ExitStatus>
pub fn wait(&mut self) -> Result<ExitStatus>
Waits for the inner child process to finish execution.
Identical to wait
in std::process::Child
.
Sourcepub fn as_inner_mut(&mut self) -> &mut Child
pub fn as_inner_mut(&mut self) -> &mut Child
Escape hatch to mutably access the inner Child
.