ffmpeg_sidecar::child

Struct FfmpegChild

Source
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

Source

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?
examples/progress.rs (line 21)
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
Hide additional examples
examples/metadata.rs (line 15)
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}"),
        _ => {}
      }
    });
}
examples/hello_world.rs (line 14)
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(())
}
examples/sockets.rs (line 50)
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(())
}
examples/h265_transcode.rs (line 32)
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);
}
examples/named_pipes.rs (line 139)
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
fn main() -> anyhow::Result<()> {
  use anyhow::Result;
  use ffmpeg_sidecar::command::FfmpegCommand;
  use ffmpeg_sidecar::event::{FfmpegEvent, LogLevel};
  use ffmpeg_sidecar::named_pipes::NamedPipe;
  use ffmpeg_sidecar::pipe_name;
  use std::io::Read;
  use std::sync::mpsc;
  use std::thread;

  const VIDEO_PIPE_NAME: &str = pipe_name!("ffmpeg_video");
  const AUDIO_PIPE_NAME: &str = pipe_name!("ffmpeg_audio");
  const SUBTITLES_PIPE_NAME: &str = pipe_name!("ffmpeg_subtitles");

  // Prepare an FFmpeg command with separate outputs for video, audio, and subtitles.
  let mut command = FfmpegCommand::new();
  command
    // 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(VIDEO_PIPE_NAME)
    // Audio output
    .map("1:a")
    .format("s16le")
    .output(AUDIO_PIPE_NAME)
    // Subtitles output
    .map("2:s")
    .format("srt")
    .output(SUBTITLES_PIPE_NAME);

  // Create a separate thread for each output pipe
  let threads = [VIDEO_PIPE_NAME, AUDIO_PIPE_NAME, SUBTITLES_PIPE_NAME]
    .iter()
    .cloned()
    .map(|pipe_name| {
      // It's important to create the named pipe on the main thread before
      // sending it elsewhere so that any errors are caught at the top level.
      let mut pipe = NamedPipe::new(pipe_name)?;
      println!("[{pipe_name}] pipe created");
      let (ready_sender, ready_receiver) = mpsc::channel::<()>();
      let thread = thread::spawn(move || -> Result<()> {
        // Wait for FFmpeg to start writing
        // Only needed for Windows, since Unix will block until a writer has connected
        println!("[{pipe_name}] waiting for ready signal");
        ready_receiver.recv()?;

        // Read continuously until finished
        // Note that if the stream of output is interrupted or paused,
        // you may need additional logic to keep the read loop alive.
        println!("[{pipe_name}] reading from pipe");
        let mut buf = vec![0; 1920 * 1080 * 3];
        let mut total_bytes_read = 0;

        // In the case of subtitles, we'll decode the string contents directly
        let mut text_content = if pipe_name == SUBTITLES_PIPE_NAME {
          Some("".to_string())
        } else {
          None
        };

        loop {
          match pipe.read(&mut buf) {
            Ok(bytes_read) => {
              total_bytes_read += bytes_read;

              // read bytes into string
              if let Some(cur_str) = &mut text_content {
                let s = std::str::from_utf8(&buf[..bytes_read]).unwrap();
                text_content = Some(format!("{}{}", cur_str, s));
              }

              if bytes_read == 0 {
                break;
              }
            }
            Err(err) => {
              if err.kind() != std::io::ErrorKind::BrokenPipe {
                return Err(err.into());
              } else {
                break;
              }
            }
          }
        }

        // Log how many bytes were received over this pipe.
        // You can visually compare this to the FFmpeg log output to confirm
        // that all the expected bytes were captured.
        let size_str = if total_bytes_read < 1024 {
          format!("{}B", total_bytes_read)
        } else {
          format!("{}KiB", total_bytes_read / 1024)
        };

        if let Some(text_content) = text_content {
          println!("[{pipe_name}] subtitle text content: ");
          println!("{}", text_content.trim());
        }

        println!("[{pipe_name}] done reading ({size_str} total)");
        Ok(())
      });

      Ok((thread, ready_sender))
    })
    .collect::<Result<Vec<_>>>()?;

  // Start FFmpeg
  let mut ready_signal_sent = false;
  command
    .print_command()
    .spawn()?
    .iter()?
    .for_each(|event| match event {
      // Signal threads when output is ready
      FfmpegEvent::Progress(_) if !ready_signal_sent => {
        threads.iter().for_each(|(_, sender)| {
          sender.send(()).ok();
        });
        ready_signal_sent = true;
      }

      // 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}");
      }

      _ => {}
    });

  for (thread, _) in threads {
    thread.join().unwrap()?;
  }

  Ok(())
}
Source

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?
examples/ffplay_preview.rs (line 30)
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();
  }
}
Source

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.

Source

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?
examples/h265_transcode.rs (line 54)
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),
    _ => {}
  });
}
Source

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
Source

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.

Source

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.

Source

pub fn wait(&mut self) -> Result<ExitStatus>

Waits for the inner child process to finish execution.

Identical to wait in std::process::Child.

Source

pub fn as_inner(&mut self) -> &Child

Escape hatch to access the inner Child.

Source

pub fn as_inner_mut(&mut self) -> &mut Child

Escape hatch to mutably access the inner Child.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> MaybeSendSync for T