use container_probe::{Confidence, Detail, Format, Probe};
use std::fs;
fn fixture(rel: &str) -> Vec<u8> {
fs::read(format!("{}/../{}", env!("CARGO_MANIFEST_DIR"), rel))
.unwrap_or_else(|e| panic!("failed to read fixture {rel}: {e}"))
}
const LATTICE_STRONG: Confidence = Confidence::LATTICE_STRONG;
#[test]
fn m2ts_192_stride() {
let p = probe_fixture("fixtures/container-probe/m2ts_192.m2ts");
assert_identical_ts(p, 192, 4, LATTICE_STRONG);
}
#[test]
fn ts_midpacket_phase() {
let p = probe_fixture("fixtures/container-probe/ts_midpacket_phase.ts");
assert_identical_ts(p, 188, 111, LATTICE_STRONG);
}
#[test]
fn ts_204_stride_synthetic() {
let p = probe_fixture("fixtures/container-probe/ts_204_stride_SYNTHETIC.ts");
assert_identical_ts(p, 204, 0, LATTICE_STRONG);
}
#[test]
fn h264_aac_188_stride() {
let p = probe_fixture("fixtures/ts/h264_aac.ts");
assert_identical_ts(p, 188, 0, LATTICE_STRONG);
}
#[test]
fn france2_capture() {
let p = probe_fixture("fixtures/ts/france2.ts");
assert_identical_ts(p, 188, 0, LATTICE_STRONG);
}
#[test]
fn gulli_opengop_capture() {
let p = probe_fixture("fixtures/ts/gulli-opengop.ts");
assert_identical_ts(p, 188, 0, LATTICE_STRONG);
}
#[test]
fn tnt5w_capture() {
let p = probe_fixture("fixtures/dvb-si/tnt-5w-12732v-isi6-10s.ts");
assert_identical_ts(p, 188, 0, LATTICE_STRONG);
}
fn assert_format(rel: &str, format: Format) {
let p = probe_fixture(rel);
assert!(
matches!(p, Probe::Identified { format: f, .. } if f == format),
"{rel} must be Identified {format:?}, got {p:?}"
);
}
#[test]
fn mp4_is_not_ts() {
assert_format("fixtures/mp4/h264_high.mp4", Format::Isobmff);
}
#[test]
fn wav_is_not_ts() {
assert_format("fixtures/container-probe/pcm_s16le.wav", Format::Wav);
}
#[test]
fn ogg_is_not_ts() {
assert_format("fixtures/container-probe/opus.ogg", Format::Ogg);
}
#[test]
fn mkv_is_not_ts() {
assert_format("fixtures/mkv/h264_aac.mkv", Format::Matroska);
}
#[test]
fn flv_is_not_ts() {
assert_format("fixtures/flv/av.flv", Format::Flv);
}
#[test]
fn asf_is_not_ts() {
assert_format("fixtures/container-probe/video.asf", Format::Asf);
}
#[test]
fn cenc_mp4_is_isobmff() {
assert_format("fixtures/mp4/cenc.mp4", Format::Isobmff);
}
#[test]
fn cenc_payload_without_its_boxes_is_not_ts() {
let data = fixture("fixtures/mp4/cenc.mp4");
let p = probe(&data[1..]);
assert_eq!(
p,
Probe::Unknown,
"encrypted payload with no box header must not match any prober, got {p:?}"
);
}
#[test]
fn av1_mp4_is_not_ts() {
assert_format("fixtures/mp4/av1.mp4", Format::Isobmff);
}
#[test]
fn vp9_opus_mkv_is_not_ts() {
assert_format("fixtures/mkv/vp9_opus.mkv", Format::Matroska);
}
#[test]
fn vp8_opus_webm_is_not_ts() {
assert_format("fixtures/webm/vp8_opus.webm", Format::WebM);
}
#[test]
fn ps_is_not_ts() {
assert_format("fixtures/ps/h264_ac3.ps", Format::MpegPs);
}
#[test]
fn mxf_is_not_ts() {
assert_format("fixtures/mxf/op1a_mpeg2_pcm.mxf", Format::Mxf);
}
#[test]
fn adts_aac_is_not_ts() {
assert_format("fixtures/container-probe/aac.adts", Format::AdtsAac);
}
#[test]
fn mp3_is_not_ts() {
assert_format("fixtures/container-probe/audio.mp3", Format::Mp3);
}
#[test]
fn annexb_is_not_ts() {
assert_format("fixtures/container-probe/h264.annexb", Format::AnnexB);
}
#[test]
fn empty_slice() {
match probe(&[]) {
Probe::Insufficient { need_at_least, .. } => assert_eq!(need_at_least, 4),
other => panic!("an empty slice must be Insufficient {{ 4 }}, got {other:?}"),
}
}
#[test]
fn single_byte() {
match probe(&[0x42]) {
Probe::Insufficient { need_at_least, .. } => assert_eq!(need_at_least, 4),
other => panic!("a single byte must be Insufficient {{ 4 }}, got {other:?}"),
}
}
#[test]
fn eight_zero_bytes() {
match probe(&[0u8; 8]) {
Probe::Insufficient { need_at_least, .. } => assert_eq!(need_at_least, 13),
other => panic!("eight zero bytes must be Insufficient {{ 13 }}, got {other:?}"),
}
}
#[test]
fn ff_bytes() {
let p = probe(&[0xFFu8; 4096]);
assert!(
matches!(p, Probe::Unknown),
"4096 bytes of 0xFF rule out every format, so this must be Unknown, got {p:?}"
);
}
#[test]
fn zeros_with_single_sync_byte() {
let mut data = vec![0u8; 70_000];
data[12_345] = 0x47;
let p = probe(&data);
assert_eq!(p, Probe::Unknown, "got {p:?}");
}
#[test]
fn short_but_complete_ts_is_a_weak_match_not_insufficient() {
let data = fixture("fixtures/ts/h264_aac.ts");
let p = probe(&data[..600]);
assert_identical_ts(p, 188, 0, Confidence::LATTICE_WEAK);
}
#[test]
fn a_single_ts_packet_is_insufficient() {
let data = fixture("fixtures/ts/h264_aac.ts");
match probe(&data[..188]) {
Probe::Insufficient { need_at_least, .. } => {
assert!(
need_at_least > 188,
"need_at_least {need_at_least} must exceed the 188 supplied"
);
}
other => panic!("a single TS packet must be Insufficient, got {other:?}"),
}
}
#[test]
fn small_complete_ts_fixtures_all_identify() {
for path in [
"fixtures/mpeg-ts/af-transport-private-data.ts", "fixtures/ts/pcr-wrap.ts", "fixtures/ts/pts-backward.ts", "fixtures/ts/pts-wrap.ts", "fixtures/ts/scte35-pcr.ts", ] {
let data = fixture(path);
let p = probe(&data);
match &p {
Probe::Identified {
format,
confidence,
detail: Detail::Ts { stride, phase, .. },
..
} => {
assert_eq!(*format, Format::MpegTs, "{path}");
assert_eq!(*confidence, Confidence::LATTICE_WEAK, "{path}");
assert_eq!(*stride, 188, "{path}");
assert_eq!(*phase, 0, "{path}");
}
other => panic!("{path} ({} bytes) must identify, got {other:?}", data.len()),
}
}
}
#[test]
fn tiny_ts_fixtures_below_the_weak_threshold_are_insufficient() {
for path in [
"fixtures/ts/emsg-pid4.ts", "fixtures/ts/scte35-balanced.ts", "fixtures/ts/scte35-real.ts", "fixtures/ts/scte35-unbalanced.ts", "fixtures/mpeg-ts/af-pcr-stuffing.ts", ] {
let data = fixture(path);
match probe(&data) {
Probe::Insufficient { need_at_least, .. } => {
assert!(need_at_least > data.len(), "{path}");
}
other => panic!(
"{path} ({} bytes) is below the weak threshold and must be \
Insufficient, got {other:?}",
data.len()
),
}
}
}
#[test]
fn all_sync_bytes() {
let p = probe(&[0x47u8; 4096]);
assert!(
matches!(p, Probe::Unknown),
"got {p:?} — a full all-sync input must be ruled out, not flagged as TS"
);
}
#[test]
fn budget_caps_the_read() {
let data = fixture("fixtures/ts/h264_aac.ts");
let p = probe_with_budget(&data, 512);
match p {
Probe::Identified {
format,
confidence,
detail,
..
} => {
assert_eq!(format, Format::MpegTs);
assert_eq!(confidence, Confidence::LATTICE_WEAK);
match detail {
Detail::Ts { stride, phase, .. } => {
assert_eq!(stride, 188);
assert_eq!(phase, 0);
}
other => panic!("expected Ts detail, got {other:?}"),
}
}
Probe::Insufficient { need_at_least, .. } => {
assert!(need_at_least > 512);
}
other => panic!("budget probe returned {other:?}; expected a weak verdict or Insufficient"),
}
}
#[test]
fn budget_sizes_the_insufficient_hint() {
let data = fixture("fixtures/ts/h264_aac.ts");
let p = probe_with_budget(&data, 64);
match p {
Probe::Insufficient { need_at_least, .. } => {
assert!(
need_at_least > 64,
"need_at_least {need_at_least} must exceed the 64-byte budget"
);
assert!(
need_at_least < data.len() + 188,
"need_at_least {need_at_least} must not be sized by the full {} byte buffer",
data.len()
);
}
other => panic!("a 64-byte budget on a TS must be Insufficient, got {other:?}"),
}
}
fn assert_identical_ts(p: Probe, stride: u16, phase: u16, confidence: Confidence) {
match p {
Probe::Identified {
format: Format::MpegTs,
confidence: c,
detail:
Detail::Ts {
stride: s,
phase: ph,
..
},
..
} => {
assert_eq!(c, confidence, "probe mismatch: confidence");
assert_eq!(s, stride, "probe mismatch: stride");
assert_eq!(ph, phase, "probe mismatch: phase");
}
other => panic!("probe mismatch: {other:?}"),
}
}
fn probe(data: &[u8]) -> Probe {
container_probe::probe(data)
}
fn probe_with_budget(data: &[u8], budget: usize) -> Probe {
container_probe::probe_with_budget(data, budget)
}
fn probe_fixture(rel: &str) -> Probe {
probe(&fixture(rel))
}