use container_probe::{Format, Probe};
use std::fs;
use std::path::PathBuf;
const SWEEP_LIMIT: usize = 2048;
fn repo_path(rel: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join(rel)
}
#[derive(PartialEq, Eq, Clone, Copy)]
enum WholeFile {
Identifies,
TooShortToConclude,
}
const REAL_FILES: &[(&str, Format, WholeFile)] = &[
(
"fixtures/ts/h264_aac.ts",
Format::MpegTs,
WholeFile::Identifies,
),
(
"fixtures/ts/france2.ts",
Format::MpegTs,
WholeFile::Identifies,
),
(
"fixtures/ts/scte35-real.ts",
Format::MpegTs,
WholeFile::TooShortToConclude,
),
(
"fixtures/ts/pcr-wrap.ts",
Format::MpegTs,
WholeFile::Identifies,
),
(
"fixtures/mpeg-ts/af-pcr-stuffing.ts",
Format::MpegTs,
WholeFile::TooShortToConclude,
),
(
"fixtures/container-probe/ts_midpacket_phase.ts",
Format::MpegTs,
WholeFile::Identifies,
),
(
"fixtures/container-probe/m2ts_192.m2ts",
Format::MpegTs,
WholeFile::Identifies,
),
(
"fixtures/mp4/h264_high.mp4",
Format::Isobmff,
WholeFile::Identifies,
),
(
"fixtures/mp4/hevc_main.mp4",
Format::Isobmff,
WholeFile::Identifies,
),
(
"fixtures/mp4/cenc.mp4",
Format::Isobmff,
WholeFile::Identifies,
),
(
"fixtures/mp4/cmaf/av_frag.mp4",
Format::Isobmff,
WholeFile::Identifies,
),
(
"fixtures/mp4/progressive/av_prog.mp4",
Format::Isobmff,
WholeFile::Identifies,
),
(
"fixtures/mp4/frag/av1.frag.mp4",
Format::Isobmff,
WholeFile::Identifies,
),
(
"fixtures/mkv/h264_aac.mkv",
Format::Matroska,
WholeFile::Identifies,
),
(
"fixtures/mkv/vp9_opus.mkv",
Format::Matroska,
WholeFile::Identifies,
),
(
"fixtures/webm/vorbis.webm",
Format::WebM,
WholeFile::Identifies,
),
(
"fixtures/webm/vp9_opus.webm",
Format::WebM,
WholeFile::Identifies,
),
(
"fixtures/mxf/op1a_mpeg2_pcm.mxf",
Format::Mxf,
WholeFile::Identifies,
),
(
"fixtures/ps/h264_ac3.ps",
Format::MpegPs,
WholeFile::Identifies,
),
("fixtures/flv/av.flv", Format::Flv, WholeFile::Identifies),
(
"fixtures/container-probe/pcm_s16le.wav",
Format::Wav,
WholeFile::Identifies,
),
(
"fixtures/container-probe/opus.ogg",
Format::Ogg,
WholeFile::Identifies,
),
(
"fixtures/container-probe/video.asf",
Format::Asf,
WholeFile::Identifies,
),
(
"fixtures/container-probe/aac.adts",
Format::AdtsAac,
WholeFile::Identifies,
),
(
"fixtures/container-probe/audio.mp3",
Format::Mp3,
WholeFile::Identifies,
),
(
"fixtures/container-probe/h264.annexb",
Format::AnnexB,
WholeFile::Identifies,
),
];
#[test]
fn no_prefix_of_a_real_file_is_ever_unknown() {
let mut failures: Vec<String> = Vec::new();
let mut skipped: Vec<&str> = Vec::new();
for (rel, _expected, _whole) in REAL_FILES {
let path = repo_path(rel);
let Ok(data) = fs::read(&path) else {
skipped.push(rel);
continue;
};
let sweep_to = data.len().min(SWEEP_LIMIT);
let mut bad: Vec<usize> = Vec::new();
for n in 0..=sweep_to {
if matches!(container_probe::probe(&data[..n]), Probe::Unknown) {
bad.push(n);
}
}
if !bad.is_empty() {
failures.push(format!(
" {rel}: {} of {} prefix lengths probe Unknown (a real file of a \
supported format telling the caller to stop): {}",
bad.len(),
sweep_to + 1,
summarise_runs(&bad)
));
}
}
assert!(
skipped.is_empty(),
"fixtures missing from the repository — the sweep must be exhausting, \
not silently reduced. Missing: {skipped:?}"
);
assert!(
failures.is_empty(),
"Probe::Unknown means \"stop, more bytes will not help\", which is false for \
every one of these:\n{}\n(skipped, not present: {:?})",
failures.join("\n"),
skipped
);
}
#[test]
fn no_prefix_of_a_real_file_is_identified_as_another_format() {
let mut failures: Vec<String> = Vec::new();
let mut checked = 0usize;
for (rel, expected, _whole) in REAL_FILES {
let path = repo_path(rel);
let Ok(data) = fs::read(&path) else { continue };
checked += 1;
let sweep_to = data.len().min(SWEEP_LIMIT);
let mut bad: Vec<(usize, Format)> = Vec::new();
for n in 0..=sweep_to {
if let Probe::Identified { format, .. } = container_probe::probe(&data[..n])
&& format != *expected
{
bad.push((n, format));
}
}
if !bad.is_empty() {
let (first_len, first_fmt) = bad[0];
failures.push(format!(
" {rel} (really {}): {} prefix lengths identify as the wrong format, \
first at {first_len} bytes -> {}",
expected.name(),
bad.len(),
first_fmt.name()
));
}
}
assert!(
checked == REAL_FILES.len(),
"{} of {} fixtures were missing — the sweep must confirm every real file, \
not a subset (checked {checked})",
REAL_FILES.len() - checked,
REAL_FILES.len()
);
assert!(
failures.is_empty(),
"a prefix identified as the wrong format sends the caller to the wrong \
demuxer:\n{}",
failures.join("\n")
);
}
#[test]
fn every_real_file_identifies_in_full() {
let mut failures: Vec<String> = Vec::new();
let mut checked = 0usize;
for (rel, expected, whole) in REAL_FILES {
let Ok(data) = fs::read(repo_path(rel)) else {
continue;
};
checked += 1;
let got = container_probe::probe(&data);
match (whole, &got) {
(WholeFile::Identifies, Probe::Identified { format, .. }) if format == expected => {}
(WholeFile::TooShortToConclude, Probe::Insufficient { .. }) => {}
_ => failures.push(format!(
" {rel}: expected {}, got {got:?}",
match whole {
WholeFile::Identifies => expected.name(),
WholeFile::TooShortToConclude => "Insufficient (too short to conclude)",
}
)),
}
}
assert!(
checked == REAL_FILES.len(),
"{} of {} fixtures were missing — the sweep must confirm every real file, \
not a subset (checked {checked})",
REAL_FILES.len() - checked,
REAL_FILES.len()
);
assert!(
failures.is_empty(),
"a complete real file must identify as its own format:\n{}",
failures.join("\n")
);
}
fn summarise_runs(sorted: &[usize]) -> String {
let mut out: Vec<String> = Vec::new();
let mut i = 0;
while i < sorted.len() {
let start = sorted[i];
let mut end = start;
while i + 1 < sorted.len() && sorted[i + 1] == end + 1 {
i += 1;
end = sorted[i];
}
out.push(if start == end {
format!("{start}")
} else {
format!("{start}..={end}")
});
i += 1;
}
out.join(", ")
}