Skip to main content

bc_mur/
prores.rs

1use std::{path::Path, process::Command};
2
3use crate::{Error, QrFrame, Result};
4
5/// Encode frames to ProRes 4444 via ffmpeg subprocess.
6///
7/// Writes frames as temporary PNGs, invokes ffmpeg, and
8/// cleans up the temp directory. Requires `ffmpeg` on PATH.
9pub fn encode_prores(
10    frames: &[QrFrame],
11    fps: f64,
12    output_path: &Path,
13) -> Result<()> {
14    // Check for ffmpeg
15    let ffmpeg = which_ffmpeg()?;
16
17    // Write frames to temp directory
18    let tmp_dir = tempfile::tempdir()?;
19    crate::write_frame_pngs(frames, tmp_dir.path())?;
20
21    let fps_str = format!("{fps}");
22    let input_pattern = tmp_dir.path().join("%04d.png");
23
24    let status = Command::new(&ffmpeg)
25        .args([
26            "-y",
27            "-r",
28            &fps_str,
29            "-i",
30            input_pattern.to_str().unwrap(),
31            "-c:v",
32            "prores_ks",
33            "-profile:v",
34            "4444",
35            "-pix_fmt",
36            "yuva444p10le",
37            output_path.to_str().unwrap(),
38        ])
39        .stdout(std::process::Stdio::null())
40        .stderr(std::process::Stdio::piped())
41        .status()?;
42
43    if !status.success() {
44        return Err(Error::FfmpegFailed(format!(
45            "ffmpeg exited with status {status}"
46        )));
47    }
48
49    // tmp_dir auto-cleans up on drop
50    Ok(())
51}
52
53fn which_ffmpeg() -> Result<String> {
54    // Try `which ffmpeg` on Unix
55    let output = Command::new("which")
56        .arg("ffmpeg")
57        .output()
58        .map_err(|_| Error::FfmpegNotFound)?;
59
60    if output.status.success() {
61        let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
62        if !path.is_empty() {
63            return Ok(path);
64        }
65    }
66
67    Err(Error::FfmpegNotFound)
68}