Skip to main content

pfx_run/
ffmpeg.rs

1use sha2::{Digest, Sha256};
2use std::io::Read;
3use std::path::{Path, PathBuf};
4use std::process::Command;
5
6#[derive(Clone, Debug)]
7pub struct Ffmpeg {
8    pub path: PathBuf,
9    pub version: String,
10    pub sha256: String,
11}
12
13impl Ffmpeg {
14    pub fn record(&self) -> serde_json::Value {
15        serde_json::json!({
16            "path": self.path.display().to_string(),
17            "version": self.version,
18            "sha256": self.sha256,
19        })
20    }
21}
22
23pub fn cache() -> Result<PathBuf, String> {
24    let dir = std::env::var_os("PFX_CACHE")
25        .map(PathBuf::from)
26        .or_else(|| {
27            std::env::var_os("XDG_CACHE_HOME")
28                .map(PathBuf::from)
29                .filter(|p| p.is_absolute())
30                .map(|p| p.join("pito-hd-renderer"))
31        })
32        .or_else(|| {
33            std::env::var_os("HOME")
34                .map(|h| PathBuf::from(h).join(".cache").join("pito-hd-renderer"))
35        })
36        .ok_or("no cache folder: set PFX_CACHE")?;
37    std::fs::create_dir_all(&dir).map_err(|e| format!("{}: {e}", dir.display()))?;
38    Ok(dir)
39}
40
41fn hash_file(path: &Path) -> Result<String, String> {
42    let mut file = std::fs::File::open(path).map_err(|e| format!("{}: {e}", path.display()))?;
43    let mut hasher = Sha256::new();
44    let mut buf = vec![0u8; 1 << 20];
45    loop {
46        let n = file
47            .read(&mut buf)
48            .map_err(|e| format!("{}: {e}", path.display()))?;
49        if n == 0 {
50            break;
51        }
52        hasher.update(&buf[..n]);
53    }
54    Ok(hex::encode(hasher.finalize()))
55}
56
57fn on_path(exe: &str) -> Option<PathBuf> {
58    std::env::split_paths(&std::env::var_os("PATH")?)
59        .map(|dir| dir.join(exe))
60        .find(|p| p.is_file())
61}
62
63fn version_of(text: &str) -> Option<String> {
64    text.lines()
65        .next()?
66        .strip_prefix("ffmpeg version ")?
67        .split_whitespace()
68        .next()
69        .map(String::from)
70}
71
72pub fn system() -> Result<Ffmpeg, String> {
73    let exe = if cfg!(windows) {
74        "ffmpeg.exe"
75    } else {
76        "ffmpeg"
77    };
78    let path = on_path(exe).ok_or_else(|| {
79        format!("no {exe} on PATH: install the system's ffmpeg, with libx264 and zscale")
80    })?;
81    let path = path.canonicalize().unwrap_or(path);
82    let out = Command::new(&path)
83        .arg("-version")
84        .output()
85        .map_err(|e| format!("{}: {e}", path.display()))?;
86    if !out.status.success() {
87        return Err(format!("{} -version failed", path.display()));
88    }
89    let version = version_of(&String::from_utf8_lossy(&out.stdout))
90        .ok_or_else(|| format!("{} -version printed no version", path.display()))?;
91    let sha256 = hash_file(&path)?;
92    Ok(Ffmpeg {
93        path,
94        version,
95        sha256,
96    })
97}