1use std::{path::Path, process::Command};
2
3use crate::{Error, QrFrame, Result};
4
5pub fn encode_prores(
10 frames: &[QrFrame],
11 fps: f64,
12 output_path: &Path,
13) -> Result<()> {
14 let ffmpeg = which_ffmpeg()?;
16
17 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 Ok(())
51}
52
53fn which_ffmpeg() -> Result<String> {
54 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}