open-standards/wai/identify-conformance/identify_verify.py
Transaction Science 2fd26925d3 wai identify: what any file is, from its bytes alone
`wai inspect` reads envelopes; most files a toolchain meets are not envelopes, and the
first question about one is what it is. `wai identify <file>` (operation media.inspect,
extensions/media-identify.md) answers it for any file:

- the formats the bytes fit, each with the offsets of the bytes that say so, how far its
  structure was followed and where and why that stopped;
- a container's tracks as it states them: codec, size, channels, rate, protection;
- the registered capabilities they map to, and what a format probe reads of them;
- signatures found inside the file;
- where no format is recognised, measures that still hold: how the byte values are
  spread, uniform windows, and the lags at which the bytes repeat.

The verdict is identified, ambiguous, signature_only or unknown, and there is no
confidence figure: a candidate's strength says what was checked. Nothing is decoded, no
protected track is opened, and unknown stays unknown. Walks cover ISO media, Matroska,
transport streams, Ogg, FLAC, RIFF, MPEG audio, ADTS, PNG, JPEG, GIF, IVF, YUV4MPEG2,
FLV, AV1 OBU and Annex B streams, and HLS and DASH text; 35 more formats are known by
their signature. Every walk is bounded, reads a file where its lengths lead (a large file
is never held whole), and has been given hostile bytes; it builds for wasm32.

The agent door has it as media_inspect, its results marked untrusted_content: a file's
author chooses the strings in it.

identify-conformance/ holds files written by real muxers and encoders from test patterns
and tones, what an independent media prober reads of them (prober.json), and generated
cases sitting on each rule's edge. identify_verify.py is a second implementation written
from the text; it reads the extension's tables, gives every file its whole result, and
holds ext/ to the prober's reading. tests/identify.rs holds the reference's tables to
the extension's.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-04 05:20:15 -05:00

2155 lines
70 KiB
Python

"""The second implementation of media identification: written from extensions/media-identify.md,
sharing no code with wai-rs (the probes are format_probe_verify.py's, itself written from
format-probes).
python3 -I identify_verify.py <corpus-dir>
It identifies every file of <corpus-dir>/vectors.json and requires the whole result stated there;
that every file under ext/ and gen/ is a vector's; that every reason, verdict and strength is
reached; and that what prober.json reads of ext/ agrees wherever both state a value. Then it mutates
copies of the corpus and requires each copy to fail.
Exit status 0 only if everything verifies and every mutated copy fails. Python 3.8 or later;
standard library only.
"""
import copy
import hashlib
import importlib.util
import json
import os
import re
import struct
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
HEAD = 1 << 20
SCAN = 16 << 20
MAX_STEPS = 4096
U64 = (1 << 64) - 1
_spec = importlib.util.spec_from_file_location("fpv", os.path.join(HERE, "..", "format-probe-conformance", "format_probe_verify.py"))
fpv = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(fpv)
def spec_text():
return open(os.path.join(HERE, "..", "extensions", "media-identify.md"), encoding="utf-8").read()
def section(text, heading):
at = text.index("\n" + heading)
end = text.find("\n## ", at + 1)
return text[at:] if end < 0 else text[at:end]
def table_rows(text):
rows = []
for line in text.split("\n"):
if line.startswith("| ") and not line.startswith("| id ") and not line.startswith("|-"):
rows.append([c.strip().strip("`") for c in line.strip().strip("|").split("|")])
return rows
def signature_rows(text):
out = []
for r in table_rows(section(text, "## 6.")):
parts = [(int(p.split(":")[0]), bytes.fromhex(p.split(":")[1])) for p in r[2].split()]
out.append((r[0], r[1], parts, r[3].split()))
return out
def embedded_rows(text):
return [(r[0], bytes.fromhex(r[1]), int(r[2])) for r in table_rows(section(text, "## 7."))]
def codec_capabilities(text):
s = section(text, "## 5.")
s = s[s.index("| codec | capability |"):]
out = {}
for line in s.split("\n")[2:]:
if not line.startswith("|"):
break
c = [x.strip().strip("`") for x in line.strip("|").split("|")]
out[c[0]] = c[1]
return out
def spec_table(text, header):
"""The rows of the table whose header line is `header`, each cell without its backticks."""
s = text[text.index("\n" + header + "\n") + 1:]
rows = []
for line in s.split("\n")[2:]:
if not line.startswith("|"):
break
rows.append([c.strip().strip("`") for c in line.strip().strip("|").split("|")])
return rows
def paired(rows, width):
"""A table that holds `width`-column records side by side, as records."""
out = []
for r in rows:
for k in range(0, len(r), width):
rec = r[k:k + width]
if rec and rec[0]:
out.append(rec)
return out
def iso_codecs(text):
out = {}
for names, codec in paired(spec_table(text, "| entry | codec | entry | codec |"), 2):
for n in names.replace("`", "").split():
out[n.encode()] = codec
return out
def mkv_codecs(text):
return {i.encode(): c for i, c in paired(spec_table(text, "| identifier | codec | identifier | codec |"), 2)}
def ts_types(text):
out = {}
for types, codec, media in paired(spec_table(text, "| type | codec | media | type | codec | media |"), 3):
for t in types.split():
out[int(t, 16)] = (None if codec == "null" else codec, media)
return out
def bitrates(text):
out = {}
for label, rates in spec_table(text, "| | indexes 1 to 14 |"):
version = 3 if label.startswith("version 1,") else 2
layers = [n for word, n in (("III", 1), ("II", 2), ("I", 3)) if re.search(r"\b%s\b" % word, label)]
for layer in layers:
out[(version, layer)] = [int(x) for x in rates.split()]
return out
def adts_rates(text):
m = re.search(r"rate index \(below 13: ([0-9,\s]+)\)", text)
return [int(x) for x in m.group(1).replace(",", " ").split()]
TEXT = spec_text()
SIGNATURES = signature_rows(TEXT)
EMBEDDED = embedded_rows(TEXT)
CAPS = codec_capabilities(TEXT)
ISO_CODEC = iso_codecs(TEXT)
MKV_CODEC = mkv_codecs(TEXT)
TS_TYPE = ts_types(TEXT)
BITRATES = bitrates(TEXT)
ADTS_RATES = adts_rates(TEXT)
AVC_PROFILES = [int(x) for x in re.search(r"a `profile_idc`\s+other than ([0-9,\sor]+?)\s+or a failed", TEXT).group(1).replace(",", " ").split() if x != "or"]
def clean(b):
out = []
for c in b[:64]:
out.append(chr(c) if 0x20 <= c < 0x7f and c != 0x5c else "\\x%02x" % c)
return "".join(out)
def num(b, i, n, order):
if i < 0 or i + n > len(b):
return None
return int.from_bytes(b[i:i + n], order)
def be16(b, i): return num(b, i, 2, "big")
def be24(b, i): return num(b, i, 3, "big")
def be32(b, i): return num(b, i, 4, "big")
def be64(b, i): return num(b, i, 8, "big")
def le16(b, i): return num(b, i, 2, "little")
def le32(b, i): return num(b, i, 4, "little")
class Cand:
def __init__(self, id_, kind):
self.id, self.kind, self.strength = id_, kind, "signature"
self.evidence, self.tracks, self.caps = [], [], set()
self.walked_to, self.stopped = None, None
self.facts = {}
def see(self, at, ln, is_):
if len(self.evidence) < 8:
self.evidence.append({"at": at, "len": ln, "is": is_})
def stop(self, at, why):
self.stopped = (at, why)
def track(self, media, stated, codec=None, track=None, width=None, height=None, channels=None, sample_rate=None, protected=None):
if codec in CAPS:
self.caps.add(CAPS[codec])
if len(self.tracks) < 64:
self.tracks.append({"track": track, "media": media, "stated": stated, "codec": codec, "width": width, "height": height,
"channels": channels, "sample_rate": sample_rate, "protected": protected})
def value(self, size):
return {
"id": self.id, "kind": self.kind, "strength": self.strength, "evidence": self.evidence,
"walked_to": None if self.walked_to is None else min(self.walked_to, size),
"stopped": None if self.stopped is None else {"at": min(self.stopped[0], size), "why": self.stopped[1]},
"facts": self.facts, "tracks": self.tracks, "capabilities": sorted(self.caps),
}
RANK = {"signature": 0, "structure": 1, "parameters": 2}
NOTHING = {"scheme": None, "original": None}
# ---- §5.2 ------------------------------------------------------------------------------
def wai_envelope(d, head):
if head[:4] not in (b"WAI1", b"WAI2"):
return None
version = head[3] - 0x30
c = Cand("wai", "wai")
c.see(0, 4, "magic:WAI%d" % version)
c.facts["version"] = version
def stopped(at, why):
c.walked_to = at
c.stop(at, why)
return c
ml = le32(head, 4)
if ml is None:
return stopped(0, "overrun")
if ml > HEAD:
return stopped(8, "limit")
if 8 + ml > len(d):
return stopped(8, "overrun")
try:
m = json.loads(d[8:8 + ml].decode("utf-8"))
except (ValueError, UnicodeDecodeError, RecursionError):
return stopped(8, "invalid")
c.see(8, ml, "manifest")
pos = 8 + ml
if version == 1:
cap = None
if isinstance(m, dict) and isinstance(m.get("model_requirement"), dict):
cap = m["model_requirement"].get("capability")
c.facts["capability"] = clean(cap.encode("utf-8")) if isinstance(cap, str) else None
pl = le32(d, pos)
if pl is None:
return stopped(pos, "overrun")
c.see(pos, 4, "payload_length")
if pos + 4 + pl > len(d):
return stopped(pos, "overrun")
pos += 4 + pl
else:
n = le16(d, pos)
if n is None:
return stopped(pos, "overrun")
c.see(pos, 2, "rendition_count")
c.facts["renditions"] = n
pos += 2
c.walked_to = pos
c.strength = "structure"
return c
# ---- §5.3 ------------------------------------------------------------------------------
ISO_FIRST = {b"ftyp", b"styp", b"moov", b"mdat", b"moof", b"free", b"skip", b"wide", b"sidx", b"pnot"}
def iso_children(b):
out, i = [], 0
while len(out) < MAX_STEPS:
s = be32(b, i)
if s is None or i + 8 > len(b):
break
ty = b[i + 4:i + 8]
if s == 0:
hdr, ln = 8, len(b) - i
elif s == 1:
ln = be64(b, i + 8)
if ln is None or ln < 16:
break
hdr = 16
elif s < 8:
break
else:
hdr, ln = 8, s
if i + ln > len(b):
break
out.append((ty, b[i + hdr:i + ln]))
i += ln
return out
def iso_child(b, ty):
for t, c in iso_children(b):
if t == ty:
return c
return None
def descr_size(b, i):
n = 0
for _ in range(4):
c = b[i]
i += 1
n = (n << 7) | (c & 0x7f)
if not c & 0x80:
return i
raise IndexError
def esds_codec(b):
try:
if b[4] != 3:
return None
j = descr_size(b, 5)
flags = b[j + 2]
j += 3
if flags & 0x80:
j += 2
if flags & 0x40:
j += 1 + b[j]
if flags & 0x20:
j += 2
if b[j] != 4:
return None
k = descr_size(b, j + 1)
o = b[k]
except (IndexError, TypeError):
return None
if o in (0x40, 0x66, 0x67, 0x68):
return "aac"
if o in (0x69, 0x6b):
return "mp3"
return None
def iso_track(c, trak):
track = None
tkhd = iso_child(trak, b"tkhd")
if tkhd is not None:
if len(tkhd) == 0:
return
track = be32(tkhd, 12) if tkhd[0] == 0 else be32(tkhd, 20)
mdia = iso_child(trak, b"mdia")
if mdia is None:
return
hdlr = iso_child(mdia, b"hdlr")
if hdlr is None or len(hdlr) < 12:
return
handler = hdlr[8:12]
media = {b"vide": "video", b"auxv": "video", b"pict": "video", b"soun": "audio", b"text": "text", b"subt": "text", b"sbtl": "text", b"clcp": "text"}.get(handler, "other")
minf = iso_child(mdia, b"minf")
stbl = None if minf is None else iso_child(minf, b"stbl")
stsd = None if stbl is None else iso_child(stbl, b"stsd")
if stsd is None or len(stsd) < 8:
return
entries = iso_children(stsd[8:])
if not entries:
return
ty, entry = entries[0]
named = ty
width = height = channels = rate = protected = None
inner = b""
if media == "video":
width, height = be16(entry, 24), be16(entry, 26)
inner = entry[78:]
elif media == "audio":
channels = be16(entry, 16)
r = be32(entry, 24)
rate = None if r is None else r >> 16
inner = entry[28:] if be16(entry, 8) == 0 else b""
if ty in (b"encv", b"enca", b"enct", b"encs"):
protected = dict(NOTHING)
sinf = iso_child(inner, b"sinf")
if sinf is not None:
frma = iso_child(sinf, b"frma")
schm = iso_child(sinf, b"schm")
original = None if frma is None else frma[:4]
if original is not None and len(original) < 4:
original = None
scheme = None if schm is None or len(schm) < 8 else clean(schm[4:8])
protected = {"scheme": scheme, "original": None if original is None else clean(original)}
if original is not None:
named = original
if named == b"mp4a":
esds = iso_child(inner, b"esds")
codec = None if esds is None else esds_codec(esds)
else:
codec = ISO_CODEC.get(named)
c.track(media, clean(ty), codec, track, width, height, channels, rate, protected)
def iso(d, head):
if head[4:8] not in ISO_FIRST:
return None
size = len(d)
c = Cand("isobmff", "container")
pos = headers = steps = fragments = 0
boxes = []
while pos != size:
if steps == MAX_STEPS:
c.stop(pos, "limit")
break
h = d[pos:pos + 16]
if len(h) < 8:
c.stop(pos, "overrun")
break
s, ty = be32(h, 0), h[4:8]
if not all(0x20 <= x < 0x7f or x == 0xa9 for x in ty):
c.stop(pos, "invalid")
break
if s == 0:
hdr, ln = 8, size - pos
elif s == 1:
ln = be64(h, 8)
if ln is None:
c.stop(pos, "overrun")
break
if ln < 16:
c.stop(pos, "invalid")
break
hdr = 16
elif s < 8:
c.stop(pos, "invalid")
break
else:
hdr, ln = 8, s
headers += 1
name = clean(ty)
c.see(pos + 4, 4, "box:" + name)
if name not in boxes and len(boxes) < 16:
boxes.append(name)
if ty == b"moof":
fragments += 1
fits = pos + ln <= size
if fits and ty == b"ftyp":
b = d[pos + hdr:pos + hdr + min(ln - hdr, 4096)]
if len(b) < 8:
c.stop(pos, "invalid")
break
c.facts["major_brand"] = clean(b[:4])
brands = [b[i:i + 4] for i in range(8, len(b) - 3, 4)]
c.facts["compatible_brands"] = [clean(x) for x in brands[:16]]
if any(x in (b"avif", b"avis") for x in brands + [b[:4]]):
c.caps.add("wai.image.avif")
elif fits and ty == b"moov":
if ln > 64 << 20:
c.facts["moov_not_read"] = True
else:
for t, content in iso_children(d[pos + hdr:pos + ln]):
if t == b"trak":
iso_track(c, content)
if not fits:
c.stop(pos, "overrun")
break
pos += ln
steps += 1
c.walked_to = pos
c.facts["boxes"] = boxes
if fragments:
c.facts["fragments"] = fragments
if headers >= 2:
c.strength = "structure"
return c
# ---- §5.4 ------------------------------------------------------------------------------
def leading(x):
return 8 - x.bit_length()
def element(b):
"""(id, size or None for unknown, header length), or None."""
if not b:
return None
idn = leading(b[0]) + 1
if idn > 4 or len(b) <= idn:
return None
id_ = int.from_bytes(b[:idn], "big")
sn = leading(b[idn]) + 1
if sn > 8 or len(b) < idn + sn:
return None
mask = 0xff >> sn
first = b[idn] & mask
rest = b[idn + 1:idn + sn]
size = (first << (8 * len(rest))) | int.from_bytes(rest, "big")
unknown = first == mask and all(x == 0xff for x in rest)
return id_, None if unknown else size, idn + sn
def ebml_children(b):
out, i = [], 0
while len(out) < MAX_STEPS:
e = element(b[i:i + 12])
if e is None or e[1] is None:
break
if i + e[2] + e[1] > len(b):
break
out.append((e[0], b[i + e[2]:i + e[2] + e[1]]))
i += e[2] + e[1]
return out
def uint(b):
return int.from_bytes(b, "big") if 1 <= len(b) <= 8 else None
def toward_zero(x):
if x != x or x <= 0:
return 0
return U64 if x >= U64 else int(x)
def mkv_track(c, entry):
t = dict(media="other", stated="", codec=None, track=None, width=None, height=None, channels=None, sample_rate=None, protected=None)
for id_, b in ebml_children(entry):
if id_ == 0xd7:
t["track"] = uint(b)
elif id_ == 0x83:
t["media"] = {1: "video", 2: "audio", 17: "text"}.get(uint(b), "other")
elif id_ == 0x86:
t["stated"] = clean(b)
t["codec"] = "aac" if b.startswith(b"A_AAC") else MKV_CODEC.get(b)
elif id_ == 0xe0:
for k, v in ebml_children(b):
if k == 0xb0:
t["width"] = uint(v)
elif k == 0xba:
t["height"] = uint(v)
elif id_ == 0xe1:
for k, v in ebml_children(b):
if k == 0xb5:
t["sample_rate"] = toward_zero(struct.unpack(">f", v)[0]) if len(v) == 4 else toward_zero(struct.unpack(">d", v)[0]) if len(v) == 8 else None
elif k == 0x9f:
t["channels"] = uint(v)
elif id_ == 0x6d80:
if any(k == 0x6240 and any(j == 0x5035 for j, _ in ebml_children(e)) for k, e in ebml_children(b)):
t["protected"] = dict(NOTHING)
c.track(t["media"], t["stated"], t["codec"], t["track"], t["width"], t["height"], t["channels"], t["sample_rate"], t["protected"])
def matroska(d, head):
if head[:4] != b"\x1a\x45\xdf\xa3":
return None
size = len(d)
c = Cand("matroska", "container")
c.see(0, 4, "ebml")
def stopped(at, why):
c.walked_to = at
c.stop(at, why)
return c
e = element(head)
if e is None or e[1] is None:
return stopped(0, "invalid")
_, hsize, hdr = e
if hdr + hsize > len(head):
return stopped(0, "overrun")
for id_, b in ebml_children(head[hdr:hdr + hsize]):
if id_ == 0x4282:
c.facts["doc_type"] = clean(b)
elif id_ == 0x4287:
c.facts["doc_type_version"] = uint(b)
pos = hdr + hsize
seg = d[pos:pos + 12]
e = element(seg)
if e is None or e[0] != 0x18538067:
return stopped(pos, "overrun" if len(seg) < 5 else "invalid")
c.see(pos, 4, "segment")
start = pos + e[2]
end = size if e[1] is None else start + e[1]
if end > size:
c.facts["segment_overruns_by"] = end - size
end = min(end, size)
pos = start
read = clusters = 0
while pos < end:
if read == MAX_STEPS:
c.stop(pos, "limit")
break
h = d[pos:pos + 12]
e = element(h)
if e is None:
c.stop(pos, "overrun" if len(h) < 12 else "invalid")
break
id_, s, hl = e
if s is None:
c.stop(pos, "unknown_size")
break
nxt = pos + hl + s
if nxt > size:
if id_ == 0x1f43b675:
clusters += 1
c.stop(pos, "overrun")
break
if id_ == 0x1654ae6b and s <= HEAD:
c.see(pos, 4, "tracks")
for k, entry in ebml_children(d[pos + hl:pos + hl + s]):
if k == 0xae:
mkv_track(c, entry)
if id_ == 0x1f43b675:
clusters += 1
read += 1
pos = nxt
c.walked_to = pos
if clusters:
c.facts["clusters"] = clusters
if read:
c.strength = "structure"
return c
# ---- §5.5 ------------------------------------------------------------------------------
def crc_msb(b, init):
c = init
for x in b:
c ^= x << 24
for _ in range(8):
c = ((c << 1) ^ 0x04c11db7) & 0xffffffff if c & 0x80000000 else (c << 1) & 0xffffffff
return c
def has_ca(d):
j = 0
while j + 2 <= len(d):
if d[j] == 9:
return True
j += 2 + d[j + 1]
return False
def transport(head, size):
layout = None
for psize, sync in ((188, 0), (192, 4), (204, 0)):
for start in range(psize):
if all(start + k * psize + sync < len(head) and head[start + k * psize + sync] == 0x47 for k in range(5)):
layout = (psize, sync, start)
break
if layout:
break
if not layout:
return None
psize, sync, start = layout
c = Cand("mpeg-ts", "container")
c.see(start + sync, 1, "sync")
c.facts["packet_size"] = psize
packets = scrambled = 0
programs, gather, pmt_done, streams = {}, {}, {}, []
pat_done = False
pos = start
stopped = None
while True:
p = head[pos + sync:pos + sync + 188]
if len(p) < 188:
break
if p[0] != 0x47:
stopped = pos + sync
break
packets += 1
pid = ((p[1] & 0x1f) << 8) | p[2]
if p[3] & 0xc0:
scrambled += 1
afc = (p[3] >> 4) & 3
i = 4
if afc & 2:
i += 1 + p[4]
if afc & 1 and i < 188 and (pid == 0 or pid in programs.values()):
if p[1] & 0x40:
i += 1 + p[i]
if i < 188:
gather[pid] = [b"", pos + sync + i]
if pid in gather and i < 188:
g = gather[pid]
g[0] += p[i:]
if len(g[0]) >= 3:
n = 3 + (((g[0][1] & 0x0f) << 8) | g[0][2])
if len(g[0]) >= n:
sec, at = g[0][:n], g[1]
del gather[pid]
if n >= 12 and crc_msb(sec, 0xffffffff) == 0:
if pid == 0 and sec[0] == 0 and not pat_done:
pat_done = True
c.see(at, n, "pat")
entries = sec[8:n - 4]
for k in range(0, min(len(entries) // 4, 64) * 4, 4):
number = be16(entries, k)
mapped = ((entries[k + 2] & 0x1f) << 8) | entries[k + 3]
if number != 0:
programs[number] = mapped
pmt_done[mapped] = False
elif sec[0] == 2 and pmt_done.get(pid) is False:
pmt_done[pid] = True
c.see(at, n, "pmt")
j = 12 + (((sec[10] & 0x0f) << 8) | sec[11])
program_ca = j <= len(sec) and has_ca(sec[12:j])
while j + 5 <= n - 4:
dl = ((sec[j + 3] & 0x0f) << 8) | sec[j + 4]
ca = program_ca or (j + 5 + dl <= len(sec) and has_ca(sec[j + 5:j + 5 + dl]))
streams.append((((sec[j + 1] & 0x1f) << 8) | sec[j + 2], sec[j], ca))
j += 5 + dl
pos += psize
for es, ty, ca in streams:
codec, media = TS_TYPE.get(ty, (None, "other"))
c.track(media, "stream_type:0x%02x" % ty, codec, es, protected=dict(NOTHING) if ca else None)
c.facts["packets_read"] = packets
c.facts["scrambled_packets"] = scrambled
c.facts["programs"] = len(programs)
c.walked_to = pos
if stopped is not None:
c.stop(stopped, "invalid")
elif pos < size and len(head) == size:
c.stop(pos, "overrun")
elif len(head) < size:
c.stop(pos, "limit")
c.strength = "structure"
return c
# ---- §5.6 ------------------------------------------------------------------------------
def ogg(d, head):
if head[:5] != b"OggS\x00":
return None
size = len(d)
c = Cand("ogg", "container")
pos = pages = 0
while pos != size:
if pages == 256:
c.stop(pos, "limit")
break
h = d[pos:pos + 27]
if len(h) < 27:
c.stop(pos, "overrun")
break
if h[:5] != b"OggS\x00":
c.stop(pos, "invalid")
break
nseg = h[26]
table = d[pos + 27:pos + 27 + nseg]
if len(table) < nseg:
c.stop(pos, "overrun")
break
n = sum(table)
body = d[pos + 27 + nseg:pos + 27 + nseg + n]
if len(body) < n:
c.stop(pos, "overrun")
break
if crc_msb(h[:22] + b"\0\0\0\0" + h[26:] + table + body, 0) != le32(h, 22):
c.stop(pos, "crc")
break
c.see(pos, 27 + nseg + n, "page")
if h[5] & 2:
serial = le32(h, 14)
if body.startswith(b"OpusHead"):
c.track("audio", "OpusHead", "opus", serial, channels=body[9] if len(body) > 9 else None, sample_rate=le32(body, 12))
elif body.startswith(b"\x01vorbis"):
c.track("audio", "vorbis", "vorbis", serial, channels=body[11] if len(body) > 11 else None, sample_rate=le32(body, 12))
elif body.startswith(b"\x7fFLAC"):
c.track("audio", "FLAC", "flac", serial)
elif body.startswith(b"\x80theora"):
c.track("video", "theora", "theora", serial)
elif body.startswith(b"Speex "):
c.track("audio", "Speex", "speex", serial)
else:
c.track("other", clean(body[:8]), None, serial)
pos += 27 + nseg + n
pages += 1
c.walked_to = pos
c.facts["pages_read"] = pages
if pages >= 2 or (pages == 1 and pos == size):
c.strength = "structure"
return c
# ---- §5.7 ------------------------------------------------------------------------------
def after_id3(head):
"""Where the bytes after an ID3v2 tag begin: 0 with none, None with a header that is not one."""
if head[:3] != b"ID3":
return 0
if len(head) < 10 or head[3] == 0xff or head[4] == 0xff or any(x & 0x80 for x in head[6:10]):
return None
n = 0
for x in head[6:10]:
n = (n << 7) | x
return 10 + n + (10 if head[5] & 0x10 else 0)
def flac(d, head):
start = after_id3(head)
if start is None or d[start:start + 4] != b"fLaC":
return None
size = len(d)
c = Cand("flac", "audio")
c.caps.add("wai.audio.flac")
c.see(start, 4, "fLaC")
pos = start + 4
blocks = 0
info = False
while True:
if blocks == MAX_STEPS:
c.stop(pos, "limit")
break
h = d[pos:pos + 4]
if len(h) < 4:
c.stop(pos, "overrun")
break
last, ty, ln = h[0] & 0x80, h[0] & 0x7f, be24(h, 1)
if blocks == 0:
if ty != 0 or ln != 34:
c.stop(pos, "invalid")
break
b = d[pos + 4:pos + 38]
if len(b) < 34:
c.stop(pos, "overrun")
break
c.see(pos + 4, 34, "streaminfo")
c.facts["sample_rate"] = (b[10] << 12) | (b[11] << 4) | (b[12] >> 4)
c.facts["channels"] = ((b[12] >> 1) & 7) + 1
c.facts["bits_per_sample"] = (((b[12] & 1) << 4) | (b[13] >> 4)) + 1
c.facts["samples"] = ((b[13] & 0x0f) << 32) | be32(b, 14)
info = True
if pos + 4 + ln > size:
c.stop(pos, "overrun")
break
pos += 4 + ln
blocks += 1
if last:
f = d[pos:pos + 2]
if len(f) == 2 and f[0] == 0xff and f[1] & 0xfe == 0xf8:
c.see(pos, 2, "frame_sync")
if info:
c.strength = "structure"
elif f:
c.stop(pos, "invalid")
else:
c.stop(pos, "overrun")
break
c.walked_to = pos
return c
# ---- §5.8 ------------------------------------------------------------------------------
def riff(d, head):
if head[:4] != b"RIFF" or len(head) < 12:
return None
form = head[8:12]
id_, kind = {b"WAVE": ("wave", "audio"), b"AVI ": ("avi", "container"), b"WEBP": ("webp", "image")}.get(form, ("riff", "container"))
size = len(d)
c = Cand(id_, kind)
c.see(8, 4, "form:" + clean(form))
c.facts["form"] = clean(form)
declared = le32(head, 4) + 8
end = min(declared, size)
if declared != size:
c.facts["declared_size"] = declared
pos, chunks = 12, 0
names = []
while True:
if pos + 8 > end:
if pos < end:
c.stop(pos, "overrun")
break
if chunks == MAX_STEPS:
c.stop(pos, "limit")
break
ty, ln = d[pos:pos + 4], le32(d, pos + 4)
name = clean(ty)
c.see(pos, 4, "chunk:" + name)
if name not in names and len(names) < 16:
names.append(name)
b = d[pos + 8:pos + 8 + 64]
if id_ == "wave" and ty == b"fmt ":
b = b[:min(ln, 64)]
tag = le16(b, 0)
if tag == 0xfffe:
tag = le16(b, 24)
c.facts.update(format_tag=tag, channels=le16(b, 2), sample_rate=le32(b, 4), bits_per_sample=le16(b, 14))
elif id_ == "wave" and ty == b"data":
c.facts["data_bytes"] = ln
elif id_ == "webp" and ty == b"VP8 ":
b = b[:10]
if b[3:6] == b"\x9d\x01\x2a":
w, h = le16(b, 6), le16(b, 8)
c.facts.update(coding="lossy", width=None if w is None else w & 0x3fff, height=None if h is None else h & 0x3fff)
elif id_ == "webp" and ty == b"VP8L":
b = b[:5]
if b[:1] == b"\x2f" and len(b) == 5:
v = le32(b, 1)
c.facts.update(coding="lossless", width=(v & 0x3fff) + 1, height=((v >> 14) & 0x3fff) + 1)
elif id_ == "webp" and ty == b"VP8X":
b = b[:10]
if len(b) == 10:
c.facts.update(coding="extended", width=int.from_bytes(b[4:7], "little") + 1, height=int.from_bytes(b[7:10], "little") + 1)
if pos + 8 + ln > end:
c.stop(pos, "overrun")
break
pos += 8 + ln + (ln & 1)
chunks += 1
c.walked_to = min(pos, end)
c.facts["chunks"] = names
if chunks >= 1:
c.strength = "structure"
return c
# ---- §5.9, §5.10 -----------------------------------------------------------------------
def mpeg_frame(b):
if len(b) < 4 or b[0] != 0xff or b[1] & 0xe0 != 0xe0:
return None
version, layer, bi, ri = (b[1] >> 3) & 3, (b[1] >> 1) & 3, b[2] >> 4, (b[2] >> 2) & 3
if version == 1 or layer == 0 or bi in (0, 15) or ri == 3:
return None
kbps = BITRATES[(3 if version == 3 else 2, layer)][bi - 1]
rate = (44100, 48000, 32000)[ri] >> {3: 0, 2: 1, 0: 2}[version]
pad = (b[2] >> 1) & 1
if layer == 3:
ln = (12000 * kbps // rate + pad) * 4
elif version != 3 and layer == 1:
ln = 72000 * kbps // rate + pad
else:
ln = 144000 * kbps // rate + pad
return version, layer, kbps, rate, b[3] >> 6, ln
def mpeg_audio(head, size):
start = after_id3(head)
if start is None or start >= len(head):
return None
first = mpeg_frame(head[start:start + 4])
if first is None:
return None
c = Cand("mpeg-audio", "audio")
if start:
c.see(0, 3, "id3v2")
c.see(start, 4, "frame_header")
c.facts.update(version={3: "1", 2: "2", 0: "2.5"}[first[0]], layer=4 - first[1], bitrate_kbps=first[2], sample_rate=first[3], channels=1 if first[4] == 3 else 2)
pos, frames = start, 0
while True:
if frames == MAX_STEPS:
c.stop(pos, "limit")
break
left = len(head) - pos
if left <= 0:
if pos < size:
c.stop(pos, "limit")
break
if left == 128 and head[pos:pos + 3] == b"TAG":
c.see(pos, 3, "id3v1")
pos += 128
break
f = mpeg_frame(head[pos:pos + 4])
if f is None or f[:2] != first[:2] or f[3] != first[3]:
c.stop(pos, "overrun" if left < 4 else "invalid")
break
if pos + f[5] > size:
c.stop(pos, "overrun")
break
if f[5] > left:
c.stop(pos, "limit")
break
pos += f[5]
frames += 1
c.walked_to = pos
c.facts["frames_read"] = frames
if frames >= 3:
c.strength = "structure"
return c if frames else None
def adts_frame(h):
if len(h) < 7 or h[0] != 0xff or h[1] & 0xf6 != 0xf0:
return None
ri = (h[2] >> 2) & 0x0f
ln = ((h[3] & 3) << 11) | (h[4] << 3) | (h[5] >> 5)
if ri >= len(ADTS_RATES) or ln < 7:
return None
return (h[2] >> 6) + 1, ADTS_RATES[ri], ((h[2] & 1) << 2) | (h[3] >> 6), ln
def adts(head, size):
first = adts_frame(head[:7])
if first is None:
return None
c = Cand("adts", "audio")
c.caps.add("wai.audio.aac")
c.see(0, 7, "frame_header")
c.facts.update(audio_object_type=first[0], sample_rate=first[1], channel_configuration=first[2])
pos, frames = 0, 0
while True:
if frames == MAX_STEPS:
c.stop(pos, "limit")
break
left = len(head) - pos
if left == 0:
if pos < size:
c.stop(pos, "limit")
break
f = adts_frame(head[pos:pos + 7])
if f is None or f[:2] != first[:2]:
c.stop(pos, "overrun" if left < 7 else "invalid")
break
if pos + f[3] > size:
c.stop(pos, "overrun")
break
if f[3] > left:
c.stop(pos, "limit")
break
pos += f[3]
frames += 1
c.walked_to = pos
c.facts["frames_read"] = frames
if frames >= 3:
c.strength = "structure"
return c if frames else None
# ---- §5.11 to §5.13 --------------------------------------------------------------------
def crc_png(b):
c = 0xffffffff
for x in b:
c ^= x
for _ in range(8):
c = (c >> 1) ^ 0xedb88320 if c & 1 else c >> 1
return c ^ 0xffffffff
def png(d, head):
if head[:8] != b"\x89PNG\r\n\x1a\n":
return None
size = len(d)
c = Cand("png", "image")
c.caps.add("wai.image.png")
c.see(0, 8, "signature")
pos, chunks = 8, 0
while True:
if chunks == MAX_STEPS:
c.stop(pos, "limit")
break
if pos == size:
c.stop(pos, "overrun")
break
h = d[pos:pos + 8]
if len(h) < 8:
c.stop(pos, "overrun")
break
ln, ty = be32(h, 0), h[4:8]
if not all(0x41 <= x <= 0x5a or 0x61 <= x <= 0x7a for x in ty):
c.stop(pos, "invalid")
break
if chunks == 0:
if ty != b"IHDR" or ln != 13:
c.stop(pos, "invalid")
break
b = d[pos + 4:pos + 25]
if len(b) < 21:
c.stop(pos, "overrun")
break
if crc_png(b[:17]) != be32(b, 17):
c.stop(pos, "crc")
break
c.see(pos, 25, "ihdr")
c.facts.update(width=be32(b, 4), height=be32(b, 8), bit_depth=b[12], colour_type=b[13], interlace=b[16])
c.strength = "structure"
if ty == b"acTL":
c.facts["animated"] = True
if ty == b"cICP":
b = d[pos + 8:pos + 12]
if len(b) == 4:
c.facts["cicp"] = list(b)
if pos + 12 + ln > size:
c.stop(pos, "overrun")
break
pos += 12 + ln
chunks += 1
if ty == b"IEND":
c.see(pos - 12 - ln, 12, "iend")
break
c.walked_to = pos
c.facts["chunks_read"] = chunks
return c
APPS = [(b"JFIF\0", "jfif"), (b"Exif\0\0", "exif"), (b"ICC_PROFILE\0", "icc"), (b"MPF\0", "mpf"), (b"http://ns.adobe.com/xap/1.0/\0", "xmp"), (b"Adobe", "adobe")] # allow-name: the APP14 segment's identifier and the XMP namespace, byte signatures a JPEG carries
SOF = set(range(0xc0, 0xc4)) | set(range(0xc5, 0xc8)) | set(range(0xc9, 0xcc)) | set(range(0xcd, 0xd0))
def jpeg(d, head):
if head[:3] != b"\xff\xd8\xff":
return None
size = len(d)
c = Cand("jpeg", "image")
c.caps.add("wai.image.jpeg")
c.see(0, 2, "soi")
pos, markers = 2, 0
apps = []
while True:
if markers == MAX_STEPS:
c.stop(pos, "limit")
break
h = d[pos:pos + 4]
if len(h) < 2:
c.stop(pos, "overrun")
break
if h[0] != 0xff:
c.stop(pos, "invalid")
break
m = h[1]
if m == 0xff:
pos += 1
markers += 1
continue
if m == 0xda:
c.see(pos, 2, "sos")
break
if len(h) < 4:
c.stop(pos, "overrun")
break
ln = be16(h, 2)
if ln < 2:
c.stop(pos, "invalid")
break
if 0xe0 <= m <= 0xef:
b = d[pos + 4:pos + 4 + min(ln - 2, 32)]
name = next((n for p, n in APPS if b.startswith(p)), "other")
if name not in apps and len(apps) < 8:
apps.append(name)
if m in SOF:
b = d[pos + 4:pos + 10]
if len(b) < 6:
c.stop(pos, "overrun")
break
c.see(pos, 2 + ln, "sof")
process = {0xc0: "baseline", 0xc1: "extended", 0xc2: "progressive", 0xc3: "lossless", 0xc9: "arithmetic", 0xca: "arithmetic", 0xcb: "arithmetic"}.get(m, "hierarchical")
c.facts.update(process=process, precision=b[0], height=be16(b, 1), width=be16(b, 3), components=b[5])
c.strength = "structure"
if pos + 2 + ln > size:
c.stop(pos, "overrun")
break
pos += 2 + ln
markers += 1
c.walked_to = pos
c.facts["applications"] = apps
return c
def gif(head, size):
if head[:6] not in (b"GIF87a", b"GIF89a"):
return None
c = Cand("gif", "image")
c.see(0, 6, "signature:" + clean(head[:6]))
def cut(at):
c.stop(at, "limit" if len(head) < size else "overrun")
if len(head) < 13:
c.walked_to = 6
cut(6)
return c
c.facts["width"], c.facts["height"] = le16(head, 6), le16(head, 8)
pos = 13 + (3 << ((head[10] & 7) + 1) if head[10] & 0x80 else 0)
def blocks(p):
while True:
if p >= len(head):
return None
n = head[p]
p += 1 + n
if n == 0:
return p
frames = steps = 0
ended = False
while True:
if steps == MAX_STEPS:
c.stop(pos, "limit")
break
if pos >= len(head):
cut(pos)
break
b = head[pos]
if b == 0x3b:
c.see(pos, 1, "trailer")
pos += 1
ended = True
break
if b == 0x21:
p = blocks(pos + 2)
if p is None:
cut(pos)
break
pos = p
elif b == 0x2c:
desc = head[pos + 1:pos + 10]
if len(desc) < 9:
cut(pos)
break
table = 3 << ((desc[8] & 7) + 1) if desc[8] & 0x80 else 0
p = blocks(pos + 10 + table + 1)
if p is None:
cut(pos)
break
if frames == 0:
c.see(pos, 10, "image")
frames += 1
pos = p
else:
c.stop(pos, "invalid")
break
steps += 1
c.walked_to = pos
c.facts["frames_read"] = frames
if frames >= 1 or ended:
c.strength = "structure"
return c
# ---- §5.14 to §5.17 --------------------------------------------------------------------
def ivf(d, head):
if head[:4] != b"DKIF":
return None
size = len(d)
c = Cand("ivf", "container")
c.see(0, 4, "dkif")
if len(head) < 12:
c.walked_to = 0
c.stop(0, "overrun")
return c
hdr, fourcc = le16(head, 6), head[8:12]
if hdr < 32 or le16(head, 4) != 0:
c.walked_to = 0
c.stop(0, "invalid")
return c
c.track("video", clean(fourcc), {b"AV01": "av1", b"VP90": "vp9", b"VP80": "vp8"}.get(fourcc), width=le16(head, 12), height=le16(head, 14))
pos, frames = hdr, 0
while pos != size:
if frames == MAX_STEPS:
c.stop(pos, "limit")
break
h = d[pos:pos + 12]
if len(h) < 12:
c.stop(pos, "overrun")
break
n = le32(h, 0)
if pos + 12 + n > size:
c.stop(pos, "overrun")
break
pos += 12 + n
frames += 1
c.walked_to = pos
c.facts["frames_read"] = frames
if frames >= 1:
c.strength = "structure"
return c
def decimal(t):
if not re.fullmatch(rb"\+?[0-9]+", t):
return None
v = int(t)
return v if v <= U64 else None
def y4m_frame(tag, w, h):
try:
tag = tag.decode("utf-8")
except UnicodeDecodeError:
return None
base, depth = tag, 8
if "p" in tag:
b, _, dd = tag.partition("p")
if dd and re.fullmatch("[0-9]+", dd):
base, depth = b, int(dd)
if depth > U64:
return None
cw, ch = (w + 1) // 2, (h + 1) // 2
if base in ("420", "420jpeg", "420mpeg2", "420paldv"):
n = w * h + 2 * cw * ch
elif base == "422":
n = w * h + 2 * cw * h
elif base == "444":
n = 3 * w * h
elif base == "mono":
n = w * h
else:
return None
n *= 2 if depth > 8 else 1
return n if n <= U64 else None
def y4m(d, head):
if head[:10] != b"YUV4MPEG2 ":
return None
size = len(d)
c = Cand("y4m", "elementary")
c.see(0, 10, "signature")
nl = head[:256].find(b"\n")
if nl < 0:
c.walked_to = 0
c.stop(0, "overrun" if len(head) == size and len(head) < 256 else "invalid")
return c
w = h = None
colour = b"420"
for tok in head[10:nl].split(b" "):
if not tok:
continue
if tok[:1] == b"W":
w = decimal(tok[1:])
elif tok[:1] == b"H":
h = decimal(tok[1:])
elif tok[:1] == b"C":
colour = tok[1:]
elif tok[:1] == b"F":
c.facts["frame_rate"] = clean(tok[1:])
c.facts.update(width=w, height=h, colour=clean(colour))
pos = nl + 1
frame = None if w is None or h is None else y4m_frame(colour, w, h)
if frame is None:
c.walked_to = pos
c.stop(pos, "invalid")
return c
frames = 0
while pos != size:
if frames == MAX_STEPS:
c.stop(pos, "limit")
break
line = d[pos:pos + 256]
if not line.startswith(b"FRAME"):
c.stop(pos, "overrun" if len(line) < 5 else "invalid")
break
e = line.find(b"\n")
if e < 0:
c.stop(pos, "invalid")
break
nxt = pos + e + 1 + frame
if nxt > size:
c.stop(pos, "overrun")
break
pos = nxt
frames += 1
c.walked_to = pos
c.facts["frames_read"] = frames
if frames >= 1:
c.strength = "structure"
return c
def flv(d, head):
if head[:4] != b"FLV\x01":
return None
size = len(d)
c = Cand("flv", "container")
c.see(0, 4, "signature")
data = be32(head, 5)
if data is None or data < 9:
c.walked_to = 0
c.stop(0, "overrun" if len(head) < 9 else "invalid")
return c
pos = data + 4
tags = 0
audio = video = None
while pos != size:
if tags == MAX_STEPS:
c.stop(pos, "limit")
break
h = d[pos:pos + 16]
if len(h) < 11:
c.stop(pos, "overrun")
break
ty = h[0] & 0x1f
if ty not in (8, 9, 18):
c.stop(pos, "invalid")
break
n = be24(h, 1)
if ty == 8 and audio is None and n > 0 and len(h) > 11:
fmt = h[11] >> 4
audio = ("sound_format:%d" % fmt, {2: "mp3", 10: "aac"}.get(fmt))
if ty == 9 and video is None and n > 0 and len(h) > 11:
if h[11] & 0x80:
if len(h) == 16:
video = (clean(h[12:16]), {b"avc1": "avc", b"hvc1": "hevc", b"av01": "av1", b"vp09": "vp9"}.get(h[12:16]))
else:
video = ("codec_id:%d" % (h[11] & 0x0f), "avc" if h[11] & 0x0f == 7 else None)
p = be32(d, pos + 11 + n)
if p is None:
c.stop(pos, "overrun")
break
if p != 11 + n:
c.stop(pos, "invalid")
break
pos += 15 + n
tags += 1
c.walked_to = pos
c.facts["tags_read"] = tags
if video:
c.track("video", video[0], video[1])
if audio:
c.track("audio", audio[0], audio[1])
if tags >= 1:
c.strength = "structure"
return c
class Bits:
def __init__(self, b):
self.b, self.at = b, 0
def u(self, n):
v = 0
for _ in range(n):
if self.at >= 8 * len(self.b):
raise IndexError
v = (v << 1) | ((self.b[self.at // 8] >> (7 - self.at % 8)) & 1)
self.at += 1
return v
def ue(self):
z = 0
while self.u(1) == 0:
z += 1
if z > 31:
raise IndexError
return 0 if z == 0 else (1 << z) - 1 + self.u(z)
def av1_sequence(b):
r = Bits(b)
try:
profile = r.u(3)
r.u(1)
if r.u(1):
level = r.u(5)
else:
model, delay_bits = False, 0
if r.u(1):
r.u(32)
r.u(32)
if r.u(1):
z = 0
while r.u(1) == 0:
z += 1
if z >= 32:
return None
r.u(z)
if r.u(1):
model = True
delay_bits = r.u(5) + 1
r.u(32)
r.u(5)
r.u(5)
delay = r.u(1)
level = None
for _ in range(r.u(5) + 1):
r.u(12)
k = r.u(5)
if level is None:
level = k
if k > 7:
r.u(1)
if model and r.u(1):
r.u(delay_bits)
r.u(delay_bits)
r.u(1)
if delay and r.u(1):
r.u(4)
wb = r.u(4) + 1
hb = r.u(4) + 1
return profile, level, r.u(wb) + 1, r.u(hb) + 1
except IndexError:
return None
def obu(b):
if not b:
return None
h = b[0]
if h & 0x81 or not h & 0x02:
return None
ty = (h >> 3) & 15
if not (1 <= ty <= 8 or ty == 15):
return None
i = 1 + ((h >> 2) & 1)
n = shift = 0
while True:
if i >= len(b):
return None
c = b[i]
i += 1
n |= (c & 0x7f) << shift
if not c & 0x80:
break
shift += 7
if shift > 49:
return None
return ty, i, n
def av1(head, size):
first = obu(head[:16])
if first is None or first[0] not in (1, 2):
return None
c = Cand("av1-obu", "elementary")
c.caps.add("wai.video.av1")
pos = count = 0
seen = False
while True:
if count == MAX_STEPS:
c.stop(pos, "limit")
break
left = len(head) - pos
if left == 0:
if pos < size:
c.stop(pos, "limit")
break
o = obu(head[pos:pos + 16])
if o is None:
c.stop(pos, "limit" if left < 8 and len(head) < size else "invalid")
break
ty, hdr, n = o
if pos + hdr + n > size:
c.stop(pos, "overrun")
break
if hdr + n > left:
c.stop(pos, "limit")
break
if ty == 1 and not seen:
seen = True
c.see(pos, hdr + n, "sequence_header")
q = av1_sequence(head[pos + hdr:pos + hdr + n])
if q:
c.facts.update(profile=q[0], level=q[1], width=q[2], height=q[3])
pos += hdr + n
count += 1
c.walked_to = pos
c.facts["units_read"] = count
if count and seen and count >= 2 and "width" in c.facts:
c.strength = "structure"
return c
return None
# ---- §5.18, §5.19 ----------------------------------------------------------------------
def hls(head):
b = head[3:] if head[:3] == b"\xef\xbb\xbf" else head
offset = len(head) - len(b)
if not b.startswith(b"#EXTM3U") or b[7:8] not in (b"\r", b"\n", b""):
return None
c = Cand("hls", "playlist")
c.see(offset, 7, "extm3u")
variants = segments = tags = maps = 0
key = version = codecs = None
ended = False
for line in b.split(b"\n"):
if line.endswith(b"\r"):
line = line[:-1]
if line.startswith(b"#EXT-X-"):
tags += 1
if line.startswith(b"#EXT-X-STREAM-INF:"):
variants += 1
if codecs is None and b'CODECS="' in line:
rest = line[line.index(b'CODECS="') + 8:]
if b'"' in rest:
codecs = clean(rest[:rest.index(b'"')])
elif line.startswith(b"#EXTINF:"):
segments += 1
elif line.startswith(b"#EXT-X-MAP:"):
maps += 1
elif line.startswith(b"#EXT-X-ENDLIST"):
ended = True
elif line.startswith(b"#EXT-X-VERSION:"):
version = decimal(line[15:])
elif line.startswith(b"#EXT-X-KEY:") or line.startswith(b"#EXT-X-SESSION-KEY:"):
if b"METHOD=" in line:
m = line[line.index(b"METHOD=") + 7:].split(b",")[0]
if m != b"NONE" and key is None:
key = clean(m)
c.facts.update(playlist="multivariant" if variants else "media" if segments else "other", variants=variants, segments=segments, maps=maps, ended=ended, version=version, key_method=key, codecs=codecs)
if tags:
c.strength = "structure"
return c
WS = b" \t\n\x0c\r"
def dash(head):
b = head[3:] if head[:3] == b"\xef\xbb\xbf" else head
offset = len(head) - len(b)
i = 0
def skip(i):
while i < len(b) and b[i] in WS:
i += 1
return i
i = skip(i)
while True:
if b[i:i + 2] == b"<?":
e = b.find(b"?>", i)
if e < 0:
return None
i = e + 2
elif b[i:i + 4] == b"<!--":
e = b.find(b"-->", i)
if e < 0:
return None
i = e + 3
else:
break
i = skip(i)
if b[i:i + 4] != b"<MPD" or i + 4 >= len(b) or (b[i + 4] not in WS and b[i + 4] != 0x3e):
return None
c = Cand("dash", "playlist")
c.see(offset + i, 4, "mpd")
n = b.find(b"urn:mpeg:dash:schema:mpd:2011")
if n >= 0:
c.see(offset + n, 29, "namespace")
c.strength = "structure"
rest = b[i + 4:]
root = rest.split(b">")[0]
kind = None
if b' type="' in root:
tail = root[root.index(b' type="') + 7:]
if b'"' in tail:
kind = clean(tail[:tail.index(b'"')])
c.facts.update(type=kind, periods=b.count(b"<Period"), adaptation_sets=b.count(b"<AdaptationSet"), representations=b.count(b"<Representation"), content_protection=b.count(b"<ContentProtection"))
codecs = []
j = 0
while True:
at = b.find(b'codecs="', j)
if at < 0:
break
s = at + 8
e = b.find(b'"', s)
if e < 0:
break
v = clean(b[s:e])
if v not in codecs and len(codecs) < 16:
codecs.append(v)
j = e
c.facts["codecs"] = codecs
return c
# ---- §5.20 -----------------------------------------------------------------------------
def nal_units(head):
lead = 0
while lead < len(head) and head[lead] == 0:
lead += 1
if lead >= len(head) or lead < 2 or head[lead] != 1:
return None
starts = []
i = head.find(b"\0\0\1")
while 0 <= i and i + 2 < len(head):
starts.append(i + 3)
if len(starts) > MAX_STEPS:
break
i = head.find(b"\0\0\1", i + 3)
units = []
for k, s in enumerate(starts):
if k == MAX_STEPS:
break
end = starts[k + 1] - 3 if k + 1 < len(starts) else len(head)
while end > s and head[end - 1] == 0:
end -= 1
units.append((s, head[s:end]))
return units
def unescape(n):
out = bytearray()
zeros = 0
for x in n:
if zeros >= 2 and x == 3:
zeros = 0
continue
zeros = zeros + 1 if x == 0 else 0
out.append(x)
return bytes(out)
def avc_sps(rbsp):
# The profiles whose header holds the chroma format: every one §5.20 lists but the first three.
high = set(AVC_PROFILES[3:])
r = Bits(rbsp)
try:
profile = r.u(8)
if profile not in AVC_PROFILES:
return None
flags, level = r.u(8), r.u(8)
r.ue()
chroma, separate = 1, False
if profile in high:
chroma = r.ue()
if chroma > 3:
return None
if chroma == 3:
separate = bool(r.u(1))
r.ue()
r.ue()
r.u(1)
if r.u(1):
for i in range(8 if chroma != 3 else 12):
if r.u(1):
last, nxt = 8, 8
for _ in range(16 if i < 6 else 64):
if nxt != 0:
code = r.ue()
delta = (code + 1) // 2 if code % 2 else -(code // 2)
nxt = (last + delta + 256) % 256
last = last if nxt == 0 else nxt
r.ue()
poc = r.ue()
if poc == 0:
r.ue()
elif poc == 1:
r.u(1)
r.ue()
r.ue()
n = r.ue()
if n > 255:
return None
for _ in range(n):
r.ue()
elif poc != 2:
return None
r.ue()
r.u(1)
w_mbs, h_units = r.ue() + 1, r.ue() + 1
frame_only = r.u(1)
if not frame_only:
r.u(1)
r.u(1)
left = right = top = bottom = 0
if r.u(1):
left, right, top, bottom = r.ue(), r.ue(), r.ue(), r.ue()
except IndexError:
return None
array = 0 if separate else chroma
sw, sh = {1: (2, 2), 2: (2, 1)}.get(array, (1, 1))
cx = 1 if array == 0 else sw
cy = (1 if array == 0 else sh) * (1 if frame_only else 2)
width = w_mbs * 16 - cx * (left + right)
height = h_units * (16 if frame_only else 32) - cy * (top + bottom)
if width < 0 or height < 0:
return None
return {"profile_idc": profile, "constraint_flags": flags, "level_idc": level, "chroma_format_idc": chroma, "width": width, "height": height}
def in_order(types, order, vcl):
first = next((k for k, t in enumerate(types) if vcl(t)), len(types))
last = 0
for want in order:
if want not in types:
return False
at = types.index(want)
if at >= first or at < last:
return False
last = at
return True
def annex_b(head, size):
units = nal_units(head)
if not units or not units[0][1] or units[0][1][0] in (0xba, 0xb3):
return []
out = []
def make(id_, cap):
c = Cand(id_, "elementary")
c.caps.add(cap)
c.strength = "structure"
c.facts["units_read"] = len(units)
return c
def finish(c):
if len(head) < size or len(units) == MAX_STEPS:
c.stop(units[-1][0], "limit")
out.append(c)
types = []
for _, u in units:
if not u:
types = None
break
b = u[0]
t = b & 31
if b & 0x80 or not 1 <= t <= 21 or (t in (5, 7, 8) and b >> 5 == 0):
types = None
break
types.append(t)
if types and in_order(types, [7, 8], lambda t: 1 <= t <= 5):
at, sps = next((a, u) for a, u in units if u[0] & 31 == 7)
v = avc_sps(unescape(sps)[1:])
if v:
c = make("avc-annexb", "wai.video.avc")
c.see(at, len(sps), "nal:sps")
c.facts.update(v)
finish(c)
types = []
for _, u in units:
if len(u) < 2:
types = None
break
t = (u[0] >> 1) & 63
if u[0] & 0x80 or not u[1] & 7 or not (t <= 9 or 16 <= t <= 21 or 32 <= t <= 40):
types = None
break
types.append(t)
if types and in_order(types, [32, 33, 34], lambda t: t <= 9 or 16 <= t <= 21):
c = make("hevc-annexb", "wai.video.hevc")
for at, u in units:
if (u[0] >> 1) & 63 == 33:
c.see(at, len(u), "nal:sps")
break
finish(c)
types = []
for _, u in units:
if len(u) < 2:
types = None
break
t = u[1] >> 3
if u[0] & 0xc0 or not u[1] & 7 or t > 25:
types = None
break
types.append(t)
if types and in_order(types, [15, 16], lambda t: t <= 10):
c = make("vvc-annexb", "wai.video.vvc")
for at, u in units:
if u[1] >> 3 == 15:
c.see(at, len(u), "nal:sps")
break
finish(c)
return out
# ---- §3.5, §6 --------------------------------------------------------------------------
IDENTIFYING = ["wai.image.jxs", "wai.video.jxs", "wai.video.hevc", "wai.video.mvhevc", "wai.video.vvc", "wai.video.lcevc", "wai.audio.mpegh", "wai.video.avs3", "wai.audio.avs3"]
def probes(head, found):
accepted = []
for cap in IDENTIFYING:
o = fpv.probe(cap, head)
if "accept" in o:
accepted.append(cap)
hit = next((c for c in found if c.kind == "elementary" and (cap in c.caps or (cap == "wai.video.mvhevc" and "wai.video.hevc" in c.caps))), None)
if hit is None:
hit = Cand(cap, "elementary")
hit.see(0, len(head), "probe")
found.append(hit)
hit.strength = "parameters"
hit.caps.add(cap)
hit.facts.setdefault("probe", {})[cap] = o["accept"]
elif o["refuse"] != "no_signature":
hit = next((c for c in found if c.kind == "elementary" and cap in c.caps), None)
if hit is not None:
hit.facts.setdefault("probe_refused", {})[cap] = o["refuse"]
return accepted
# ---- §7, §8 ----------------------------------------------------------------------------
def embedded(scan):
hits = []
for id_, b, back in EMBEDDED:
i = scan.find(b)
while i >= 0:
if i > back:
hits.append((i, len(hits), {"at": i - back, "id": id_}))
i = scan.find(b, i + 1)
# In order of the offset their bytes are found at, within one offset in the table's order.
hits.sort(key=lambda h: (h[0], h[1]))
return [h[2] for h in hits[:32]], len(hits) > 32
def counts(b):
return [b.count(bytes([v])) for v in range(256)]
def isqrt(n):
if n < 2:
return n
x = 1 << ((n.bit_length() + 1) // 2)
while True:
y = (x + n // x) // 2
if y >= x:
return x
x = y
def periods(s):
n = len(s)
longest = min(4096, n // 4)
if longest < 2:
return []
total = sum(c * c for c in counts(s))
whole = int.from_bytes(s, "big")
e = [0] * (longest + 2)
z = [0] * (longest + 2)
for lag in range(1, longest + 2):
m = n - lag
# The bytes at i and i + lag differ where the XOR of the two runs has a nonzero byte.
x = (whole >> (8 * lag)) ^ (whole & ((1 << (8 * m)) - 1))
matches = x.to_bytes(m, "big").count(0)
expected = max(m * total // (n * n), 1)
e[lag] = matches - expected
if e[lag] > 0:
z[lag] = isqrt(e[lag] * e[lag] // expected)
sizes = sorted(abs(v) for v in e[1:])
floor = 8 * sizes[len(sizes) // 2]
out = []
for lag in range(2, longest + 1):
if z[lag] < 8 or e[lag] < floor or e[lag] <= 2 * max(e[lag - 1], e[lag + 1], 0):
continue
if 2 * lag <= longest + 1 and z[2 * lag] < 8:
continue
if any(lag % p == 0 for p in out):
continue
out.append(lag)
if len(out) == 8:
break
return [{"lag": lag, "z": z[lag]} for lag in out]
def is_text(b):
if not b:
return False
try:
b.decode("utf-8")
except UnicodeDecodeError as err:
if err.end != len(b) or err.reason != "unexpected end of data":
return False
return not any((x < 0x20 and x not in (9, 10, 12, 13)) or x == 0x7f for x in set(b))
def measures(scan):
c = counts(scan)
n = len(scan)
windows = uniform = 0
for i in range(0, n - 4095, 4096):
windows += 1
w = scan[i:i + 4096]
if sum(x * x for x in counts(w)) <= 70496:
uniform += 1
return {
"scanned": n,
"distinct_bytes": sum(1 for x in c if x),
"zero_bytes": c[0],
"chi_square": 0 if n == 0 else max(256 * sum(x * x for x in c) // n - n, 0),
"windows": windows,
"uniform_windows": uniform,
"text": is_text(scan),
"periods": periods(scan[:256 << 10]),
}
def identify(d):
size = len(d)
head, scan = d[:HEAD], d[:SCAN]
found = [c for c in (
wai_envelope(d, head), iso(d, head), matroska(d, head), transport(head, size), ogg(d, head), flac(d, head), riff(d, head),
mpeg_audio(head, size), adts(head, size), png(d, head), jpeg(d, head), gif(head, size), ivf(d, head), y4m(d, head), flv(d, head),
av1(head, size), hls(head), dash(head)) if c is not None]
found += annex_b(head, size)
for id_, kind, parts, caps in SIGNATURES:
if all(head[a:a + len(b)] == b for a, b in parts):
c = Cand(id_, kind)
for a, b in parts:
c.see(a, len(b), "signature")
c.caps |= set(caps)
found.append(c)
accepted = probes(head, found)
found.sort(key=lambda c: -RANK[c.strength])
top = [c for c in found if c.strength == found[0].strength]
if not found:
verdict = "unknown"
elif len(top) > 1:
verdict = "ambiguous"
elif top[0].strength == "signature":
verdict = "signature_only"
else:
verdict = "identified"
em, capped = embedded(scan)
return {"format": "wai.identify/1", "size": size, "verdict": verdict, "candidates": [c.value(size) for c in found], "probes_accepted": accepted,
"embedded": em, "embedded_capped": capped, "measures": measures(scan)}
# ---- the corpus ------------------------------------------------------------------------
PROBER_FORMAT = {"isobmff": "mov", "matroska": "matroska", "mpeg-ts": "mpegts", "ogg": "ogg", "flac": "flac", "wave": "wav", "mpeg-audio": "mp3", "adts": "aac", "png": "png_pipe", "jpeg": "jpeg_pipe",
"gif": "gif", "webp": "webp_pipe", "ivf": "ivf", "y4m": "yuv4mpegpipe", "flv": "flv", "av1-obu": "obu", "avc-annexb": "h264", "hevc-annexb": "hevc", "wai.video.hevc": "hevc",
"jpegxl": "jpegxl_pipe", "jp2": "j2k_pipe", "j2k": "j2k_pipe", "tiff": "tiff_pipe", "aiff": "aiff", "caf": "caf", "mpeg-video": "mpegvideo", "mpeg-ps": "mpeg", "webvtt": "webvtt"}
def agrees(name, result, prober):
"""Where a result and the prober both state a value, what does not agree."""
p = prober.get(name)
if not p:
return []
out = []
best = result["candidates"][0] if result["candidates"] else None
if best is None:
return ["%s: the prober reads %s, and the result holds no candidate" % (name, p["format"])]
if not p["format"].startswith(PROBER_FORMAT.get(best["id"], "?")):
out.append("%s: %s against the prober's %s" % (name, best["id"], p["format"]))
if best["tracks"] and len(best["tracks"]) != len(p["streams"]):
out.append("%s: %d tracks against the prober's %d" % (name, len(best["tracks"]), len(p["streams"])))
for t in best["tracks"]:
s = next((x for x in p["streams"] if x["media"] == t["media"] and (t["codec"] is None or x["codec"] == t["codec"])), None)
if s is None:
out.append("%s: no stream of the prober's is %s %s" % (name, t["media"], t["codec"]))
continue
for k in ("width", "height", "channels", "sample_rate"):
if t[k] is not None and s.get(k) is not None and t[k] != s[k]:
out.append("%s: %s %s against the prober's %s" % (name, k, t[k], s[k]))
facts = best["facts"]
first = p["streams"][0] if p["streams"] else {}
for k in ("width", "height", "sample_rate", "channels"):
if facts.get(k) is not None and first.get(k) is not None and facts[k] != first[k]:
out.append("%s: %s %s against the prober's %s" % (name, k, facts[k], first[k]))
return out
def check(doc, files, prober):
fails = []
named = set()
results = []
for v in doc["vectors"]:
named.add(v["file"])
b = files.get(v["file"])
if b is None:
fails.append("%s is absent" % v["file"])
continue
if hashlib.sha256(b).hexdigest() != v["sha256"]:
fails.append("%s: its digest" % v["file"])
r = identify(b * v.get("repeat", 1))
results.append(r)
if r != v["result"]:
for k in sorted(set(r) | set(v["result"])):
if r.get(k) != v["result"].get(k):
fails.append("%s: %s is %s, stated %s" % (v["file"], k, json.dumps(r.get(k))[:400], json.dumps(v["result"].get(k))[:400]))
if v["origin"] == "ext":
fails += agrees(v["file"].split("/", 1)[1], r, prober)
for k in files:
if k not in named:
fails.append("%s is no vector's" % k)
stops = {c["stopped"]["why"] for r in results for c in r["candidates"] if c["stopped"]}
for why in ("overrun", "invalid", "crc", "limit", "unknown_size"):
if why not in stops:
fails.append("no vector reaches %s" % why)
verdicts = {r["verdict"] for r in results}
for k in ("identified", "ambiguous", "signature_only", "unknown"):
if k not in verdicts:
fails.append("no vector reaches %s" % k)
strengths = {c["strength"] for r in results for c in r["candidates"]}
for k in RANK:
if k not in strengths:
fails.append("no vector reaches %s" % k)
return fails
def load(corpus):
doc = json.load(open(os.path.join(corpus, "vectors.json"), encoding="utf-8"))
files = {}
for sub in ("ext", "gen"):
d = os.path.join(corpus, sub)
for n in sorted(os.listdir(d)):
files["%s/%s" % (sub, n)] = open(os.path.join(d, n), "rb").read()
prober = json.load(open(os.path.join(corpus, "prober.json"), encoding="utf-8"))
return doc, files, prober
def find(doc, name):
return next(v for v in doc["vectors"] if v["file"].endswith("/" + name))
def mutations(doc, files, prober):
"""Each yields a corpus that must fail, the fast vectors only: those past the windows are costly."""
fast = dict(doc)
fast["vectors"] = [v for v in doc["vectors"] if "repeat" not in v]
fast_files = {k: v for k, v in files.items() if not any(v2["file"] == k and "repeat" in v2 for v2 in doc["vectors"])}
def changed(name, edit):
d = copy.deepcopy(fast)
edit(find(d, name)["result"])
return d
def cand(r):
return r["candidates"][0]
yield "a result's verdict", changed("avc-aac.mp4", lambda r: r.update(verdict="ambiguous")), fast_files, prober
yield "a candidate's strength", changed("avc-aac.mp4", lambda r: cand(r).update(strength="signature")), fast_files, prober
yield "an evidence offset", changed("avc-aac.mp4", lambda r: cand(r)["evidence"][0].update(at=5)), fast_files, prober
yield "where a walk reached", changed("cut-mp4.bin", lambda r: cand(r).update(walked_to=41)), fast_files, prober
yield "why a walk stopped", changed("cut-mp4.bin", lambda r: cand(r)["stopped"].update(why="invalid")), fast_files, prober
yield "a track's codec", changed("avc-aac.mp4", lambda r: cand(r)["tracks"][0].update(codec="hevc")), fast_files, prober
yield "a track's width", changed("avc-aac.mp4", lambda r: cand(r)["tracks"][0].update(width=33)), fast_files, prober
yield "a track's protection", changed("avc-protected.mp4", lambda r: cand(r)["tracks"][0].update(protected=None)), fast_files, prober
yield "a capability", changed("avc-aac.mp4", lambda r: cand(r).update(capabilities=[])), fast_files, prober
yield "an embedded offset", changed("embedded.bin", lambda r: r["embedded"][0].update(at=1)), fast_files, prober
yield "the embedded cap", changed("embedded-capped.bin", lambda r: r.update(embedded_capped=False)), fast_files, prober
yield "a repeat", changed("records.bin", lambda r: r["measures"].update(periods=[])), fast_files, prober
yield "the chi-square", changed("noise.bin", lambda r: r["measures"].update(chi_square=1)), fast_files, prober
yield "the uniform windows", changed("noise.bin", lambda r: r["measures"].update(uniform_windows=0)), fast_files, prober
yield "whether it is text", changed("text.bin", lambda r: r["measures"].update(text=False)), fast_files, prober
yield "a probe's parameters", changed("hevc.h265", lambda r: r.update(probes_accepted=[])), fast_files, prober
yield "a stated fact", changed("a.flac", lambda r: cand(r)["facts"].update(sample_rate=44100)), fast_files, prober
d = copy.deepcopy(fast)
d["vectors"] = [v for v in d["vectors"] if not v["file"].endswith("/polyglot-annexb.bin") and not v["file"].endswith("/polyglot-signature.bin")]
yield "no vector reaching a verdict", d, {k: v for k, v in fast_files.items() if "polyglot" not in k}, prober
yield "a file not a vector's", fast, dict(fast_files, **{"gen/stray.bin": b"x"}), prober
f = dict(fast_files)
f["ext/avc-aac.mp4"] = f["ext/avc-aac.mp4"][:-1] + bytes([f["ext/avc-aac.mp4"][-1] ^ 1])
yield "a file's byte", fast, f, prober
p = copy.deepcopy(prober)
p["avc-aac.mp4"]["streams"][0]["width"] = 33
yield "the prober disagreeing on a width", fast, fast_files, p
p = copy.deepcopy(prober)
p["a.flac"]["format"] = "ogg"
yield "the prober disagreeing on the format", fast, fast_files, p
p = copy.deepcopy(prober)
p["avc-aac.mp4"]["streams"].pop()
yield "the prober seeing fewer tracks", fast, fast_files, p
def main(argv):
if len(argv) != 2:
print(__doc__)
return 2
doc, files, prober = load(argv[1])
fails = check(doc, files, prober)
for f in fails:
print("FAIL", f[:900])
print("%d vectors, %d files: %d failures" % (len(doc["vectors"]), len(files), len(fails)))
caught_all = True
for label, d, fs, p in mutations(doc, files, prober):
try:
caught = check(d, fs, p)
except Exception as e: # a crash on a mutated copy counts as a failure to read it
caught = ["raised %s" % e]
print(("PASS" if caught else "FAIL") + " mutated copy fails: %s%s" % (label, " (%s)" % caught[0][:90] if caught else ""))
caught_all &= bool(caught)
return 0 if not fails and caught_all else 1
if __name__ == "__main__":
sys.exit(main(sys.argv))