ffmpeg_sidecar/
child.rs

1//! Wrapper around `std::process::Child` containing a spawned FFmpeg command.
2
3use crate::iter::FfmpegIterator;
4use anyhow::Context;
5use std::{
6  io::{self, copy, sink, Write},
7  process::{Child, ChildStderr, ChildStdin, ChildStdout, ExitStatus},
8};
9
10/// A wrapper around [`std::process::Child`] containing a spawned FFmpeg command.
11/// Provides interfaces for reading parsed metadata, progress updates, warnings and errors, and
12/// piped output frames if applicable.
13pub struct FfmpegChild {
14  inner: Child,
15}
16
17impl FfmpegChild {
18  /// Creates an iterator over events emitted by FFmpeg. Functions similarly to
19  /// `Lines` from [`std::io::BufReader`], but providing a variety of parsed
20  /// events:
21  /// - Log messages
22  /// - Parsed metadata
23  /// - Progress updates
24  /// - Errors and warnings
25  /// - Raw output frames
26  pub fn iter(&mut self) -> anyhow::Result<FfmpegIterator> {
27    FfmpegIterator::new(self)
28  }
29
30  /// Escape hatch to manually control the process' stdout channel.
31  /// Calling this method takes ownership of the stdout channel, so
32  /// the iterator will no longer include output frames in the stream of events.
33  pub fn take_stdout(&mut self) -> Option<ChildStdout> {
34    self.inner.stdout.take()
35  }
36
37  /// Escape hatch to manually control the process' stderr channel.
38  /// This method is mutually exclusive with `events_iter`, which relies on
39  /// the stderr channel to parse events.
40  pub fn take_stderr(&mut self) -> Option<ChildStderr> {
41    self.inner.stderr.take()
42  }
43
44  /// Escape hatch to manually control the process' stdin channel.
45  /// This method is mutually exclusive with `send_stdin_command` and `quit`,
46  /// which use the stdin channel to send commands to ffmpeg.
47  pub fn take_stdin(&mut self) -> Option<ChildStdin> {
48    self.inner.stdin.take()
49  }
50
51  /// Send a command to ffmpeg over stdin, used during interactive mode.
52  ///
53  /// This method does not validate that the command is expected or handled
54  /// correctly by ffmpeg. The returned `io::Result` indicates only whether the
55  /// command was successfully sent or not.
56  ///
57  /// In a typical ffmpeg build, these are the supported commands:
58  ///
59  /// ```txt
60  /// ?      show this help
61  /// +      increase verbosity
62  /// -      decrease verbosity
63  /// c      Send command to first matching filter supporting it
64  /// C      Send/Queue command to all matching filters
65  /// D      cycle through available debug modes
66  /// h      dump packets/hex press to cycle through the 3 states
67  /// q      quit
68  /// s      Show QP histogram
69  /// ```
70  pub fn send_stdin_command(&mut self, command: &[u8]) -> anyhow::Result<()> {
71    let mut stdin = self.inner.stdin.take().context("Missing child stdin")?;
72    stdin.write_all(command)?;
73    self.inner.stdin.replace(stdin);
74    Ok(())
75  }
76
77  /// Send a `q` command to ffmpeg over stdin,
78  /// requesting a graceful shutdown as soon as possible.
79  ///
80  /// This method returns after the command has been sent; the actual shut down
81  /// may take a few more frames as ffmpeg flushes its buffers and writes the
82  /// trailer, if applicable.
83  pub fn quit(&mut self) -> anyhow::Result<()> {
84    self.send_stdin_command(b"q")
85  }
86
87  /// Forcibly terminate the inner child process.
88  ///
89  /// Alternatively, you may choose to gracefully stop the child process by
90  /// sending a command over stdin, using the `quit` method.
91  ///
92  /// Identical to `kill` in [`std::process::Child`].
93  pub fn kill(&mut self) -> io::Result<()> {
94    self.inner.kill()
95  }
96
97  /// Waits for the inner child process to finish execution.
98  ///
99  /// Identical to `wait` in [`std::process::Child`].
100  pub fn wait(&mut self) -> io::Result<ExitStatus> {
101    // If stderr hasn't already been consumed by a method like `iter()`,
102    // we need to run it to completion to avoid a deadlock.
103    if let Some(mut stderr) = self.take_stderr() {
104      copy(&mut stderr, &mut sink())?;
105    };
106
107    self.inner.wait()
108  }
109
110  /// Wrap a [`std::process::Child`] in a `FfmpegChild`. Should typically only
111  /// be called by `FfmpegCommand::spawn`.
112  ///
113  /// ## Panics
114  ///
115  /// Panics if the any of the child process's stdio channels were not piped.
116  /// This could be because ffmpeg was spawned with `-nostdin`, or if the
117  /// `Child` instance was not configured with `stdin(Stdio::piped())`.
118  pub(crate) fn from_inner(inner: Child) -> Self {
119    assert!(inner.stdin.is_some(), "stdin was not piped");
120    assert!(inner.stdout.is_some(), "stdout was not piped");
121    assert!(inner.stderr.is_some(), "stderr was not piped");
122    Self { inner }
123  }
124
125  /// Escape hatch to access the inner `Child`.
126  pub fn as_inner(&mut self) -> &Child {
127    &self.inner
128  }
129
130  /// Escape hatch to mutably access the inner `Child`.
131  pub fn as_inner_mut(&mut self) -> &mut Child {
132    &mut self.inner
133  }
134}