ffmpeg_sidecar/
child.rs

1
2
3
4
5
6
7
8
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
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
//! Wrapper around `std::process::Child` containing a spawned FFmpeg command.

use std::{
  io::{self, Write},
  process::{Child, ChildStderr, ChildStdin, ChildStdout, ExitStatus},
};

use anyhow::Context;

use crate::iter::FfmpegIterator;

/// 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.
pub struct FfmpegChild {
  inner: Child,
}

impl FfmpegChild {
  /// 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
  pub fn iter(&mut self) -> anyhow::Result<FfmpegIterator> {
    FfmpegIterator::new(self)
  }

  /// 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.
  pub fn take_stdout(&mut self) -> Option<ChildStdout> {
    self.inner.stdout.take()
  }

  /// 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.
  pub fn take_stderr(&mut self) -> Option<ChildStderr> {
    self.inner.stderr.take()
  }

  /// 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.
  pub fn take_stdin(&mut self) -> Option<ChildStdin> {
    self.inner.stdin.take()
  }

  /// 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:
  ///
  /// ```txt
  /// ?      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
  /// ```
  pub fn send_stdin_command(&mut self, command: &[u8]) -> anyhow::Result<()> {
    let mut stdin = self.inner.stdin.take().context("Missing child stdin")?;
    stdin.write_all(command)?;
    self.inner.stdin.replace(stdin);
    Ok(())
  }

  /// 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.
  pub fn quit(&mut self) -> anyhow::Result<()> {
    self.send_stdin_command(b"q")
  }

  /// 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`].
  pub fn kill(&mut self) -> io::Result<()> {
    self.inner.kill()
  }

  /// Waits for the inner child process to finish execution.
  ///
  /// Identical to `wait` in [`std::process::Child`].
  pub fn wait(&mut self) -> io::Result<ExitStatus> {
    self.inner.wait()
  }

  /// Wrap a [`std::process::Child`] in a `FfmpegChild`. Should typically only
  /// be called by `FfmpegCommand::spawn`.
  ///
  /// ## Panics
  ///
  /// Panics if the any of the child process's stdio channels were not piped.
  /// This could be because ffmpeg was spawned with `-nostdin`, or if the
  /// `Child` instance was not configured with `stdin(Stdio::piped())`.
  pub(crate) fn from_inner(inner: Child) -> Self {
    assert!(inner.stdin.is_some(), "stdin was not piped");
    assert!(inner.stdout.is_some(), "stdout was not piped");
    assert!(inner.stderr.is_some(), "stderr was not piped");
    Self { inner }
  }

  /// Escape hatch to access the inner `Child`.
  pub fn as_inner(&mut self) -> &Child {
    &self.inner
  }

  /// Escape hatch to mutably access the inner `Child`.
  pub fn as_inner_mut(&mut self) -> &mut Child {
    &mut self.inner
  }
}