1use std::ffi::OsStr;
4use std::io::{BufRead, BufReader};
5use std::path::Path;
6use std::process::{Command, Stdio};
7
8use crate::progress::{self, Progress};
9use crate::FfmpegError;
10
11pub fn run_pass<S: AsRef<OsStr>>(
17 ffmpeg: &Path,
18 args: &[S],
19 total_secs: f64,
20 on_progress: &mut dyn FnMut(f64),
21) -> Result<(), FfmpegError> {
22 let mut child = Command::new(ffmpeg)
23 .args(args)
24 .stdin(Stdio::null())
25 .stdout(Stdio::piped())
26 .stderr(Stdio::piped())
27 .spawn()
28 .map_err(|source| FfmpegError::Spawn {
29 tool: "ffmpeg",
30 source,
31 })?;
32
33 let stderr = child.stderr.take();
36 let stderr_handle = std::thread::spawn(move || {
37 let mut buf = String::new();
38 if let Some(stderr) = stderr {
39 use std::io::Read;
40 let _ = BufReader::new(stderr).read_to_string(&mut buf);
41 }
42 buf
43 });
44
45 if let Some(stdout) = child.stdout.take() {
46 for line in BufReader::new(stdout).lines().map_while(Result::ok) {
47 match progress::parse_line(&line) {
48 Some(Progress::OutTimeUs(us)) => on_progress(progress::fraction(us, total_secs)),
49 Some(Progress::End) => on_progress(1.0),
50 _ => {}
51 }
52 }
53 }
54
55 let status = child.wait().map_err(|source| FfmpegError::Spawn {
56 tool: "ffmpeg",
57 source,
58 })?;
59 let stderr = stderr_handle.join().unwrap_or_default();
60
61 if !status.success() {
62 return Err(FfmpegError::CommandFailed {
63 tool: "ffmpeg",
64 status: status.to_string(),
65 stderr: stderr.trim().to_string(),
66 });
67 }
68 Ok(())
69}