Skip to main content

deepshrink_ffmpeg/
caps.rs

1//! What the local ffmpeg build can actually do.
2//!
3//! ffmpeg is an external dependency we don't control: the same version can ship
4//! with or without a given encoder (AV1 in particular). Callers gate on these
5//! probes and either fall back or fail with a clear message, instead of letting
6//! ffmpeg reject the argv with a wall of text.
7
8use std::path::Path;
9use std::process::Command;
10
11/// Whether this ffmpeg build exposes an encoder by name (e.g. `libsvtav1`).
12///
13/// Matches on the encoder-name column of `ffmpeg -encoders`, so a name that only
14/// appears inside a description doesn't count as a match.
15pub fn has_encoder(ffmpeg: &Path, name: &str) -> bool {
16    Command::new(ffmpeg)
17        .args(["-hide_banner", "-encoders"])
18        .output()
19        .map(|o| {
20            let text = String::from_utf8_lossy(&o.stdout);
21            encoder_listed(&text, name)
22        })
23        .unwrap_or(false)
24}
25
26/// Parse the `ffmpeg -encoders` table for an exact encoder name. Split out from
27/// the process call so the parsing is testable without ffmpeg.
28pub(crate) fn encoder_listed(listing: &str, name: &str) -> bool {
29    listing.lines().any(|line| {
30        // " V..... libsvtav1            SVT-AV1 … encoder (codec av1)"
31        let mut cols = line.split_whitespace();
32        let flags = cols.next().unwrap_or("");
33        // The flag column is a fixed-width capability mask, never a word.
34        flags.len() == 6 && cols.next() == Some(name)
35    })
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    const LISTING: &str = "Encoders:
43 V..... libx264              libx264 H.264 / AVC (codec h264)
44 V..... libsvtav1            SVT-AV1 encoder (codec av1)
45 A....D aac                  AAC (Advanced Audio Coding)
46";
47
48    #[test]
49    fn finds_a_listed_encoder() {
50        assert!(encoder_listed(LISTING, "libsvtav1"));
51        assert!(encoder_listed(LISTING, "libx264"));
52        assert!(encoder_listed(LISTING, "aac"));
53    }
54
55    #[test]
56    fn rejects_absent_and_description_only_matches() {
57        assert!(!encoder_listed(LISTING, "libaom-av1"));
58        // "AV1" and "codec av1" appear in the description column only.
59        assert!(!encoder_listed(LISTING, "av1"));
60        assert!(!encoder_listed(LISTING, "Encoders:"));
61    }
62}