summaryrefslogtreecommitdiffstats
path: root/src/ffmpeg.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/ffmpeg.rs')
-rw-r--r--src/ffmpeg.rs150
1 files changed, 150 insertions, 0 deletions
diff --git a/src/ffmpeg.rs b/src/ffmpeg.rs
new file mode 100644
index 0000000..fbfdc94
--- /dev/null
+++ b/src/ffmpeg.rs
@@ -0,0 +1,150 @@
+use std::process::Command;
+
+/// Options that ffmpeg supports but ffprobe does not.
+const FFMPEG_ONLY_OPTIONS: &[&str] = &["-stream_loop", "-t"];
+
+const ALPHA_FMTS: &[&str] = &[
+ "pal8",
+ "argb",
+ "rgba",
+ "abgr",
+ "bgra",
+ "yuva420p",
+ "ya8",
+ "yuva422p",
+ "yuva444p",
+ "yuva420p9be",
+ "yuva420p9le",
+ "yuva422p9be",
+ "yuva422p9le",
+ "yuva444p9be",
+ "yuva444p9le",
+ "yuva420p10be",
+ "yuva420p10le",
+ "yuva422p10be",
+ "yuva422p10le",
+ "yuva444p10be",
+ "yuva444p10le",
+ "yuva420p16be",
+ "yuva420p16le",
+ "yuva422p16be",
+ "yuva422p16le",
+ "yuva444p16be",
+ "yuva444p16le",
+ "rgba64be",
+ "rgba64le",
+ "bgra64be",
+ "bgra64le",
+ "ya16be",
+ "ya16le",
+ "gbrap",
+ "gbrap16be",
+ "gbrap16le",
+ "ayuv64le",
+ "ayuv64be",
+ "gbrap12be",
+ "gbrap12le",
+ "gbrap10be",
+ "gbrap10le",
+ "gbrapf32be",
+ "gbrapf32le",
+ "yuva422p12be",
+ "yuva422p12le",
+ "yuva444p12be",
+ "yuva444p12le",
+ "vuya",
+ "rgbaf16be",
+ "rgbaf16le",
+ "rgbaf32be",
+ "rgbaf32le",
+ "gbrap14be",
+ "gbrap14le",
+ "ayuv",
+ "uyva",
+ "rgba128be",
+ "rgba128le",
+ "gbrapf16be",
+ "gbrapf16le",
+ "yaf32be",
+ "yaf32le",
+ "yaf16be",
+ "yaf16le",
+ "gbrap32be",
+ "gbrap32le",
+];
+
+fn filter_probe_args(ff_args: &[String]) -> Vec<String> {
+ let mut out = Vec::new();
+ let mut skip_next = false;
+ for arg in ff_args {
+ if skip_next {
+ skip_next = false;
+ continue;
+ }
+ if FFMPEG_ONLY_OPTIONS.contains(&arg.as_str()) {
+ skip_next = true;
+ continue;
+ }
+ out.push(arg.clone());
+ }
+ out
+}
+
+pub struct VideoInfo {
+ pub width: u32,
+ pub height: u32,
+ pub num_frames: Option<u32>,
+ pub fps: f64,
+ pub alpha: bool,
+ pub codec_tag: String,
+}
+
+pub fn probe_video(ff_args: &[String], max_frames: Option<u32>) -> VideoInfo {
+ let probe_args = filter_probe_args(ff_args);
+ let output = Command::new("ffprobe")
+ .args(["-v", "error"])
+ .args(["-select_streams", "v:0"])
+ .args([
+ "-show_entries",
+ "stream=codec_tag_string,width,height,pix_fmt,r_frame_rate,nb_frames",
+ ])
+ .args(["-of", "csv=p=0"])
+ .args(&probe_args)
+ .output()
+ .expect("failed to run ffprobe");
+
+ if !output.status.success() {
+ let stderr = String::from_utf8_lossy(&output.stderr);
+ panic!("ffprobe failed: {stderr}");
+ }
+
+ let stdout = String::from_utf8_lossy(&output.stdout);
+ let line = stdout.trim();
+ let parts: Vec<&str> = line.split(',').collect();
+ if parts.len() != 6 {
+ panic!("unexpected ffprobe output: {line}");
+ }
+
+ let codec_tag = parts[0].to_string();
+ let width: u32 = parts[1].parse().expect("parse width");
+ let height: u32 = parts[2].parse().expect("parse height");
+ let alpha = ALPHA_FMTS.contains(&parts[3]);
+
+ let fps: f64 = if let Some((num, den)) = parts[4].split_once('/') {
+ let n: f64 = num.parse().expect("parse fps numerator");
+ let d: f64 = den.parse().expect("parse fps denominator");
+ n / d
+ } else {
+ parts[4].parse().expect("parse fps")
+ };
+ let num_frames = parts[5].parse().ok().or(max_frames);
+
+ VideoInfo {
+ width,
+ height,
+ num_frames,
+ fps,
+ alpha,
+ codec_tag,
+ }
+}