use std::process::Command;
use oxideav_core::stream::{CodecResolver, ProbeContext};
use oxideav_core::CodecRegistry;
use oxideav_core::{CodecId, CodecTag};
use oxideav_prores::{codec_id_for_fourcc, profile_for_fourcc, PRORES_FOURCCS};
fn have_ffmpeg() -> bool {
Command::new("ffmpeg")
.arg("-version")
.output()
.map(|o| o.status.success())
.unwrap_or(false)
}
fn extract_stsd_fourcc(mp4: &[u8]) -> Option<[u8; 4]> {
let needle = b"stsd";
let mut i = 0;
while i + 4 <= mp4.len() {
if &mp4[i..i + 4] == needle {
let entry_start = i + 4 + 4 + 4;
if entry_start + 8 <= mp4.len() {
let mut fc = [0u8; 4];
fc.copy_from_slice(&mp4[entry_start + 4..entry_start + 8]);
return Some(fc);
}
}
i += 1;
}
None
}
fn ffmpeg_make_prores_mov(profile_flag: u8) -> Option<Vec<u8>> {
let tmp = tempdir()?;
let out_path = tmp.join(format!("prores_p{profile_flag}.mov"));
let pix_fmt = match profile_flag {
4 | 5 => "yuva444p10le",
_ => "yuv422p10le",
};
let status = Command::new("ffmpeg")
.args([
"-hide_banner",
"-loglevel",
"error",
"-y",
"-f",
"lavfi",
"-i",
"testsrc=size=64x48:rate=1:duration=1",
"-c:v",
"prores_ks",
"-profile:v",
&profile_flag.to_string(),
"-pix_fmt",
pix_fmt,
"-frames:v",
"1",
out_path.to_str()?,
])
.status()
.ok()?;
if !status.success() {
return None;
}
std::fs::read(&out_path).ok()
}
fn tempdir() -> Option<std::path::PathBuf> {
let base = std::env::temp_dir();
let pid = std::process::id();
let ts = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.ok()?
.as_nanos();
let p = base.join(format!("oxideav-prores-mov-{pid}-{ts}"));
std::fs::create_dir_all(&p).ok()?;
Some(p)
}
#[test]
fn ffmpeg_generated_fourccs_map_to_prores() {
if !have_ffmpeg() {
eprintln!("ffmpeg not on PATH — skipping black-box FourCC test");
return;
}
let expected: [&[u8; 4]; 6] = PRORES_FOURCCS;
for (profile_flag, expect_fc) in expected.iter().enumerate() {
let flag = profile_flag as u8;
let Some(mp4) = ffmpeg_make_prores_mov(flag) else {
eprintln!("ffmpeg prores_ks profile={flag} unavailable, skipping");
continue;
};
let fc = extract_stsd_fourcc(&mp4).expect("stsd fourcc parse");
let mut upper_fc = fc;
upper_fc.make_ascii_uppercase();
let mut upper_ex = **expect_fc;
upper_ex.make_ascii_uppercase();
assert_eq!(
upper_fc,
upper_ex,
"ffmpeg profile {flag} produced fourcc {:?}, expected {:?}",
std::str::from_utf8(&fc).unwrap_or("?"),
std::str::from_utf8(*expect_fc).unwrap_or("?"),
);
assert_eq!(
codec_id_for_fourcc(&fc),
Some(CodecId::new(oxideav_prores::CODEC_ID_STR)),
"codec_id_for_fourcc rejected ffmpeg's {:?}",
std::str::from_utf8(&fc).unwrap_or("?"),
);
let p = profile_for_fourcc(&fc).expect("profile_for_fourcc");
let mut p_fc = *p.fourcc();
p_fc.make_ascii_uppercase();
assert_eq!(p_fc, upper_fc);
let mut reg = CodecRegistry::new();
oxideav_prores::register_codecs(&mut reg);
let tag = CodecTag::fourcc(&fc);
let ctx = ProbeContext::new(&tag);
let id = reg.resolve_tag(&ctx).expect("registry resolve_tag");
assert_eq!(id, CodecId::new(oxideav_prores::CODEC_ID_STR));
}
}