Skip to main content

mcraw_tui/
hardware.rs

1use std::process::Command;
2
3/// Runtime-detected hardware capabilities for video encoding.
4#[derive(Debug)]
5pub struct HardwareCaps {
6    /// Best available HEVC encoder name
7    pub best_hevc_encoder: String,
8    /// True if `best_hevc_encoder` is a hardware accelerator
9    pub hevc_is_hw: bool,
10
11    /// Best available H.264 encoder name
12    pub best_h264_encoder: String,
13    /// True if `best_h264_encoder` is a hardware accelerator
14    pub h264_is_hw: bool,
15
16    /// Best available AV1 encoder name
17    pub best_av1_encoder: String,
18    /// True if `best_av1_encoder` is a hardware accelerator
19    pub av1_is_hw: bool,
20
21    /// Best available ProRes encoder name
22    pub best_prores_encoder: String,
23    /// True if `best_prores_encoder` is a hardware accelerator
24    pub prores_is_hw: bool,
25}
26
27/// Silently probe available FFmpeg HW encoders in priority order.
28/// Falls back to the matching software encoder if none is found
29/// or ffmpeg is missing.
30pub fn probe_hardware() -> HardwareCaps {
31    let output = match Command::new("ffmpeg").arg("-encoders").output() {
32        Ok(o) => {
33            // FFmpeg may print encoder info to stdout or stderr depending on build
34            let stdout = String::from_utf8_lossy(&o.stdout);
35            let stderr = String::from_utf8_lossy(&o.stderr);
36            format!("{}{}", stdout, stderr).into_bytes()
37        }
38        Err(e) => {
39            tracing::warn!("ffmpeg not found or failed to probe encoders: {}", e);
40            return HardwareCaps {
41                best_hevc_encoder: "libx265".to_string(),
42                hevc_is_hw: false,
43                best_h264_encoder: "libx264".to_string(),
44                h264_is_hw: false,
45                best_av1_encoder: "libaom-av1".to_string(),
46                av1_is_hw: false,
47                best_prores_encoder: "prores_ks".to_string(),
48                prores_is_hw: false,
49            };
50        }
51    };
52    let stdout = String::from_utf8_lossy(&output);
53
54    let (hevc_enc, hevc_hw) = probe_one(
55        &stdout,
56        &["hevc_nvenc", "hevc_amf", "hevc_qsv", "hevc_videotoolbox"],
57        "libx265",
58    );
59
60    let (h264_enc, h264_hw) = probe_one(
61        &stdout,
62        &["h264_nvenc", "h264_amf", "h264_qsv", "h264_videotoolbox"],
63        "libx264",
64    );
65
66    let (av1_enc, av1_hw) = probe_one(
67        &stdout,
68        &["av1_nvenc", "av1_amf", "av1_qsv", "libsvtav1"],
69        "libaom-av1",
70    );
71
72    let (prores_enc, prores_hw) = probe_one(
73        &stdout,
74        &["prores_videotoolbox"],
75        "prores_ks",
76    );
77
78    tracing::info!(
79        "Encoder detection: HEVC={} (hw={}), H264={} (hw={}), AV1={} (hw={}), ProRes={} (hw={})",
80        hevc_enc, hevc_hw, h264_enc, h264_hw, av1_enc, av1_hw, prores_enc, prores_hw
81    );
82
83    HardwareCaps {
84        best_hevc_encoder: hevc_enc,
85        hevc_is_hw: hevc_hw,
86        best_h264_encoder: h264_enc,
87        h264_is_hw: h264_hw,
88        best_av1_encoder: av1_enc,
89        av1_is_hw: av1_hw,
90        best_prores_encoder: prores_enc,
91        prores_is_hw: prores_hw,
92    }
93}
94
95/// Walk `priority` in order; return the first encoder name found in `ffmpeg_output`.
96/// If none match, return `fallback`. An encoder is considered hardware when its
97/// name does **not** start with `"lib"`.
98fn probe_one(ffmpeg_output: &str, priority: &[&str], fallback: &str) -> (String, bool) {
99    for name in priority {
100        if ffmpeg_output.contains(name) {
101            let is_hw = !name.starts_with("lib");
102            return (name.to_string(), is_hw);
103        }
104    }
105    (fallback.to_string(), false)
106}