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}