container-probe 0.1.0

Robust media container-format detection — MPEG-2 TS (188/192/204/208 stride + phase), ISOBMFF, Matroska/WebM, MPEG-PS, FLV, MXF, WAV, Ogg, ASF, and elementary streams.
Documentation

container-probe

Crates.io docs.rs

Robust media container-format detection over a byte prefix — the MPEG-2 TS, ISOBMFF, Matroska/WebM, MXF, MPEG-PS, FLV, WAV, Ogg, ASF, ADTS AAC, MP3 and Annex B probers, scored and compared by an evidence-confidence model with cross-prober suppression.

#![no_std] + alloc; runtime dependency is broadcast-common only.

What it detects

Every registered prober runs over the same bytes and returns a scored candidate; the highest score wins.

Format Detected by Confidence tier
MPEG-2 TS sync lattice over 188/192/204/208-byte strides LATTICE_STRONG (128)
ISOBMFF (.mp4/.m4s) box-chain walk (ISO/IEC 14496-12 §4.2) STRUCTURAL (160)
Matroska (.mkv) EBML magic + DocType == "matroska" CERTAIN (240)
WebM (.webm) EBML magic + DocType == "webm" CERTAIN (240)
MXF (.mxf) partition-pack key + valid BER length CERTAIN (240)
MPEG-PS (.ps/.mpg) pack start code + marker bits STRUCTURAL (160)
FLV (.flv) "FLV" signature + header fields STRONG (192)
WAV (.wav) "RIFF".."WAVE" STRONG (192)
Ogg (.ogg) "OggS" STRONG (192)
ASF (.asf/.wmv) 16-byte header GUID STRONG (192)
ADTS AAC (.aac/.adts) frame-length chaining LATTICE_STRONG (128)
MP3 (.mp3) frame-length chaining LATTICE_STRONG (128)
Annex B H.264 (.h264) start-code NAL chaining LATTICE_STRONG (128)

Usage

use container_probe::{probe, Probe};

// The first 8 bytes of a TS packet's sync-adjacent header. This is too short
// for any prober to conclude from, so `probe` reports `Insufficient` (read
// more), never `Identified`.
let bytes = [0x47, 0x40, 0x11, 0x10, 0x00, 0x42, 0xf0, 0x25];
match probe(&bytes) {
    Probe::Identified { format, .. } => println!("detected {}", format.name()),
    Probe::Insufficient { need_at_least, .. } => println!("need at least {need_at_least} bytes"),
    Probe::Unknown => println!("nothing matched; stop"),
    Probe::Ambiguous { candidates, .. } => println!("tied: {:?}", candidates),
    _ => {} // `#[non_exhaustive]` requires a wildcard arm.
}

To probe a real file:

use std::fs;
let data = fs::read("fixtures/ts/h264_aac.ts").unwrap();
println!("{:?}", container_probe::probe(&data));

How detection works

Every prober is a pure function over the byte slice; all of them always run. Each returns a Confidence tier and a Detail, and the highest score wins. If the top two are within TIE_THRESHOLD (16) the result is Ambiguous listing every candidate, never an arbitrary pick. Insufficient { need_at_least } means "read more bytes"; Unknown means "stop, more bytes will not help".

Run length alone is not evidence. The test suite guards two real cases where naive magic-byte counting fails:

  • A CENC-encrypted MP4 (fixtures/mp4/cenc.mp4): its high-entropy payload aligned three consecutive 0x47 bytes on one of 792 TS lattice lanes purely by chance. A run-length-only TS prober called that a match; a candidate lane must now cover at least 50% of its positions with sync bytes — a real TS syncs at ~100%, noise at ~2.5%.
  • A TS file with 18,239 MP3 syncwords (fixtures/ts/h264_aac.ts, plus 141 ADTS syncwords and 273 Annex B start codes): raw syncword counting would identify every container as an elementary stream. Each ES prober instead follows each frame's own length field to where the next syncword must be and counts how many chain; a real stream chains 40+, a container chains 0-1. And because ES frames genuinely appear inside container payloads, a container match at LATTICE_STRONG or above zeroes every elementary-stream candidate.

Known gaps

  • Annex B detection is H.264 only. HEVC (H.265) is not detected — HEVC uses a 2-byte NAL header with nal_unit_type at bits [6:1], which the H.264 1-byte-header probe does not parse, so an HEVC stream fails at the first NAL. It is deliberately not implemented: this workspace does not implement a format without a real fixture to test it against, and no HEVC Annex B fixture exists in the repo.
  • 204-byte-stride TS (DVB with Reed–Solomon parity) is covered only by a marked synthetic fixture — fixtures/container-probe/PROVENANCE.md explains why: no real DVB Reed–Solomon capture exists in this repository.
  • 208-byte stride has no fixture at all.
  • .ts is an ambiguous extension — TypeScript declaration files also use it — so a caller must not infer format from a .ts extension; only the probe result is authoritative.

Minimum Supported Rust Version

1.95.0

License

MIT OR Apache-2.0