SPEC §2 states the value limit (2^22, manifest_too_many_values) beside the length and nesting limits, and what the two bound together. staged_verify, claim_verify, http_verify and pack_verify refuse past it and check both edges in memory; the HTTP corpus gains parse_head cases at the limit and one past it (pad_values), verified by wai_http_vectors and http_verify.py alike. Co-Authored-By: Claude Opus 5.5 <[email protected]>
3819 lines
174 KiB
Python
3819 lines
174 KiB
Python
#!/usr/bin/env python3
|
||
"""Independent verifier of the packaging corpus (extensions/packaging.md).
|
||
|
||
Written from the extension text. It shares no code with wai-pack, which generates the
|
||
corpus. It takes BLAKE3 from ../claim-conformance/_blake3.py and the WIV1 parser and
|
||
keyframe-0 decoder from ../int-payload-conformance/int_payload_verify.py, both written
|
||
from their specifications, reads the capability registry from its published export
|
||
(../registry/capabilities.json), reads TOML with the standard library's tomllib (TOML
|
||
1.0), checks Ed25519 signatures with ../claim-conformance/_ed25519.py, WAI's strict rule
|
||
(SPEC §7 *Signatures*) written from RFC 8032, signs its own re-derivations with the
|
||
`cryptography` package, imported only where it signs, and implements the rest:
|
||
|
||
- the policy (§3): the value-kind walk, the schema with every default, the constraints
|
||
and their refusal names, the effective policy as JSON, RFC 8785 canonical JSON and the
|
||
digest; every case of policy/ is recomputed, every equal pair compared, and every
|
||
policy of refusals/policy/ refused with its reason, the capability rules of §3.6
|
||
included;
|
||
- the sources (§4.7): a Y4M reader with BT.709 limited-range conversion to RGB8, and a
|
||
WAV reader; every source of refusals/source/ refused with its reason, and every frame
|
||
of conversions/ converted to the SHA-256 the corpus states;
|
||
- the unit conversions (§4.4, §4.5);
|
||
- the fixtures, regenerated from their definition and checked against their pinned
|
||
SHA-256, and wai.pack.scenecut/1 (§4.3) with its lookahead clauses, written in the
|
||
on-demand form (every frame known), against the streaming segmenter's vectors; and the
|
||
classification (§4.6) of every piece;
|
||
- the encode vectors: which frames each one codes (every piece of every fixture, cut by
|
||
the segmenter above), each payload's BLAKE3, its header's parameters, every decoded
|
||
frame's SHA-256, and the SSE of the decode against the regenerated source; and the
|
||
encoder's choices (§5.5) recomputed from the decode: every motion vector by the
|
||
search, its order and its tie-break, on the source and the previous decoded frame;
|
||
every coded residual (the stream's symbols) equal to quantize(source − prediction);
|
||
and the residual table and its offset equal to the fit of those residuals;
|
||
- the choice of step (§5.6): every candidate's SSE from an encoder of its own (search,
|
||
quantization, reconstruction, closed loop), and the step the rule chooses;
|
||
- the stage index (§6.2–§6.4): the WSIX reader and its refusals, the rules in their
|
||
order, each tier from the registry's replicate class, the index digest and the stage
|
||
root (a jwp-receipts §2.2 Merkle root); and the object rules of §6.3 over WAI1 and WAI2
|
||
envelopes. Every case of mutations/ (an index; an index and its objects; a receipt and
|
||
its indexes) gets its reason, and every file of mutations/ is used by a case;
|
||
- the package receipt (§9.1): its JSON reader, its signing payload with the energy class
|
||
of energy-measurement §2.7–§2.8, its receipt hash, and its verification against its
|
||
indexes; and version 1 (§9.3), every receipt of receipts/: its payload, receipt hash,
|
||
segment map root, counts and signature;
|
||
- the package (packs/, §11.3, in its order, without the source): the LISTING, the
|
||
receipt, policy.json and policy.toml, the indexes, every object, each prefix digest by
|
||
decoding its WIV1 base, the plan file, the floor objects, the CMAF files rebuilt byte
|
||
for byte from their temporal units (an AV1 OBU reader and sequence header parser, §7.6's
|
||
boxes), every catalog rendered byte for byte (§8.3–§8.8), §9.4's claim rule, and no
|
||
unlisted or unexpected file; then what only the source decides: the fixture
|
||
regenerated and cut, each piece's start, duration, flags and kind, its rules, moments
|
||
and class plan (§8.1), each base's sse (§6.6) and step (§5.6) and its decode equal to the
|
||
encoder's reconstruction, and the supplied floor (§7.3): its listing, a temporal unit
|
||
per frame, shown key frames exactly at the piece starts, and each floor object holding
|
||
its piece's units of the stream;
|
||
- the package mutations (package-mutations/): each case the golden altered (re-sealed
|
||
with its key where the receipt or LISTING alone would catch it), written out and
|
||
verified as above without its inputs, and refused with the reason the corpus gives it
|
||
(the first check that fails), or accepted where the corpus says `ok`.
|
||
|
||
python3 pack_verify.py <pack-conformance dir>
|
||
|
||
Exit status 0 only if every vector verifies. Python 3.11 or later (tomllib).
|
||
"""
|
||
import hashlib
|
||
import itertools
|
||
import json
|
||
import os
|
||
import re
|
||
import shutil
|
||
import struct
|
||
import sys
|
||
import tempfile
|
||
import tomllib
|
||
import zlib
|
||
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
sys.path.insert(0, os.path.join(HERE, "..", "claim-conformance"))
|
||
sys.path.insert(0, os.path.join(HERE, "..", "int-payload-conformance"))
|
||
sys.path.insert(0, os.path.join(HERE, "..", "staged-conformance"))
|
||
sys.path.insert(0, os.path.join(HERE, "..", "staged-measure-conformance"))
|
||
from _blake3 import blake3 # noqa: E402
|
||
import _ed25519 as ED # noqa: E402
|
||
import int_payload_verify as IPV # noqa: E402
|
||
import staged_verify as SV # noqa: E402
|
||
import sim_verify as SIM # noqa: E402
|
||
sys.path.insert(0, os.path.join(HERE, "..", "http-conformance"))
|
||
import http_verify as HV # noqa: E402
|
||
HV.load_registry()
|
||
|
||
MAX_INTEGER = (1 << 53) - 1
|
||
# The longest manifest a reader must accept (SPEC §2, *Reader limits*).
|
||
MANIFEST_MAX = 1 << 27
|
||
# The most JSON values a manifest may hold that a reader must accept (SPEC §2, *Reader
|
||
# limits*): the manifest and every value in it, at any depth, and no member name.
|
||
MANIFEST_MAX_VALUES = 1 << 22
|
||
U32 = (1 << 32) - 1
|
||
# The frames of each piece an encode vector codes: the first ENCODE_FRAMES.
|
||
ENCODE_FRAMES = 12
|
||
|
||
# ---------------------------------------------------------------- canonical JSON (RFC 8785)
|
||
|
||
|
||
def jcs(v):
|
||
if v is None:
|
||
return "null"
|
||
if v is True:
|
||
return "true"
|
||
if v is False:
|
||
return "false"
|
||
if isinstance(v, int):
|
||
if not 0 <= v <= MAX_INTEGER:
|
||
raise ValueError("an integer outside the canonical subset")
|
||
return str(v)
|
||
if isinstance(v, str):
|
||
return json.dumps(v, ensure_ascii=False)
|
||
if isinstance(v, list):
|
||
return "[" + ",".join(jcs(x) for x in v) + "]"
|
||
if isinstance(v, dict):
|
||
keys = sorted(v, key=lambda k: k.encode("utf-16-be"))
|
||
return "{" + ",".join(json.dumps(k, ensure_ascii=False) + ":" + jcs(v[k]) for k in keys) + "}"
|
||
raise ValueError("a value outside the canonical subset")
|
||
|
||
|
||
# ---------------------------------------------------------------- policy (§3)
|
||
|
||
|
||
class Refused(Exception):
|
||
def __init__(self, code, at=""):
|
||
super().__init__(code)
|
||
self.code = code
|
||
self.at = at
|
||
|
||
|
||
def is_int(v):
|
||
return isinstance(v, int) and not isinstance(v, bool)
|
||
|
||
|
||
def kinds(v, at):
|
||
"""Stage 1: the value kinds canonical JSON cannot carry, members in key order."""
|
||
import datetime
|
||
|
||
if isinstance(v, float):
|
||
raise Refused("policy-float", at)
|
||
if isinstance(v, (datetime.datetime, datetime.date, datetime.time)):
|
||
raise Refused("policy-datetime", at)
|
||
if is_int(v) and v < 0:
|
||
raise Refused("policy-negative", at)
|
||
if is_int(v) and v > MAX_INTEGER:
|
||
raise Refused("policy-integer-range", at)
|
||
if isinstance(v, list):
|
||
for i, x in enumerate(v):
|
||
kinds(x, f"{at}[{i}]")
|
||
if isinstance(v, dict):
|
||
for k in sorted(v):
|
||
kinds(v[k], f"{at}.{k}" if at else k)
|
||
|
||
|
||
class Tab:
|
||
def __init__(self, t, at):
|
||
self.t, self.at = t, at
|
||
|
||
def err(self, code, k):
|
||
return Refused(code, f"{self.at}.{k}" if self.at else k)
|
||
|
||
def keys(self, allowed):
|
||
for k in sorted(self.t):
|
||
if k not in allowed:
|
||
raise self.err("policy-unknown-key", k)
|
||
|
||
def string(self, k):
|
||
if k not in self.t:
|
||
return None
|
||
if not isinstance(self.t[k], str):
|
||
raise self.err("policy-type", k)
|
||
return self.t[k]
|
||
|
||
def string_req(self, k):
|
||
s = self.string(k)
|
||
if s is None:
|
||
raise self.err("policy-missing-key", k)
|
||
return s
|
||
|
||
def ident(self, k):
|
||
s = self.string_req(k)
|
||
if not s or len(s.encode()) > 255:
|
||
raise self.err("policy-value", k)
|
||
return s
|
||
|
||
def uint(self, k):
|
||
if k not in self.t:
|
||
return None
|
||
if not is_int(self.t[k]):
|
||
raise self.err("policy-type", k)
|
||
return self.t[k]
|
||
|
||
def uint_in(self, k, lo, hi, default):
|
||
v = self.uint(k)
|
||
if v is None:
|
||
if default is None:
|
||
raise self.err("policy-missing-key", k)
|
||
return default
|
||
if v < lo or v > hi:
|
||
raise self.err("policy-value", k)
|
||
return v
|
||
|
||
def uint_opt_in(self, k, lo, hi):
|
||
v = self.uint(k)
|
||
if v is not None and (v < lo or v > hi):
|
||
raise self.err("policy-value", k)
|
||
return v
|
||
|
||
def boolean(self, k, default):
|
||
if k not in self.t:
|
||
return default
|
||
if not isinstance(self.t[k], bool):
|
||
raise self.err("policy-type", k)
|
||
return self.t[k]
|
||
|
||
def table(self, k):
|
||
if k not in self.t:
|
||
return Tab({}, f"{self.at}.{k}" if self.at else k)
|
||
if not isinstance(self.t[k], dict):
|
||
raise self.err("policy-type", k)
|
||
return Tab(self.t[k], f"{self.at}.{k}" if self.at else k)
|
||
|
||
def table_req(self, k):
|
||
if k not in self.t:
|
||
raise self.err("policy-missing-key", k)
|
||
return self.table(k)
|
||
|
||
def tables(self, k):
|
||
if k not in self.t:
|
||
return []
|
||
v = self.t[k]
|
||
if not isinstance(v, list) or not all(isinstance(x, dict) for x in v):
|
||
raise self.err("policy-type", k)
|
||
return [Tab(x, f"{k}[{i}]") for i, x in enumerate(v)]
|
||
|
||
def strings(self, k):
|
||
if k not in self.t:
|
||
return None
|
||
v = self.t[k]
|
||
if not isinstance(v, list) or not all(isinstance(x, str) for x in v):
|
||
raise self.err("policy-type", k)
|
||
return list(v)
|
||
|
||
def uints(self, k):
|
||
if k not in self.t:
|
||
return None
|
||
v = self.t[k]
|
||
if not isinstance(v, list) or not all(is_int(x) for x in v):
|
||
raise self.err("policy-type", k)
|
||
return list(v)
|
||
|
||
|
||
# §3.3: the parameters each capability's rung takes; another capability takes none.
|
||
PARAMS = {
|
||
"wai.video.int_motion": [
|
||
("block", ("one_of", [4, 8, 16, 32], 16)),
|
||
("search", ("int", 0, 32, 7)),
|
||
("mv_precision", ("one_of", [1, 2], 2)),
|
||
("q_candidates", ("ints", 1, 255, [8])),
|
||
("max_sse_px_milli", ("int", 0, MAX_INTEGER, 0)),
|
||
],
|
||
"wai.video.int_refine": [("q", ("int", 1, 255, None))],
|
||
"wai.video.av1": [("source", ("word", ["supplied", "encode"], "supplied"))],
|
||
"wai.video.av1.lossless": [("source", ("word", ["supplied", "encode"], "supplied"))],
|
||
"wai.audio.int_refine": [("to_rate", ("int", 1, 384000, None)), ("q", ("int", 1, 65535, None))],
|
||
"wai.audio.flac": [("source", ("word", ["encode", "supplied"], "encode"))],
|
||
}
|
||
|
||
MEDIA = ["video", "image", "audio", "world", "feed", "avatar", "film"]
|
||
KINDS = ["natural", "static", "synthetic", "motion_high"]
|
||
|
||
|
||
def read_params(rung, cap):
|
||
p = rung.table("params")
|
||
schema = PARAMS.get(cap, [])
|
||
p.keys([k for k, _ in schema])
|
||
out = {}
|
||
for k, kind in schema:
|
||
if kind[0] == "int":
|
||
out[k] = p.uint_in(k, kind[1], kind[2], kind[3])
|
||
elif kind[0] == "one_of":
|
||
v = p.uint_in(k, 0, MAX_INTEGER, kind[2])
|
||
if v not in kind[1]:
|
||
raise p.err("policy-value", k)
|
||
out[k] = v
|
||
elif kind[0] == "ints":
|
||
v = p.uints(k)
|
||
v = list(kind[3]) if v is None else v
|
||
if not v or any(x < kind[1] or x > kind[2] for x in v):
|
||
raise p.err("policy-value", k)
|
||
out[k] = v
|
||
else:
|
||
v = p.string(k)
|
||
v = kind[2] if v is None else v
|
||
if v not in kind[1]:
|
||
raise p.err("policy-value", k)
|
||
out[k] = v
|
||
return out
|
||
|
||
|
||
def is_hex64(s):
|
||
return len(s) == 64 and all(c in "0123456789abcdef" for c in s)
|
||
|
||
|
||
def read_policy(doc):
|
||
"""Stage 2. Returns the policy as a dict of its JSON members (layer_lag_ms unchecked)."""
|
||
top = Tab(doc, "")
|
||
top.keys(["schema", "asset_id", "royalty_free_only", "lead", "component", "segmentation", "ladder", "floor",
|
||
"live", "class", "moment", "rule", "defaults", "classify"])
|
||
if top.string_req("schema") != "wai.pack.policy/1":
|
||
raise top.err("policy-schema", "schema")
|
||
P = {"schema": "wai.pack.policy/1", "asset_id": top.ident("asset_id"),
|
||
"royalty_free_only": top.boolean("royalty_free_only", False), "lead": top.ident("lead")}
|
||
if "component" not in doc:
|
||
raise top.err("policy-missing-key", "component")
|
||
comps = []
|
||
for c in top.tables("component"):
|
||
c.keys(["id", "media", "source", "timebase"])
|
||
m = {"id": c.ident("id")}
|
||
media = c.string_req("media")
|
||
if media not in MEDIA:
|
||
raise c.err("policy-value", "media")
|
||
m["media"] = media
|
||
s = c.string("source")
|
||
if s is not None:
|
||
m["source"] = s
|
||
tb = c.table_req("timebase")
|
||
tb.keys(["num", "den"])
|
||
m["timebase"] = {"num": tb.uint_in("num", 1, U32, None), "den": tb.uint_in("den", 1, U32, None)}
|
||
comps.append(m)
|
||
P["component"] = comps
|
||
s = top.table("segmentation")
|
||
s.keys(["target_ms", "min_ms", "max_ms", "scene_cut", "cut_abs_permille", "cut_rel_permille", "cut_window",
|
||
"lookahead_frames", "closed"])
|
||
seg = {"target_ms": s.uint_in("target_ms", 0, MAX_INTEGER, 2000),
|
||
"min_ms": s.uint_in("min_ms", 0, MAX_INTEGER, 1000),
|
||
"max_ms": s.uint_in("max_ms", 0, MAX_INTEGER, 4000)}
|
||
sc = s.string("scene_cut")
|
||
sc = "wai.pack.scenecut/1" if sc is None else sc
|
||
if sc != "wai.pack.scenecut/1":
|
||
raise s.err("policy-value", "scene_cut")
|
||
seg["scene_cut"] = sc
|
||
seg["cut_abs_permille"] = s.uint_in("cut_abs_permille", 1, 1000, 300)
|
||
seg["cut_rel_permille"] = s.uint_in("cut_rel_permille", 0, 1000000, 3000)
|
||
seg["cut_window"] = s.uint_in("cut_window", 3, 64, 8)
|
||
seg["lookahead_frames"] = s.uint_in("lookahead_frames", 0, 64, 6)
|
||
seg["closed"] = s.boolean("closed", True)
|
||
P["segmentation"] = seg
|
||
ladder = []
|
||
for r in top.tables("ladder"):
|
||
r.keys(["id", "component", "capability", "role", "refines", "params"])
|
||
m = {"id": r.ident("id"), "component": r.ident("component"), "capability": r.string_req("capability")}
|
||
role = r.string_req("role")
|
||
if role not in ("base", "refinement", "floor"):
|
||
raise r.err("policy-value", "role")
|
||
m["role"] = role
|
||
t = r.string("refines")
|
||
if t is not None:
|
||
m["refines"] = t
|
||
m["params"] = read_params(r, m["capability"])
|
||
ladder.append(m)
|
||
P["ladder"] = ladder
|
||
f = top.table("floor")
|
||
f.keys(["supplied_listing", "extra_keyframes"])
|
||
fl = {}
|
||
sl = f.string("supplied_listing")
|
||
if sl is not None:
|
||
if not is_hex64(sl):
|
||
raise f.err("policy-value", "supplied_listing")
|
||
fl["supplied_listing"] = sl
|
||
fl["extra_keyframes"] = f.boolean("extra_keyframes", False)
|
||
P["floor"] = fl
|
||
lv = top.table("live")
|
||
lv.keys(["layer_lag_ms"])
|
||
lag = lv.uints("layer_lag_ms")
|
||
classes = []
|
||
for c in top.tables("class"):
|
||
c.keys(["id", "max_layer", "max_kbps", "floor_only"])
|
||
m = {"id": c.ident("id"), "max_layer": c.uint_in("max_layer", 0, 255, 0)}
|
||
k = c.uint_opt_in("max_kbps", 1, U32)
|
||
if k is not None:
|
||
m["max_kbps"] = k
|
||
m["floor_only"] = c.boolean("floor_only", False)
|
||
classes.append(m)
|
||
P["class"] = classes
|
||
moments = []
|
||
for m in top.tables("moment"):
|
||
m.keys(["id", "from_ms", "to_ms", "tags"])
|
||
x = {"id": m.ident("id"), "from_ms": m.uint_in("from_ms", 0, MAX_INTEGER, None),
|
||
"to_ms": m.uint_in("to_ms", 0, MAX_INTEGER, None)}
|
||
if x["to_ms"] <= x["from_ms"]:
|
||
raise m.err("policy-value", "to_ms")
|
||
x["tags"] = m.strings("tags") or []
|
||
moments.append(x)
|
||
P["moment"] = moments
|
||
rules = []
|
||
for r in top.tables("rule"):
|
||
r.keys(["id", "when", "then"])
|
||
x = {"id": r.ident("id")}
|
||
w = r.table("when")
|
||
w.keys(["piece_kind", "moment_tag"])
|
||
when = {}
|
||
pk = w.strings("piece_kind")
|
||
if pk is not None:
|
||
if any(k not in KINDS for k in pk):
|
||
raise w.err("policy-value", "piece_kind")
|
||
when["piece_kind"] = pk
|
||
mt = w.strings("moment_tag")
|
||
if mt is not None:
|
||
when["moment_tag"] = mt
|
||
t = r.table("then")
|
||
t.keys(["ladder", "max_layer", "deadline_ms"])
|
||
then = {}
|
||
ld = t.strings("ladder")
|
||
if ld is not None:
|
||
then["ladder"] = ld
|
||
ml = t.uint_opt_in("max_layer", 0, 255)
|
||
if ml is not None:
|
||
then["max_layer"] = ml
|
||
dl = t.uint_opt_in("deadline_ms", 1, U32)
|
||
if dl is not None:
|
||
then["deadline_ms"] = dl
|
||
x["when"], x["then"] = when, then
|
||
rules.append(x)
|
||
P["rule"] = rules
|
||
d = top.table("defaults")
|
||
d.keys(["deadline_ms"])
|
||
P["defaults"] = {"deadline_ms": d.uint_in("deadline_ms", 1, U32, 1500)}
|
||
c = top.table("classify")
|
||
c.keys(["static_permille", "static_mad_milli", "synthetic_bins", "motion_high_milli"])
|
||
P["classify"] = {"static_permille": c.uint_in("static_permille", 0, 1000, 5),
|
||
"static_mad_milli": c.uint_in("static_mad_milli", 0, MAX_INTEGER, 300),
|
||
"synthetic_bins": c.uint_in("synthetic_bins", 0, 64, 12),
|
||
"motion_high_milli": c.uint_in("motion_high_milli", 0, MAX_INTEGER, 9000)}
|
||
return P, lag
|
||
|
||
|
||
def first_dup(ids):
|
||
seen = set()
|
||
for i in ids:
|
||
if i in seen:
|
||
return i
|
||
seen.add(i)
|
||
return None
|
||
|
||
|
||
def layers(P):
|
||
by = {r["id"]: r for r in P["ladder"]}
|
||
out = {}
|
||
for r in P["ladder"]:
|
||
k, cur = 0, r
|
||
while "refines" in cur and cur["refines"] in by:
|
||
k, cur = k + 1, by[cur["refines"]]
|
||
out[r["id"]] = k
|
||
return out
|
||
|
||
|
||
def constrain(P, lag):
|
||
"""Stage 3, in the order of the extension's list."""
|
||
def no(code):
|
||
raise Refused(code)
|
||
if not P["component"] or len(P["component"]) > 8:
|
||
no("policy-too-many-components")
|
||
if len(P["ladder"]) > 64:
|
||
no("policy-too-many-rungs")
|
||
if len(P["class"]) > 32:
|
||
no("policy-too-many-classes")
|
||
if len(P["rule"]) > 32:
|
||
no("policy-too-many-rules")
|
||
if len(P["moment"]) > 32:
|
||
no("policy-too-many-moments")
|
||
# One namespace for every id, so no reference names two things.
|
||
if first_dup(x["id"] for k in ("component", "ladder", "class", "moment", "rule") for x in P[k]) is not None:
|
||
no("policy-duplicate-id")
|
||
if P["lead"] not in [c["id"] for c in P["component"]]:
|
||
no("policy-lead")
|
||
s = P["segmentation"]
|
||
if not s["closed"]:
|
||
no("policy-open-gop")
|
||
if not (1 <= s["min_ms"] <= s["target_ms"] <= s["max_ms"]):
|
||
no("policy-piece-bounds")
|
||
# §4.4: at most ten minutes, and at most 2^24 units on every component's timebase.
|
||
if s["max_ms"] > 600000 or any(units_from_ms(s["max_ms"], c["timebase"]["num"], c["timebase"]["den"]) > 1 << 24
|
||
for c in P["component"]):
|
||
no("policy-piece-bounds")
|
||
comps = {c["id"] for c in P["component"]}
|
||
if any(r["component"] not in comps for r in P["ladder"]):
|
||
no("policy-ladder-component")
|
||
if any((r["role"] == "refinement") != ("refines" in r) for r in P["ladder"]):
|
||
no("policy-refines-role")
|
||
by = {r["id"]: r for r in P["ladder"]}
|
||
if any("refines" in r and r["refines"] not in by for r in P["ladder"]):
|
||
no("policy-dangling-refines")
|
||
for r in P["ladder"]:
|
||
seen, cur = {r["id"]}, r
|
||
while "refines" in cur:
|
||
if cur["refines"] in seen:
|
||
no("policy-refines-cycle")
|
||
seen.add(cur["refines"])
|
||
cur = by[cur["refines"]]
|
||
for r in P["ladder"]:
|
||
if r["role"] != "refinement":
|
||
continue
|
||
cur = r
|
||
while "refines" in cur:
|
||
cur = by[cur["refines"]]
|
||
if cur["component"] != r["component"]:
|
||
no("policy-refines-base")
|
||
if cur["role"] != "base":
|
||
no("policy-refines-base")
|
||
if first_dup(r["refines"] for r in P["ladder"] if "refines" in r) is not None:
|
||
no("policy-refines-branch")
|
||
for c in P["component"]:
|
||
if not any(r["component"] == c["id"] and r["role"] == "base" for r in P["ladder"]):
|
||
no("policy-no-base")
|
||
n = max(layers(P).values(), default=0) + 1
|
||
if any(c["max_layer"] > n - 1 for c in P["class"]):
|
||
no("policy-class-layer")
|
||
if any(r["then"].get("max_layer", 0) > n - 1 for r in P["rule"]):
|
||
no("policy-class-layer")
|
||
for r in P["rule"]:
|
||
if "ladder" in r["then"]:
|
||
ids = r["then"]["ladder"]
|
||
rs = [by.get(i) for i in ids]
|
||
# One component, each rung once, exactly one base, every refinement over a
|
||
# rung it names, and every floor rung of its component (§3.4 item 10).
|
||
ok = rs and all(rs) and all(x["component"] == rs[0]["component"] for x in rs) \
|
||
and len(set(ids)) == len(ids) and sum(x["role"] == "base" for x in rs) == 1 \
|
||
and all(x.get("refines", ids[0]) in ids for x in rs) \
|
||
and all(f["id"] in ids for f in P["ladder"] if f["component"] == rs[0]["component"] and f["role"] == "floor")
|
||
if not ok:
|
||
no("policy-rule-ladder")
|
||
lag = [0] * n if lag is None else lag
|
||
if len(lag) != n or lag[0] != 0 or any(b < a for a, b in zip(lag, lag[1:])):
|
||
no("policy-layer-lag")
|
||
P["live"] = {"layer_lag_ms": lag}
|
||
deadlines = {r["then"]["deadline_ms"] for r in P["rule"] if "deadline_ms" in r["then"]}
|
||
deadlines.add(P["defaults"]["deadline_ms"])
|
||
if len(deadlines) > 4:
|
||
no("policy-too-many-deadlines")
|
||
for d in deadlines:
|
||
if any(d < x + 1 for x in lag[1:]):
|
||
no("policy-deadline-unreachable")
|
||
return P
|
||
|
||
|
||
MAX_DEPTH = 32
|
||
I64 = (-(1 << 63), (1 << 63) - 1)
|
||
|
||
|
||
def shallow(v, depth):
|
||
"""Every container in v, itself at depth `depth`, at most MAX_DEPTH deep (§3.2). The
|
||
recursion stops at the first container past it."""
|
||
if isinstance(v, list):
|
||
return depth <= MAX_DEPTH and all(shallow(x, depth + 1) for x in v)
|
||
if isinstance(v, dict):
|
||
return depth <= MAX_DEPTH and all(shallow(x, depth + 1) for x in v.values())
|
||
return True
|
||
|
||
|
||
def in_i64(v):
|
||
"""TOML's integers are 64-bit (TOML 1.0); tomllib reads any size."""
|
||
if is_int(v):
|
||
return I64[0] <= v <= I64[1]
|
||
if isinstance(v, list):
|
||
return all(in_i64(x) for x in v)
|
||
if isinstance(v, dict):
|
||
return all(in_i64(x) for x in v.values())
|
||
return True
|
||
|
||
|
||
def parse_policy(text):
|
||
"""§3.2: not TOML 1.0, a byte-order mark, an integer outside 64 bits, or nesting past
|
||
MAX_DEPTH are `policy-toml`, before any value kind; then the three stages."""
|
||
if text.startswith("\ufeff"):
|
||
raise Refused("policy-toml")
|
||
try:
|
||
doc = tomllib.loads(text)
|
||
except (tomllib.TOMLDecodeError, RecursionError):
|
||
raise Refused("policy-toml")
|
||
if not all(shallow(v, 2) for v in doc.values()) or not in_i64(doc):
|
||
raise Refused("policy-toml")
|
||
for k in sorted(doc):
|
||
kinds(doc[k], k)
|
||
P, lag = read_policy(doc)
|
||
return constrain(P, lag)
|
||
|
||
|
||
def parse_policy_bytes(b):
|
||
"""A policy's bytes: UTF-8 with no byte-order mark (§3.2)."""
|
||
if b.startswith(b"\xef\xbb\xbf"):
|
||
raise Refused("policy-toml")
|
||
try:
|
||
text = b.decode("utf-8")
|
||
except UnicodeDecodeError:
|
||
raise Refused("policy-toml")
|
||
return parse_policy(text)
|
||
|
||
|
||
def digest(P):
|
||
return blake3(b"wai:pack-policy\x01" + jcs(P).encode()).hex()
|
||
|
||
|
||
# ---------------------------------------------------------------- what a packager may emit (§3.6)
|
||
|
||
with open(os.path.join(HERE, "..", "registry", "capabilities.json"), "rb") as _f:
|
||
REGISTRY = {c["capability"]: c for c in json.loads(_f.read())["capabilities"]}
|
||
|
||
|
||
def rung_refusal(cap, role, royalty_free_only):
|
||
r = REGISTRY.get(cap)
|
||
if r is None:
|
||
return f"capability-unknown:{cap}"
|
||
if r["kind"] in ("record", "derivation"):
|
||
return f"capability-not-payload:{cap}"
|
||
if r["status"] == "candidate":
|
||
return f"capability-candidate:{cap}"
|
||
if r["role"] != ("refinement" if role == "refinement" else "standalone"):
|
||
return f"capability-role:{cap}"
|
||
if royalty_free_only:
|
||
if r["licence"] is None:
|
||
return f"license-unclassified:{cap}"
|
||
if r["licence"] != "royalty-free":
|
||
return f"license-refused:{cap}:{r['licence']}"
|
||
return None
|
||
|
||
|
||
def admit(b):
|
||
"""A packager's verdict on a policy's bytes: the policy's refusal, or the first rung's."""
|
||
P = parse_policy_bytes(b)
|
||
for r in P["ladder"]:
|
||
code = rung_refusal(r["capability"], r["role"], P["royalty_free_only"])
|
||
if code:
|
||
raise Refused(code)
|
||
return P
|
||
|
||
|
||
# ---------------------------------------------------------------- sources (§4.7)
|
||
|
||
|
||
def clip(v):
|
||
return 0 if v < 0 else 255 if v > 255 else v
|
||
|
||
|
||
def bt709(y, cb, cr):
|
||
y, cb, cr = y - 16, cb - 128, cr - 128
|
||
return (clip((298 * y + 459 * cr + 128) >> 8), clip((298 * y - 55 * cb - 136 * cr + 128) >> 8),
|
||
clip((298 * y + 541 * cb + 128) >> 8))
|
||
|
||
|
||
def line(b, pos, at_end):
|
||
"""A line of at most 1024 bytes and its newline; None at the end of the bytes."""
|
||
if pos >= len(b):
|
||
return None, pos
|
||
nl = b.find(b"\n", pos, pos + 1025)
|
||
if nl < 0:
|
||
raise Refused(at_end)
|
||
return b[pos:nl], nl + 1
|
||
|
||
|
||
def u32_digits(s):
|
||
if not s or len(s) > 10 or not s.isdigit():
|
||
return None
|
||
v = int(s)
|
||
return v if v <= U32 else None
|
||
|
||
|
||
def read_y4m(b):
|
||
"""(w, h, rate, chroma, frames as RGB8 bytes)."""
|
||
l, pos = line(b, 0, "y4m-header")
|
||
if l is None:
|
||
raise Refused("y4m-header")
|
||
parts = l.split(b" ")
|
||
if parts[0] != b"YUV4MPEG2":
|
||
raise Refused("y4m-header")
|
||
w = h = rate = None
|
||
chroma = "420"
|
||
for p in parts[1:]:
|
||
if not p:
|
||
continue
|
||
tag, v = p[:1], p[1:]
|
||
if tag == b"W" or tag == b"H":
|
||
x = u32_digits(v)
|
||
if x is None:
|
||
raise Refused("y4m-header")
|
||
if tag == b"W":
|
||
w = x
|
||
else:
|
||
h = x
|
||
elif tag == b"F":
|
||
nd = v.split(b":")
|
||
n = u32_digits(nd[0]) if len(nd) == 2 else None
|
||
d = u32_digits(nd[1]) if len(nd) == 2 else None
|
||
if not n or not d:
|
||
raise Refused("y4m-header")
|
||
rate = (n, d)
|
||
elif tag == b"I" and v != b"p":
|
||
raise Refused("y4m-interlaced")
|
||
elif tag == b"C":
|
||
if v in (b"420jpeg", b"420paldv", b"420mpeg2", b"420"):
|
||
chroma = "420"
|
||
elif v == b"444":
|
||
chroma = "444"
|
||
else:
|
||
raise Refused("y4m-colorspace")
|
||
if not w or not h or w * h > (1 << 26) // 3:
|
||
raise Refused("y4m-header")
|
||
cw, ch = (w, h) if chroma == "444" else ((w + 1) // 2, (h + 1) // 2)
|
||
size = w * h + 2 * cw * ch
|
||
frames = []
|
||
while True:
|
||
l, pos = line(b, pos, "y4m-truncated")
|
||
if l is None:
|
||
break
|
||
parts = l.split(b" ")
|
||
if parts[0] != b"FRAME":
|
||
raise Refused("y4m-truncated")
|
||
for p in parts[1:]:
|
||
if p[:1] in (b"W", b"H", b"C"):
|
||
raise Refused("y4m-size-change")
|
||
if p[:1] == b"I" and p[1:] != b"p":
|
||
raise Refused("y4m-interlaced")
|
||
if pos + size > len(b):
|
||
raise Refused("y4m-truncated")
|
||
yp, up, vp = b[pos:pos + w * h], b[pos + w * h:pos + w * h + cw * ch], b[pos + w * h + cw * ch:pos + size]
|
||
pos += size
|
||
rgb = bytearray()
|
||
for y in range(h):
|
||
for x in range(w):
|
||
ci = y * w + x if chroma == "444" else (y >> 1) * cw + (x >> 1)
|
||
rgb.extend(bt709(yp[y * w + x], up[ci], vp[ci]))
|
||
frames.append(bytes(rgb))
|
||
return w, h, rate, chroma, frames
|
||
|
||
|
||
def read_wav(b):
|
||
if len(b) < 12 or b[0:4] != b"RIFF" or b[8:12] != b"WAVE":
|
||
raise Refused("wav-format")
|
||
pos, fmt = 12, None
|
||
while True:
|
||
if pos + 8 > len(b):
|
||
raise Refused("wav-truncated")
|
||
cid, ln = b[pos:pos + 4], int.from_bytes(b[pos + 4:pos + 8], "little")
|
||
pos += 8
|
||
if cid == b"fmt ":
|
||
if not 16 <= ln <= 40:
|
||
raise Refused("wav-format")
|
||
if pos + ln > len(b):
|
||
raise Refused("wav-truncated")
|
||
f = b[pos:pos + ln]
|
||
tag, ch, rate = int.from_bytes(f[0:2], "little"), int.from_bytes(f[2:4], "little"), int.from_bytes(f[4:8], "little")
|
||
align, bits = int.from_bytes(f[12:14], "little"), int.from_bytes(f[14:16], "little")
|
||
if tag != 1 or bits != 16 or ch == 0 or rate == 0 or align != 2 * ch:
|
||
raise Refused("wav-format")
|
||
fmt = (ch, rate)
|
||
pos += ln + (ln & 1)
|
||
elif cid == b"data":
|
||
if fmt is None or ln % (2 * fmt[0]):
|
||
raise Refused("wav-format")
|
||
if pos + ln > len(b):
|
||
raise Refused("wav-truncated")
|
||
return fmt, b[pos:pos + ln]
|
||
else:
|
||
pos += ln + (ln & 1)
|
||
if pos > len(b):
|
||
raise Refused("wav-truncated")
|
||
|
||
|
||
def source_verdict(name, b):
|
||
try:
|
||
read_y4m(b) if name.endswith(".y4m") else read_wav(b)
|
||
return "ok"
|
||
except Refused as r:
|
||
return r.code
|
||
|
||
|
||
# ---------------------------------------------------------------- unit conversions (§4.4, §4.5)
|
||
|
||
|
||
U64 = (1 << 64) - 1
|
||
|
||
|
||
def units_from_ms(ms, num, den):
|
||
return min(max(ms * num // (1000 * den), 1), U64)
|
||
|
||
|
||
def align(start, lead, comp):
|
||
return min(start * comp[0] * lead[1] // (comp[1] * lead[0]), U64)
|
||
|
||
|
||
# ---------------------------------------------------------------- fixtures
|
||
|
||
|
||
M32 = 0xFFFFFFFF
|
||
|
||
|
||
def mix(v):
|
||
v ^= v >> 16
|
||
v = (v * 0x7FEB352D) & M32
|
||
v ^= v >> 15
|
||
v = (v * 0x846CA68B) & M32
|
||
v ^= v >> 16
|
||
return v
|
||
|
||
|
||
def noise(seed, x, y, t):
|
||
return mix(seed ^ mix(x ^ mix(y ^ mix(t))))
|
||
|
||
|
||
def ramp(lo, hi, dx, dy, speed, jitter, seed, cb, cr):
|
||
def y_of(x, y, t):
|
||
v = (x * dx + y * dy + t * speed) % 32
|
||
base = lo + v * (hi - lo) // 31
|
||
if jitter:
|
||
return min(max(max(base + noise(seed, x, y, t) % 3 - 1, 0), 16), 235)
|
||
return base
|
||
return y_of, (cb, cr)
|
||
|
||
|
||
def blocks(levels, bw, bh, speed, cb, cr):
|
||
return (lambda x, y, t: levels[((x + t * speed) // bw + y // bh) % 3]), (cb, cr)
|
||
|
||
|
||
def noise_shot(lo, hi, seed, cb, cr):
|
||
return (lambda x, y, t: lo + noise(seed, x, y, t) % (hi - lo + 1)), (cb, cr)
|
||
|
||
|
||
# A span's optional fourth member: LOCAL reads `t` as the frame's index within the span
|
||
# (0..n-1) instead of the global frame index, so two LOCAL spans of one shot are equal
|
||
# frame for frame.
|
||
LOCAL = "local"
|
||
|
||
|
||
def fixture_spans(name):
|
||
"""(w, h, spans): each span (frames, shot, blend target or None[, LOCAL])."""
|
||
if name == "video-base-01":
|
||
shot = blocks([40, 120, 200], 8, 6, 1, 100, 160)
|
||
return 64, 36, [(60, ramp(20, 200, 1, 1, 1, True, 11, 110, 140), None),
|
||
(25, shot, None, LOCAL),
|
||
(50, ramp(30, 220, 2, 1, 1, True, 12, 150, 120), None),
|
||
(25, shot, None, LOCAL),
|
||
(40, ramp(16, 160, 1, 2, 1, True, 13, 128, 128), None)]
|
||
if name == "video-staged-01":
|
||
shot = blocks([40, 120, 200], 8, 6, 1, 100, 160)
|
||
return 64, 36, [(50, ramp(20, 200, 1, 1, 1, True, 21, 110, 140), None),
|
||
(25, shot, None, LOCAL),
|
||
(40, ramp(60, 180, 2, 1, 0, False, 0, 140, 116), None),
|
||
(25, shot, None, LOCAL),
|
||
(60, ramp(30, 120, 1, 2, 1, True, 22, 150, 120), None)]
|
||
if name == "cuts-01":
|
||
a = ramp(20, 100, 1, 1, 1, True, 1, 110, 140)
|
||
b = ramp(120, 220, 2, 1, 1, True, 2, 150, 120)
|
||
flash = noise_shot(228, 235, 3, 128, 128)
|
||
c = ramp(30, 90, 1, 2, 1, True, 4, 120, 120)
|
||
d = ramp(150, 230, 1, 0, 2, True, 5, 136, 136)
|
||
e = ramp(40, 120, 0, 1, 1, True, 6, 100, 150)
|
||
f = ramp(140, 200, 1, 0, 1, True, 7, 160, 110)
|
||
return 32, 18, [(60, a, None), (30, b, None), (6, flash, None), (44, b, None), (20, c, None), (40, c, d),
|
||
(30, d, None), (30, e, None), (30, f, None), (30, e, None), (30, f, None)]
|
||
if name == "kinds-01":
|
||
return 32, 18, [(40, ramp(20, 200, 1, 1, 0, False, 0, 128, 128), None),
|
||
(40, blocks([40, 120, 200], 8, 6, 1, 128, 128), None),
|
||
(40, noise_shot(16, 235, 7, 128, 128), None),
|
||
(40, ramp(16, 80, 1, 1, 1, True, 8, 128, 128), None),
|
||
(130, ramp(60, 180, 2, 1, 0, False, 0, 140, 116), None)]
|
||
return None
|
||
|
||
|
||
def fixture(name):
|
||
w, h, spans = fixture_spans(name)
|
||
cw, ch = (w + 1) // 2, (h + 1) // 2
|
||
out = bytearray(f"YUV4MPEG2 W{w} H{h} F25:1 Ip A1:1 C420jpeg\n".encode())
|
||
t = 0
|
||
for span in spans:
|
||
n, (y_of, chroma), to = span[:3]
|
||
local = span[3:] == (LOCAL,)
|
||
for i in range(n):
|
||
out += b"FRAME\n"
|
||
def blend(a, b):
|
||
return (a * (n - i - 1) + b * (i + 1)) // n
|
||
tt = i if local else t
|
||
for y in range(h):
|
||
for x in range(w):
|
||
v = y_of(x, y, tt)
|
||
out.append(blend(v, to[0](x, y, tt)) if to else v)
|
||
cb, cr = chroma
|
||
if to:
|
||
cb, cr = blend(cb, to[1][0]), blend(cr, to[1][1])
|
||
out += bytes([cb]) * (cw * ch) + bytes([cr]) * (cw * ch)
|
||
t += 1
|
||
return bytes(out)
|
||
|
||
|
||
# ---------------------------------------------------------------- scene cut (§4.3) and kinds (§4.6)
|
||
|
||
|
||
def luma(r, g, b):
|
||
return (77 * r + 150 * g + 29 * b + 128) >> 8
|
||
|
||
|
||
def distance(a, b, wh):
|
||
return sum(abs(x - y) for x, y in zip(a, b)) * 1000 // (2 * wh)
|
||
|
||
|
||
def segment(frames, w, h, p):
|
||
"""The on-demand form: every frame known, `last` the final index."""
|
||
wh = w * h
|
||
ys = [[luma(f[i], f[i + 1], f[i + 2]) for i in range(0, len(f), 3)] for f in frames]
|
||
hists = []
|
||
for y in ys:
|
||
hh = [0] * 64
|
||
for v in y:
|
||
hh[v >> 2] += 1
|
||
hists.append(hh)
|
||
n = len(frames)
|
||
last = n - 1
|
||
scores = [0] + [distance(hists[f], hists[f - 1], wh) for f in range(1, n)]
|
||
L = p["lookahead"]
|
||
|
||
def rejected(f):
|
||
if any(distance(hists[f + k], hists[f - 1], wh) < p["cut_abs"] for k in range(1, min(L, last - f) + 1)):
|
||
return True
|
||
return any(distance(hists[f], hists[f - 1 - j], wh) < p["cut_abs"] for j in range(1, min(L, f - 1) + 1))
|
||
|
||
starts, flags, window, start = [0], [0], [], 0
|
||
for f in range(1, n):
|
||
ln, s = f - start, scores[f]
|
||
med = sorted(window)[(len(window) - 1) // 2] if window else 0
|
||
hard = ln >= p["min"] and s >= p["cut_abs"] and (len(window) < 3 or s * 1000 >= p["cut_rel"] * med)
|
||
soft = ln >= p["target"] and 2 * s >= p["cut_abs"]
|
||
if (hard or soft) and not rejected(f):
|
||
flag = 1
|
||
elif ln >= p["max"]:
|
||
flag = 2
|
||
else:
|
||
flag = 0
|
||
if flag:
|
||
starts.append(f)
|
||
flags.append(flag)
|
||
start, window = f, []
|
||
else:
|
||
window = (window + [s])[-p["window"]:]
|
||
pieces = []
|
||
for i, st in enumerate(starts):
|
||
end = starts[i + 1] if i + 1 < len(starts) else n
|
||
abs_diff = changed = 0
|
||
for f in range(st + 1, end):
|
||
for a, b in zip(ys[f], ys[f - 1]):
|
||
abs_diff += abs(a - b)
|
||
changed += a != b
|
||
compared = wh * (end - st - 1)
|
||
pieces.append({"start": st, "len": end - st, "flags": flags[i], "abs_diff": abs_diff, "changed": changed,
|
||
"compared": compared, "occupancy": sum(1 for c in hists[st] if c)})
|
||
return pieces, scores
|
||
|
||
|
||
def classify(pc, c):
|
||
mad = pc["abs_diff"] * 1000 // pc["compared"] if pc["compared"] else 0
|
||
chg = pc["changed"] * 1000 // pc["compared"] if pc["compared"] else 0
|
||
if mad <= c["static_mad_milli"] and chg <= c["static_permille"]:
|
||
kind = "static"
|
||
elif pc["occupancy"] <= c["synthetic_bins"]:
|
||
kind = "synthetic"
|
||
elif mad >= c["motion_high_milli"]:
|
||
kind = "motion_high"
|
||
else:
|
||
kind = "natural"
|
||
return kind, mad, chg
|
||
|
||
|
||
def plan_digest(starts):
|
||
b = b"wai:pack-plan\x01" + len(starts).to_bytes(8, "little")
|
||
for s in starts:
|
||
b += s.to_bytes(8, "little")
|
||
return blake3(b).hex()
|
||
|
||
|
||
# ---------------------------------------------------------------- the encoder's choices (§5.5, §5.6)
|
||
|
||
|
||
def quantize(r, q):
|
||
"""r / q rounded to the nearest integer, halves away from zero."""
|
||
m = (2 * abs(r) + q) // (2 * q)
|
||
return -m if r < 0 else m
|
||
|
||
|
||
def hist_to_cdf(hist, precision=16):
|
||
"""§5.5's table: every symbol frequency 1 + floor(R·h/H), the remainder one each to the
|
||
largest R·h mod H (the lower symbol first), the escape 1."""
|
||
total, n = 1 << precision, len(hist)
|
||
r = total - n - 1
|
||
weight = [1] * n if all(h == 0 for h in hist) else list(hist)
|
||
big_h = sum(weight)
|
||
freq = [1 + r * x // big_h for x in weight]
|
||
given = sum(f - 1 for f in freq)
|
||
order = sorted(range(n), key=lambda i: -(r * weight[i] % big_h))
|
||
for i in order[:r - given]:
|
||
freq[i] += 1
|
||
freq.append(1)
|
||
cdf, acc = [0], 0
|
||
for f in freq:
|
||
acc += f
|
||
cdf.append(acc)
|
||
return cdf
|
||
|
||
|
||
def residual_table(residuals):
|
||
"""The table and offset of §5.5 from a clip's residuals."""
|
||
lo, hi = min(residuals), max(residuals)
|
||
width = min(hi - lo + 1, 4095)
|
||
hist = [0] * width
|
||
for r in residuals:
|
||
if lo <= r < lo + width:
|
||
hist[r - lo] += 1
|
||
return hist_to_cdf(hist), lo
|
||
|
||
|
||
def clampi(v, lo, hi):
|
||
return lo if v < lo else hi if v > hi else v
|
||
|
||
|
||
def block_rows(ref, h, w, y0, y1, x0, x1, dy, dx, prec):
|
||
"""The decoder's prediction (integer-payloads §3) of the rows y0..y1, columns x0..x1
|
||
of a block from the reference frame `ref` (h·w·3 bytes) with vector (dy, dx): one
|
||
tuple of R, G, B samples per row."""
|
||
def cols(xs):
|
||
return [clampi(x, 0, w - 1) * 3 + c for x in xs for c in range(3)]
|
||
|
||
def rowsl(y):
|
||
k = clampi(y, 0, h - 1) * w * 3
|
||
return ref[k:k + w * 3]
|
||
out = []
|
||
if prec == 1:
|
||
get = cols(range(x0 + dx, x1 + dx))
|
||
for y in range(y0, y1):
|
||
row = rowsl(y + dy)
|
||
out.append([row[i] for i in get])
|
||
return out
|
||
fy, fx = dy & 1, dx & 1
|
||
ga = cols(range(x0 + (dx >> 1), x1 + (dx >> 1)))
|
||
gb = cols(range(x0 + (dx >> 1) + 1, x1 + (dx >> 1) + 1))
|
||
for y in range(y0, y1):
|
||
iy = y + (dy >> 1)
|
||
r0 = rowsl(iy)
|
||
a = [r0[i] for i in ga]
|
||
if not fy and not fx:
|
||
out.append(a)
|
||
continue
|
||
if not fy:
|
||
b = [r0[i] for i in gb]
|
||
out.append([(p + q + 1) >> 1 for p, q in zip(a, b)])
|
||
continue
|
||
r1 = rowsl(iy + 1)
|
||
c = [r1[i] for i in ga]
|
||
if not fx:
|
||
out.append([(p + q + 1) >> 1 for p, q in zip(a, c)])
|
||
continue
|
||
b = [r0[i] for i in gb]
|
||
d = [r1[i] for i in gb]
|
||
out.append([(p + q + s + t + 2) >> 2 for p, q, s, t in zip(a, b, c, d)])
|
||
return out
|
||
|
||
|
||
def search_block(src, ref, h, w, y0, x0, block, rng, prec):
|
||
"""§5.5's motion search for one block: dy outer, dx inner, each ascending over
|
||
-rng..=rng; the least SAD wins, a tie to the smaller |dy| + |dx|, then the earlier."""
|
||
y1, x1 = min(y0 + block, h), min(x0 + block, w)
|
||
srows = [src[(y * w + x0) * 3:(y * w + x1) * 3] for y in range(y0, y1)]
|
||
best = None # (cost, l1, dy, dx)
|
||
for dy in range(-rng, rng + 1):
|
||
for dx in range(-rng, rng + 1):
|
||
cost = 0
|
||
for s, p in zip(srows, block_rows(ref, h, w, y0, y1, x0, x1, dy, dx, prec)):
|
||
cost += sum(abs(a - b) for a, b in zip(s, p))
|
||
if best is not None and cost > best[0]:
|
||
break
|
||
l1 = abs(dy) + abs(dx)
|
||
if best is None or cost < best[0] or (cost == best[0] and l1 < best[1]):
|
||
best = (cost, l1, dy, dx)
|
||
return best[2], best[3]
|
||
|
||
|
||
def frame_pred(ref, h, w, block, mvs, prec):
|
||
"""A whole frame's prediction from `ref` with its blocks' vectors (raster order)."""
|
||
pred = [0] * (h * w * 3)
|
||
nbx = -(-w // block)
|
||
for by in range(-(-h // block)):
|
||
for bx in range(nbx):
|
||
y0, x0 = by * block, bx * block
|
||
y1, x1 = min(y0 + block, h), min(x0 + block, w)
|
||
dy, dx = mvs[by * nbx + bx]
|
||
for y, row in zip(range(y0, y1), block_rows(ref, h, w, y0, y1, x0, x1, dy, dx, prec)):
|
||
pred[(y * w + x0) * 3:(y * w + x1) * 3] = row
|
||
return pred
|
||
|
||
|
||
def frame_vectors(src, ref, h, w, block, rng, prec):
|
||
nbx, nby = -(-w // block), -(-h // block)
|
||
return [search_block(src, ref, h, w, by * block, bx * block, block, rng, prec) for by in range(nby) for bx in range(nbx)]
|
||
|
||
|
||
def closed_loop(frames, h, w, block, search, prec, q):
|
||
"""The encoder's reconstruction and its SSE (§5.5), without the entropy coder: each
|
||
frame's vectors searched against the previous frame as reconstructed."""
|
||
if len(frames) == 1:
|
||
block, prec = 1, 1
|
||
rng = search * prec if len(frames) > 1 else 0
|
||
recon, sse = [], 0
|
||
for f, src in enumerate(frames):
|
||
if f == 0:
|
||
pred = [128] * len(src)
|
||
else:
|
||
mvs = frame_vectors(src, recon[f - 1], h, w, block, rng, prec)
|
||
pred = frame_pred(recon[f - 1], h, w, block, mvs, prec)
|
||
cur = []
|
||
for t, p in zip(src, pred):
|
||
v = clampi(p + quantize(t - p, q) * q, 0, 255)
|
||
sse += (t - v) * (t - v)
|
||
cur.append(v)
|
||
recon.append(cur)
|
||
return recon, sse
|
||
|
||
|
||
def encoder_choices(v, params, src, decoded):
|
||
"""Item by item, what §5.5 makes of the source against the payload: every motion
|
||
vector, every coded residual, and the table and its offset. Returns the failures."""
|
||
h, w, n, q, prec, block = v["h"], v["w"], v["n_frames"], v["q"], v["mv_precision"], v["block"]
|
||
bad = []
|
||
rng = params["search"] * prec if n > 1 else 0
|
||
nb = -(-h // block) * -(-w // block)
|
||
d = IPV.Rans(v["rans"])
|
||
residuals = []
|
||
for f in range(n):
|
||
if f == 0:
|
||
pred = [128] * (h * w * 3)
|
||
else:
|
||
mvs = frame_vectors(src[f], decoded[f - 1], h, w, block, rng, prec)
|
||
if mvs != [tuple(m) for m in v["mvs"][(f - 1) * nb:f * nb]]:
|
||
bad.append(f"frame {f}: a motion vector is not the search's")
|
||
pred = frame_pred(decoded[f - 1], h, w, block, [tuple(m) for m in v["mvs"][(f - 1) * nb:f * nb]], prec)
|
||
for t, p in zip(src[f], pred):
|
||
r = quantize(t - p, q)
|
||
residuals.append(r)
|
||
if d.decode(v["cdf"], v["offset"]) != r:
|
||
bad.append(f"frame {f}: a coded residual is not quantize(source - prediction)")
|
||
return bad
|
||
cdf, offset = residual_table(residuals)
|
||
if (cdf, offset) != (list(v["cdf"]), v["offset"]):
|
||
bad.append("the residual table or its offset is not the fit of the residuals")
|
||
return bad
|
||
|
||
|
||
def sse_px_milli(sse, h, w, n):
|
||
return sse * 1000 // (3 * h * w * n)
|
||
|
||
|
||
def choose_q(metric_of, candidates, bound):
|
||
"""§5.6: the largest candidate meeting the bound, else the smallest."""
|
||
qs = sorted(set(candidates), reverse=True)
|
||
for q in qs:
|
||
if metric_of(q) <= bound:
|
||
return q
|
||
return qs[-1]
|
||
|
||
|
||
# ---------------------------------------------------------------- the stage index (§6.2–§6.4)
|
||
|
||
|
||
MEDIA_CLASSES = ["video", "image", "audio", "world", "feed", "avatar", "film"]
|
||
ROLES = ["base", "refinement", "floor", "snapshot", "ops", "receipt", "asset"]
|
||
BASE, REFINEMENT, FLOOR = 0, 1, 2
|
||
NO_REFINES = 0xFFFFFFFF
|
||
PIECE_KINDS = KINDS + ["world", "audio"]
|
||
ENTRY_DUP, ENTRY_DERIVED = 2, 4
|
||
TIER_NAMES = [None, "entropy-consistency", "decode-equivalence"]
|
||
|
||
|
||
def le(b, at, n):
|
||
return int.from_bytes(b[at:at + n], "little")
|
||
|
||
|
||
def pad8(n):
|
||
return (n + 7) // 8 * 8
|
||
|
||
|
||
def parse_index(b):
|
||
"""§6.2: a WSIX file read into its header, records, class plan and strings, or the
|
||
reader's refusal, the first that applies."""
|
||
if b[:4] != b"WSIX":
|
||
raise Refused("index-magic")
|
||
if len(b) < 6:
|
||
raise Refused("index-truncated")
|
||
if le(b, 4, 2) != 1:
|
||
raise Refused("index-version")
|
||
if len(b) < 160:
|
||
raise Refused("index-truncated")
|
||
n_p, n_e, n_s, n_c = le(b, 16, 4), le(b, 20, 4), le(b, 24, 4), le(b, 28, 4)
|
||
# Every table's size from its count, before anything is read from it.
|
||
at_e = 160 + 32 * n_p
|
||
at_c = at_e + 128 * n_e
|
||
at_plan = at_c + 16 * n_c
|
||
at_s = at_plan + pad8(n_c * n_p)
|
||
if at_s > len(b):
|
||
raise Refused("index-truncated")
|
||
raw, pos = [], at_s
|
||
for _ in range(n_s):
|
||
if pos + 2 > len(b) or pos + 2 + le(b, pos, 2) > len(b):
|
||
raise Refused("index-truncated")
|
||
raw.append(b[pos + 2:pos + 2 + le(b, pos, 2)])
|
||
pos += 2 + le(b, pos, 2)
|
||
end = at_s + pad8(pos - at_s)
|
||
if end > len(b):
|
||
raise Refused("index-truncated")
|
||
P = [b[160 + 32 * i:192 + 32 * i] for i in range(n_p)]
|
||
E = [b[at_e + 128 * i:at_e + 128 * (i + 1)] for i in range(n_e)]
|
||
C = [b[at_c + 16 * i:at_c + 16 * (i + 1)] for i in range(n_c)]
|
||
reserved = [b[146:160], b[at_plan + n_c * n_p:at_s], b[pos:end]]
|
||
reserved += [p[28:32] for p in P] + [e[58:60] + e[108:112] + e[120:128] for e in E] + [c[8:16] for c in C]
|
||
if any(any(r) for r in reserved):
|
||
raise Refused("index-reserved")
|
||
if le(b, 6, 2) >= len(MEDIA_CLASSES):
|
||
raise Refused("index-media-class")
|
||
for e in E:
|
||
if e[54] >= len(ROLES):
|
||
raise Refused("index-role")
|
||
if e[56] >= len(TIER_NAMES):
|
||
raise Refused("index-tier")
|
||
strings = []
|
||
for s in raw:
|
||
try:
|
||
strings.append(s.decode("utf-8"))
|
||
except UnicodeDecodeError:
|
||
raise Refused("index-string")
|
||
if not all(strings) or any(i >= n_s for i in [le(b, 144, 2)] + [le(e, 52, 2) for e in E] + [le(c, 0, 2) for c in C]):
|
||
raise Refused("index-string")
|
||
if end != len(b):
|
||
raise Refused("index-trailing")
|
||
return {
|
||
"raw": b, "media_class": le(b, 6, 2), "tb": (le(b, 8, 4), le(b, 12, 4)), "policy_digest": b[32:64],
|
||
"prev": b[64:96], "first_piece": le(b, 96, 8), "first_entry": le(b, 104, 8), "plan_digest": b[112:144],
|
||
"component": strings[le(b, 144, 2)], "strings": strings,
|
||
"pieces": [{"start": le(p, 0, 8), "duration": le(p, 8, 4), "first_entry": le(p, 12, 4),
|
||
"n_entries": le(p, 16, 2), "kind": p[18], "flags": p[19], "rules": le(p, 20, 4),
|
||
"moments": le(p, 24, 4)} for p in P],
|
||
"entries": [{"record": e, "hash": e[0:32], "byte_len": le(e, 32, 8), "piece": le(e, 40, 8),
|
||
"refines": le(e, 48, 4), "cap": strings[le(e, 52, 2)], "role": e[54], "layer": e[55],
|
||
"tier": e[56], "flags": e[57], "deadline": le(e, 60, 4), "prefix": e[64:96],
|
||
"sse": le(e, 96, 8), "man_len": le(e, 104, 4), "payload_len": le(e, 112, 8)} for e in E],
|
||
"classes": [{"name": strings[le(c, 0, 2)], "max_layer": c[2], "flags": c[3], "max_kbps": le(c, 4, 4)}
|
||
for c in C],
|
||
"plan": [list(b[at_plan + k * n_p:at_plan + (k + 1) * n_p]) for k in range(n_c)],
|
||
}
|
||
|
||
|
||
def cap_tier(cap):
|
||
"""§6.4: decode-equivalence (2) for a registry class that reconstructs exactly,
|
||
entropy-consistency (1) for neural-entropy-exact, none (0) otherwise."""
|
||
rep = (REGISTRY.get(cap) or {}).get("replicate")
|
||
return 2 if rep in ("standard-defined", "neural-int-exact") else 1 if rep == "neural-entropy-exact" else 0
|
||
|
||
|
||
def due_tier(E, i):
|
||
"""The tier §6.4 gives entry i: a refinement holds decode-equivalence only over a prefix
|
||
that does; a floor states none; any other entry its capability's."""
|
||
e = E[i]
|
||
if e["role"] == REFINEMENT:
|
||
return 2 if E[e["refines"]]["tier"] == 2 and cap_tier(e["cap"]) == 2 else 0
|
||
return 0 if e["role"] == FLOOR else cap_tier(e["cap"])
|
||
|
||
|
||
def owners(ix):
|
||
"""The window-relative piece of each entry, by the pieces' entry ranges."""
|
||
return [k for k, pc in enumerate(ix["pieces"]) for _ in range(pc["n_entries"])]
|
||
|
||
|
||
def highest_layer(ix, k):
|
||
pc = ix["pieces"][k]
|
||
es = ix["entries"][pc["first_entry"]:pc["first_entry"] + pc["n_entries"]]
|
||
return max((e["layer"] for e in es if e["role"] in (BASE, REFINEMENT)), default=0)
|
||
|
||
|
||
def index_rules(ix):
|
||
"""§6.3's rules in their order, with §6.4's tiers; the first broken is the refusal."""
|
||
if 0 in ix["tb"]:
|
||
raise Refused("index-timebase")
|
||
# An index with no previous window starts the timeline and the chain (§4.5).
|
||
if not any(ix["prev"]) and (ix["first_piece"] or ix["first_entry"] or (ix["pieces"] and ix["pieces"][0]["start"])):
|
||
raise Refused("window-start")
|
||
pieces, E, before = ix["pieces"], ix["entries"], 0
|
||
for k, pc in enumerate(pieces):
|
||
if pc["duration"] == 0 or (k and pc["start"] != pieces[k - 1]["start"] + pieces[k - 1]["duration"]):
|
||
raise Refused("piece-order")
|
||
if pc["first_entry"] != before or pc["n_entries"] == 0:
|
||
raise Refused("piece-entries")
|
||
if pc["kind"] >= len(PIECE_KINDS) or pc["flags"] > 0x0F:
|
||
raise Refused("piece-kind")
|
||
before += pc["n_entries"]
|
||
if before != len(E):
|
||
raise Refused("piece-entries")
|
||
rank = {BASE: 0, REFINEMENT: 1, FLOOR: 2}
|
||
own = owners(ix)
|
||
for i, e in enumerate(E):
|
||
pc = pieces[own[i]]
|
||
if e["piece"] != ix["first_piece"] + own[i]:
|
||
raise Refused("piece-entries")
|
||
if e["flags"] > 0x07:
|
||
raise Refused("entry-flags")
|
||
if i > pc["first_entry"]:
|
||
p = E[i - 1]
|
||
if rank.get(e["role"], 3) < rank.get(p["role"], 3) or \
|
||
(e["role"] == p["role"] == REFINEMENT and e["layer"] < p["layer"]):
|
||
raise Refused("entry-order")
|
||
refinement = e["role"] == REFINEMENT
|
||
if (e["role"] in (BASE, FLOOR) and e["layer"] > 0) or (refinement and e["layer"] == 0) \
|
||
or refinement != (e["refines"] != NO_REFINES):
|
||
raise Refused("layer-role")
|
||
if refinement:
|
||
if e["refines"] >= len(E):
|
||
raise Refused("refines-outside-window")
|
||
if not pc["first_entry"] <= e["refines"] < i:
|
||
raise Refused("refines-mismatch")
|
||
if E[e["refines"]]["layer"] != e["layer"] - 1:
|
||
raise Refused("layer-gap")
|
||
due = due_tier(E, i)
|
||
if e["layer"] > 0 and e["tier"] == 1:
|
||
raise Refused("tier-entropy-passthrough")
|
||
if e["layer"] > 0 and e["tier"] == 2 and due != 2:
|
||
raise Refused("tier-weakest")
|
||
if e["tier"] != due:
|
||
raise Refused("tier-mismatch")
|
||
if any(e["prefix"]) and e["tier"] != 2:
|
||
raise Refused("digest-without-tier")
|
||
if e["deadline"] and not refinement:
|
||
raise Refused("deadline-role")
|
||
if e["role"] == FLOOR and not e["flags"] & ENTRY_DERIVED:
|
||
raise Refused("floor-derived-from")
|
||
earlier = [x for x in E[:i] if x["hash"] == e["hash"]]
|
||
if any(x["byte_len"] != e["byte_len"] for x in earlier):
|
||
raise Refused("dup-length")
|
||
if bool(e["flags"] & ENTRY_DUP) != bool(earlier):
|
||
raise Refused("dup-flag")
|
||
if len(ix["plan"]) != len(ix["classes"]):
|
||
raise Refused("class-plan")
|
||
for cl, row in zip(ix["classes"], ix["plan"]):
|
||
if cl["flags"] > 0x01:
|
||
raise Refused("class-flags")
|
||
if len(row) != len(pieces):
|
||
raise Refused("class-plan")
|
||
for k, v in enumerate(row):
|
||
if cl["flags"] & 1:
|
||
ok = v == 0xFF
|
||
else:
|
||
ok = v == 0xFF or (v <= cl["max_layer"] and v <= highest_layer(ix, k))
|
||
if not ok:
|
||
raise Refused("class-plan")
|
||
|
||
|
||
def merkle_root(items):
|
||
"""jwp-receipts §2.2: each item under the leaf domain, pairs under the node domain, an
|
||
odd level's last node duplicated; no items, 32 zero bytes."""
|
||
if not items:
|
||
return bytes(32)
|
||
level = [blake3(b"moq-jwp:merkle-leaf\x01" + x) for x in items]
|
||
while len(level) > 1:
|
||
if len(level) % 2:
|
||
level.append(level[-1])
|
||
level = [blake3(b"moq-jwp:merkle-node\x01" + level[i] + level[i + 1]) for i in range(0, len(level), 2)]
|
||
return level[0]
|
||
|
||
|
||
def index_digest(b):
|
||
return blake3(b"wai:pack-stage-index\x01" + b)
|
||
|
||
|
||
def stage_root(ix):
|
||
"""§6.2: the Merkle root over BLAKE3("wai:pack-stage\\x01" ‖ entry record)."""
|
||
return merkle_root([blake3(b"wai:pack-stage\x01" + e["record"]) for e in ix["entries"]])
|
||
|
||
|
||
# ---------------------------------------------------------------- objects (§5, §6.3)
|
||
|
||
|
||
def content_hash(b):
|
||
"""jwp-receipts §2.1."""
|
||
return blake3(b"moq-jwp:object\x01" + b)
|
||
|
||
|
||
def read_json(b, minus_zero=False):
|
||
"""JSON in UTF-8 whose objects name no member twice (SPEC §2), or None. With
|
||
`minus_zero`, the number -0 is read as the float -0.0, as RFC 8259 readers that keep
|
||
its sign do, so no rule that requires an integer accepts it."""
|
||
def members(pairs):
|
||
d = {}
|
||
for k, v in pairs:
|
||
if k in d:
|
||
raise ValueError("a member named twice")
|
||
d[k] = v
|
||
return d
|
||
|
||
def no_constant(s):
|
||
raise ValueError(s)
|
||
|
||
def finite(t):
|
||
# RFC 8259 readers that hold numbers as binary64 refuse one past its range.
|
||
v = float(t)
|
||
if v in (float("inf"), float("-inf")):
|
||
raise ValueError(t)
|
||
return v
|
||
try:
|
||
hook = (lambda t: float(t) if t == "-0" else int(t)) if minus_zero else int
|
||
return json.loads(b.decode("utf-8"), object_pairs_hook=members, parse_constant=no_constant, parse_int=hook,
|
||
parse_float=finite)
|
||
except (UnicodeDecodeError, ValueError, RecursionError):
|
||
return None
|
||
|
||
|
||
def json_values(v):
|
||
"""The JSON values `v` holds: itself and every value in it, at any depth."""
|
||
n, stack = 0, [v]
|
||
while stack:
|
||
x = stack.pop()
|
||
n += 1
|
||
if isinstance(x, dict):
|
||
stack.extend(x.values())
|
||
elif isinstance(x, list):
|
||
stack.extend(x)
|
||
return n
|
||
|
||
|
||
def opt_str(o, k):
|
||
"""An OPTIONAL string member: absent, null, or a string."""
|
||
return k not in o or o[k] is None or isinstance(o[k], str)
|
||
|
||
|
||
def prior_ok(p):
|
||
"""SPEC §3.1: a pin is null, or an object whose id, sha256 and determinism are strings,
|
||
form and tier OPTIONAL strings, supersedes null or an object of a string sha256 and an
|
||
OPTIONAL string form."""
|
||
if p is None:
|
||
return True
|
||
if not isinstance(p, dict) or not all(isinstance(p.get(k), str) for k in ("id", "sha256", "determinism")):
|
||
return False
|
||
sup = p.get("supersedes")
|
||
return opt_str(p, "form") and opt_str(p, "tier") and \
|
||
(sup is None or (isinstance(sup, dict) and isinstance(sup.get("sha256"), str) and opt_str(sup, "form")))
|
||
|
||
|
||
def is_number(v):
|
||
return isinstance(v, (int, float)) and not isinstance(v, bool)
|
||
|
||
|
||
def manifest_typed(m, v2):
|
||
"""SPEC §3 and §7.1: a manifest that parses with its members' types. wai, media and
|
||
intent are strings. A WAI1's model_requirement is an object whose capability is a
|
||
string, fallback and sim_metric OPTIONAL strings, min_sim null or a number; its
|
||
conditioning an object whose kind is a string and prior a pin; security null or an
|
||
object of an OPTIONAL string encryption and an OPTIONAL u64 kid. A WAI2's renditions are
|
||
objects whose capability and kind are strings, id, sha256, role, companion_of and
|
||
companion_of_id OPTIONAL strings, prior a pin. Members this revision does not define
|
||
may have any value; refines, derived_from, envelope_manifest and target any JSON."""
|
||
if not isinstance(m, dict) or not all(isinstance(m.get(k), str) for k in ("wai", "media", "intent")):
|
||
return False
|
||
if v2:
|
||
rs = m.get("renditions")
|
||
return isinstance(rs, list) and all(
|
||
isinstance(r, dict) and isinstance(r.get("capability"), str) and isinstance(r.get("kind"), str)
|
||
and all(opt_str(r, k) for k in ("id", "sha256", "role", "companion_of", "companion_of_id")) and prior_ok(r.get("prior"))
|
||
for r in rs)
|
||
mr, c, sec = m.get("model_requirement"), m.get("conditioning"), m.get("security")
|
||
if not isinstance(mr, dict) or not isinstance(mr.get("capability"), str) or not opt_str(mr, "fallback") \
|
||
or not opt_str(mr, "sim_metric") or not (mr.get("min_sim") is None or is_number(mr["min_sim"])):
|
||
return False
|
||
if not isinstance(c, dict) or not isinstance(c.get("kind"), str) or not prior_ok(c.get("prior")):
|
||
return False
|
||
return sec is None or (isinstance(sec, dict) and opt_str(sec, "encryption")
|
||
and (sec.get("kid") is None or (is_int(sec["kid"]) and 0 <= sec["kid"] < 1 << 64)))
|
||
|
||
|
||
def read_envelope(b):
|
||
"""Exactly one WAI1 or WAI2 envelope and nothing after it (http-delivery §5.1), its
|
||
manifest JSON that parses (SPEC §2, §7.1): (man_len, payload_len, the member that
|
||
names the capability, the capability, each payload with the capabilities that decode
|
||
it), or None. The member is the WAI1 manifest, or the WAI2's first rendition entry; a
|
||
WAI2's payload length is every component's; a WAI1's payload is decoded by its
|
||
capability and its fallback (SPEC §4)."""
|
||
if len(b) < 8 or b[:4] not in (b"WAI1", b"WAI2"):
|
||
return None
|
||
ml = le(b, 4, 4)
|
||
if ml > MANIFEST_MAX or 8 + ml > len(b):
|
||
return None
|
||
man = read_json(b[8:8 + ml])
|
||
if man is not None and json_values(man) > MANIFEST_MAX_VALUES:
|
||
return None
|
||
# §6.3: "does not parse" is the typed parse with SPEC §3's members.
|
||
if not manifest_typed(man, b[:4] == b"WAI2"):
|
||
return None
|
||
if b[:4] == b"WAI1":
|
||
if 12 + ml > len(b) or len(b) != 12 + ml + le(b, 8 + ml, 4):
|
||
return None
|
||
mr = man.get("model_requirement") if isinstance(man.get("model_requirement"), dict) else {}
|
||
caps = [c for c in (mr.get("capability"), mr.get("fallback")) if isinstance(c, str)]
|
||
return ml, le(b, 8 + ml, 4), man, mr.get("capability"), [(caps, b[12 + ml:])]
|
||
rs = man.get("renditions")
|
||
at = 8 + ml
|
||
if not isinstance(rs, list) or not rs or not all(isinstance(r, dict) for r in rs) or at + 2 > len(b):
|
||
return None
|
||
n = le(b, at, 2)
|
||
head = at + 2 + 8 * n
|
||
if n != len(rs) or head > len(b):
|
||
return None
|
||
total, parts = 0, []
|
||
for i in range(n):
|
||
if le(b, at + 2 + 8 * i, 4) != total:
|
||
return None
|
||
parts.append(([rs[i].get("capability")], b[head + total:head + total + le(b, at + 6 + 8 * i, 4)]))
|
||
total += le(b, at + 6 + 8 * i, 4)
|
||
return (ml, total, rs[0], rs[0].get("capability"), parts) if len(b) == head + total else None
|
||
|
||
|
||
def check_reader_limit(rep):
|
||
"""SPEC §2, *Reader limits*, checked in memory, since no file in the corpus is past
|
||
128 MiB or holds 4 Mi values: read_envelope reads a WAI1 and a WAI2 whose manifests
|
||
are 300 bytes, and refuses each with its manifest padded to MANIFEST_MAX + 1 bytes by
|
||
a member no revision defines, which a reader ignores (SPEC §8), so only the limit
|
||
refuses it; it reads each with that member an array of zeros that brings the manifest
|
||
to MANIFEST_MAX_VALUES values, and refuses each with one zero more."""
|
||
for magic, head, rest in (
|
||
(b"WAI1", b'{"wai":"1.0","media":"text","intent":"replicate","model_requirement":'
|
||
b'{"capability":"wai.text.zstd"},"conditioning":{"kind":"zstd"},"pad":"',
|
||
(3).to_bytes(4, "little")),
|
||
(b"WAI2", b'{"wai":"1.1","media":"text","intent":"replicate","renditions":'
|
||
b'[{"capability":"wai.text.zstd","kind":"zstd"}],"pad":"',
|
||
(1).to_bytes(2, "little") + (0).to_bytes(4, "little") + (3).to_bytes(4, "little"))):
|
||
def env(n):
|
||
return b"".join([magic, n.to_bytes(4, "little"), head, b"x" * (n - len(head) - 2), b'"}', rest, b"abc"])
|
||
rep.check(read_envelope(env(300)) is not None, f"reader limit: a {magic.decode()} with a 300-byte manifest is read")
|
||
rep.check(read_envelope(env(MANIFEST_MAX + 1)) is None,
|
||
f"reader limit: a {magic.decode()} manifest of {MANIFEST_MAX + 1} bytes, past the limit, is refused")
|
||
|
||
def valued(n):
|
||
fixed = json_values(read_json(head[:-1] + b"[]}"))
|
||
m = head[:-1] + b"[0" + b",0" * (n - fixed - 1) + b"]}"
|
||
return b"".join([magic, len(m).to_bytes(4, "little"), m, rest, b"abc"])
|
||
rep.check(read_envelope(valued(MANIFEST_MAX_VALUES)) is not None,
|
||
f"reader limit: a {magic.decode()} manifest of {MANIFEST_MAX_VALUES} values is read")
|
||
rep.check(read_envelope(valued(MANIFEST_MAX_VALUES + 1)) is None,
|
||
f"reader limit: a {magic.decode()} manifest of {MANIFEST_MAX_VALUES + 1} values, past the limit, is refused")
|
||
|
||
|
||
WIR1_KIND = {"wai.image.int_refine": "rgb8", "wai.video.int_refine": "rgb8", "wai.audio.int_refine": "pcm",
|
||
"wai.splat.int_refine": "tensor"}
|
||
|
||
|
||
def wir1_parses(cap, p):
|
||
"""staged-delivery A.1–A.3: a WIR1 payload's parse, for the capability's unit kind."""
|
||
if p[:4] != b"WIR1" or len(p) < 44 or p[4] != 1 or le(p, 42, 2):
|
||
return False
|
||
elem, pred, mode, n, d0, d1, d2 = p[5], p[6], p[7], le(p, 8, 4), le(p, 12, 4), le(p, 16, 4), le(p, 20, 4)
|
||
rn, rd, q, cdf_len = le(p, 24, 4), le(p, 28, 4), le(p, 32, 4), le(p, 40, 2)
|
||
if elem not in (1, 2) or pred not in (0, 1, 2) or mode not in (0, 1) or 0 in (n, d0, d1, d2):
|
||
return False
|
||
if not ((rn >= 1 and rd >= 1) if pred == 2 else rn == rd == 0):
|
||
return False
|
||
if not 1 <= q <= 65535 or not 3 <= cdf_len <= 4097:
|
||
return False
|
||
kind = WIR1_KIND[cap]
|
||
if kind == "rgb8" and (elem != 1 or pred not in (0, 1) or d2 != 3):
|
||
return False
|
||
if kind == "pcm" and (elem != 2 or pred not in (0, 2) or d1 != 1):
|
||
return False
|
||
if kind == "tensor" and (elem != 1 or pred != 0):
|
||
return False
|
||
if d0 * d1 * d2 > 1 << 26 or n > 1 << 20 or n * d0 * d1 * d2 > 1 << 30:
|
||
return False
|
||
at = 44
|
||
for _ in range(n if mode else 1):
|
||
if at + 4 * cdf_len > len(p):
|
||
return False
|
||
try:
|
||
IPV.check_cdf([le(p, at + 4 * k, 4) for k in range(cdf_len)])
|
||
except IPV.Refused:
|
||
return False
|
||
at += 4 * cdf_len
|
||
if at + 4 * n > len(p):
|
||
return False
|
||
lens = [le(p, at + 4 * k, 4) for k in range(n)]
|
||
if any(x % 4 or x < 8 for x in lens):
|
||
return False
|
||
return at + 4 * n + sum(lens) == len(p)
|
||
|
||
|
||
def payload_parses(cap, p):
|
||
"""§6.2 with staged-measurement §3.2 and its Appendix A: a payload of a capability WAI
|
||
pins to a binary format parses as that format, by the parse a sink runs; a capability
|
||
with no pinned format asks nothing of it."""
|
||
if cap in IPV.CAP_MAGIC:
|
||
try:
|
||
IPV.parse(cap, p)
|
||
return True
|
||
except IPV.Refused:
|
||
return False
|
||
return wir1_parses(cap, p) if cap in WIR1_KIND else True
|
||
|
||
|
||
def derived_from_ok(df):
|
||
"""SPEC §7.1, *Derived renditions*: the member's four values in their forms."""
|
||
return isinstance(df, dict) and all(isinstance(df.get(k), str) and df[k] for k in
|
||
("motif", "motif_capability", "renderer", "settings")) \
|
||
and is_hex64(df["motif"]) and is_hex64(df["settings"])
|
||
|
||
|
||
def refines_ok(ix, i, rf):
|
||
"""§5.4: `base` the content hash of its piece's base, `layer` its layer, and as
|
||
producers in `prev` only objects of the layers below it, the entry it refines among them."""
|
||
E, pc = ix["entries"], ix["pieces"][owners(ix)[i]]
|
||
e, mine = E[i], E[pc["first_entry"]:pc["first_entry"] + pc["n_entries"]]
|
||
base = next((x for x in mine if x["role"] == BASE), None)
|
||
if not isinstance(rf, dict) or base is None or rf.get("base") != base["hash"].hex():
|
||
return False
|
||
if not is_int(rf.get("layer")) or rf["layer"] != e["layer"]:
|
||
return False
|
||
prev = rf.get("prev")
|
||
if isinstance(prev, str):
|
||
producers = [prev]
|
||
elif isinstance(prev, list) and prev and all(isinstance(r, dict) for r in prev):
|
||
producers = [r.get("producer") for r in prev]
|
||
else:
|
||
return False
|
||
below = {x["hash"].hex() for x in mine if x["role"] in (BASE, REFINEMENT) and x["layer"] < e["layer"]}
|
||
return all(p in below for p in producers) and E[e["refines"]]["hash"].hex() in producers
|
||
|
||
|
||
def object_rules(ix, i, data):
|
||
"""§6.3, with the objects in hand: entry i's object, the first rule broken is the refusal."""
|
||
e = ix["entries"][i]
|
||
if len(data) != e["byte_len"]:
|
||
raise Refused("object-length")
|
||
if content_hash(data) != e["hash"]:
|
||
raise Refused("object-hash")
|
||
env = read_envelope(data)
|
||
if env is None:
|
||
raise Refused("non-envelope-entry")
|
||
ml, pl, m, cap, parts = env
|
||
if ml != e["man_len"] or pl != e["payload_len"]:
|
||
raise Refused("object-lengths")
|
||
if cap != e["cap"]:
|
||
raise Refused("object-capability")
|
||
wai2 = data[:4] == b"WAI2"
|
||
# A refinement's object is a WAI1 that carries refines (§5.4).
|
||
if e["role"] == REFINEMENT and (wai2 or not refines_ok(ix, i, m.get("refines"))):
|
||
raise Refused("object-refines")
|
||
# derived_from: a WAI1's manifest member; a WAI2's entries', present when any entry
|
||
# carries it and well formed only when every one that does is. null is absent.
|
||
if wai2:
|
||
dfs = [r.get("derived_from") for r in read_json(data[8:8 + ml])["renditions"] if r.get("derived_from") is not None]
|
||
has, good = bool(dfs), all(derived_from_ok(x) for x in dfs)
|
||
else:
|
||
has = m.get("derived_from") is not None
|
||
good = not has or derived_from_ok(m["derived_from"])
|
||
if has != bool(e["flags"] & ENTRY_DERIVED) or not good:
|
||
raise Refused("object-derived-from")
|
||
# §6.2, after the other object rules: where WAI checks a payload's format, the lengths
|
||
# are its counted bytes (staged-measurement §3), so every such payload parses as its
|
||
# format.
|
||
if not all(payload_parses(c, p) for caps, p in parts for c in caps):
|
||
raise Refused("object-lengths")
|
||
return m
|
||
|
||
|
||
# ---------------------------------------------------------------- stages (§5.4, §5.7)
|
||
|
||
RANS_L = 1 << 31
|
||
|
||
|
||
def rans_encode(values, cdf, offset):
|
||
"""integer-payloads §2's coder, written from the text: each value's symbols (its table
|
||
symbol, and past the table's span the bypass escape: a count of 4-bit groups, itself
|
||
in groups of at most 15, then the groups low first), coded last first into a 64-bit
|
||
state that emits its low 32 bits while too large; the stream is the final state, low
|
||
word first, then the emitted words in the order a decoder reads them."""
|
||
n = len(cdf)
|
||
top = n - 2
|
||
syms = []
|
||
for value in values:
|
||
v = value - offset
|
||
sym, raw = (top, -2 * v - 1) if v < 0 else (top, 2 * (v - top)) if v >= top else (v, None)
|
||
syms.append((cdf[sym], cdf[sym + 1] - cdf[sym]))
|
||
if raw is not None:
|
||
groups = 0
|
||
while raw >> (4 * groups):
|
||
groups += 1
|
||
count = groups
|
||
while count >= 15:
|
||
syms.append((None, 15))
|
||
count -= 15
|
||
syms.append((None, count))
|
||
syms += [(None, (raw >> (4 * j)) & 15) for j in range(groups)]
|
||
x, words = RANS_L, []
|
||
for start, f in reversed(syms):
|
||
freq = f if start is not None else 1 << 12
|
||
x_max = ((RANS_L >> 16) << 32) * freq
|
||
if start is not None:
|
||
while x >= x_max:
|
||
words.append(x & M32)
|
||
x >>= 32
|
||
x = ((x // freq) << 16) + x % freq + start
|
||
else:
|
||
if x >= x_max:
|
||
words.append(x & M32)
|
||
x >>= 32
|
||
x = (x << 4) | f
|
||
return struct.pack("<II", x & M32, x >> 32) + b"".join(struct.pack("<I", w) for w in reversed(words))
|
||
|
||
|
||
def wir1_layer(below, src, h, w, q, max_symbols=4095):
|
||
"""§5.4: the refinement that brings each frame of `below` toward its source frame:
|
||
staged-delivery Appendix A.1's WIR1, unit kind RGB8, one unit per frame, the identity
|
||
predictor, one table, the residual r = rdiv(source − below, q) (halves away from zero),
|
||
the table over every residual when they span at most `max_symbols` and otherwise that
|
||
many centred on the median (the lower middle), fitted by §5.5's rule, a residual past
|
||
it through the escape. Returns the payload and the frames it reconstructs,
|
||
clamp(below + r·q, 0, 255)."""
|
||
res = [[quantize(s - b, q) for s, b in zip(sf, bf)] for sf, bf in zip(src, below)]
|
||
flat = sorted(r for rs in res for r in rs)
|
||
lo, hi = flat[0], flat[-1]
|
||
width = min(hi - lo + 1, max_symbols)
|
||
start = lo if hi - lo + 1 == width else clampi(flat[len(flat) // 2] - width // 2, lo, hi - width + 1)
|
||
hist = [0] * width
|
||
for r in flat:
|
||
if start <= r < start + width:
|
||
hist[r - start] += 1
|
||
cdf = hist_to_cdf(hist)
|
||
streams = [rans_encode(rs, cdf, start) for rs in res]
|
||
out = b"WIR1" + bytes([1, 1, 0, 0]) + struct.pack("<7I", len(src), h, w, 3, 0, 0, q) + struct.pack("<iH", start, len(cdf)) + b"\0\0"
|
||
out += b"".join(struct.pack("<I", v) for v in cdf) + b"".join(struct.pack("<I", len(s)) for s in streams) + b"".join(streams)
|
||
recon = [bytes(clampi(b + r * q, 0, 255) for b, r in zip(bf, rs)) for bf, rs in zip(below, res)]
|
||
return out, recon
|
||
|
||
|
||
def wir1_apply(payload, below):
|
||
"""A WIR1 layer applied to the frames below it, by staged_verify.py's decode (its parse
|
||
in the stated order, every stream to its canonical end): the frames it reconstructs."""
|
||
w = SV.parse_wir1(payload, "rgb8")
|
||
SV.check_streams(w)
|
||
if w["n_units"] != len(below):
|
||
raise SV.Refused("units")
|
||
return [bytes(SV.apply_unit(w, u, list(f), w["d"])) for u, f in enumerate(below)]
|
||
|
||
|
||
LEN_BASE = [3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258]
|
||
LEN_EXTRA = [0] * 8 + [1] * 4 + [2] * 4 + [3] * 4 + [4] * 4 + [5] * 4 + [0]
|
||
DIST_BASE = [1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073,
|
||
4097, 6145, 8193, 12289, 16385, 24577]
|
||
DIST_EXTRA = [0, 0, 0, 0] + [k // 2 for k in range(2, 28)]
|
||
|
||
|
||
class DeflateBits:
|
||
"""Bits least significant first, as deflate packs them; a Huffman code most
|
||
significant bit first."""
|
||
def __init__(self):
|
||
self.out, self.acc, self.n = bytearray(), 0, 0
|
||
|
||
def put(self, v, n):
|
||
self.acc |= v << self.n
|
||
self.n += n
|
||
while self.n >= 8:
|
||
self.out.append(self.acc & 255)
|
||
self.acc >>= 8
|
||
self.n -= 8
|
||
|
||
def code(self, c, n):
|
||
self.put(int(format(c, f"0{n}b")[::-1], 2) if n else 0, n)
|
||
|
||
def lit(self, s):
|
||
"""RFC 1951 §3.2.6's fixed literal/length code."""
|
||
if s <= 143:
|
||
self.code(0x30 + s, 8)
|
||
elif s <= 255:
|
||
self.code(0x190 + s - 144, 9)
|
||
elif s <= 279:
|
||
self.code(s - 256, 7)
|
||
else:
|
||
self.code(0xC0 + s - 280, 8)
|
||
|
||
def finish(self):
|
||
return bytes(self.out + (bytes([self.acc]) if self.n else b""))
|
||
|
||
|
||
def deflate_fixed(d):
|
||
"""§5.7's deflate: one final block of the fixed codes; at each position with three
|
||
bytes left, the latest earlier position within 32768 bytes that starts with the same
|
||
three bytes is the match, as long as the two runs agree (at most 258); else a literal;
|
||
every position a symbol covers with three bytes left becomes its three bytes' latest."""
|
||
b = DeflateBits()
|
||
b.put(1, 1)
|
||
b.put(1, 2)
|
||
last, n, i = {}, len(d), 0
|
||
while i < n:
|
||
j = last.get(d[i:i + 3]) if i + 3 <= n else None
|
||
if j is not None and i - j <= 32768:
|
||
m = 0
|
||
while m < min(258, n - i) and d[j + m] == d[i + m]:
|
||
m += 1
|
||
li = max(k for k, x in enumerate(LEN_BASE) if x <= m)
|
||
b.lit(257 + li)
|
||
b.put(m - LEN_BASE[li], LEN_EXTRA[li])
|
||
di = max(k for k, x in enumerate(DIST_BASE) if x <= i - j)
|
||
b.code(di, 5)
|
||
b.put(i - j - DIST_BASE[di], DIST_EXTRA[di])
|
||
else:
|
||
b.lit(d[i])
|
||
m = 1
|
||
for q in range(i, i + m):
|
||
if q + 3 <= n:
|
||
last[d[q:q + 3]] = q
|
||
i += m
|
||
b.lit(256)
|
||
return b.finish()
|
||
|
||
|
||
def png_still(rgb, w, h):
|
||
"""§5.7: a frame as the still the reference packager writes: IHDR (8-bit RGB), one IDAT
|
||
of a zlib stream (0x78 0x01, the deflate above over the rows each filtered Sub, the
|
||
Adler-32), IEND."""
|
||
stride = 3 * w
|
||
raw = bytearray()
|
||
for y in range(h):
|
||
row = rgb[y * stride:(y + 1) * stride]
|
||
raw.append(1)
|
||
raw += bytes((row[i] - (row[i - 3] if i >= 3 else 0)) & 255 for i in range(stride))
|
||
z = b"\x78\x01" + deflate_fixed(bytes(raw)) + struct.pack(">I", zlib.adler32(bytes(raw)))
|
||
|
||
def chunk(kind, data):
|
||
return struct.pack(">I", len(data)) + kind + data + struct.pack(">I", zlib.crc32(kind + data))
|
||
return b"\x89PNG\r\n\x1a\n" + chunk(b"IHDR", struct.pack(">IIBBBBB", w, h, 8, 2, 0, 0, 0)) + chunk(b"IDAT", z) + chunk(b"IEND", b"")
|
||
|
||
|
||
def png_frame(b):
|
||
"""§5.7: a still read as any PNG of 8-bit RGB without interlace, by PNG's chunk rules:
|
||
every chunk's type four letters and its CRC checked; IHDR first (8-bit RGB, methods
|
||
0, interlace 0); an optional PLTE before the image data, read past; the IDATs
|
||
consecutive; ancillary chunks read past; any other critical chunk refused; IEND last,
|
||
empty, nothing after it; the zlib stream whole with its Adler-32, its rows exactly
|
||
1 + 3·width bytes, its window 32 KiB at most and nothing after it; the five filters. Its width, height and RGB8 bytes; ValueError
|
||
where it is not such a PNG."""
|
||
if b[:8] != b"\x89PNG\r\n\x1a\n":
|
||
raise ValueError("not a PNG signature")
|
||
at, ihdr, z, ended, plte, idat = 8, None, b"", False, False, 0
|
||
while at < len(b):
|
||
if ended:
|
||
raise ValueError("bytes after IEND")
|
||
if at + 12 > len(b):
|
||
raise ValueError("a chunk past the end")
|
||
n = struct.unpack_from(">I", b, at)[0]
|
||
kind, data = b[at + 4:at + 8], b[at + 8:at + 8 + n]
|
||
if at + 12 + n > len(b):
|
||
raise ValueError("a chunk past the end")
|
||
if not all(65 <= c <= 90 or 97 <= c <= 122 for c in kind):
|
||
raise ValueError("a chunk type that is not four letters")
|
||
if zlib.crc32(kind + data) != struct.unpack_from(">I", b, at + 8 + n)[0]:
|
||
raise ValueError("a chunk CRC")
|
||
if idat == 1 and kind != b"IDAT":
|
||
idat = 2
|
||
if kind == b"IHDR":
|
||
if ihdr is not None or at != 8 or n != 13:
|
||
raise ValueError("IHDR is not one 13-byte chunk first")
|
||
w, h = struct.unpack(">II", data[:8])
|
||
if data[8:] != bytes([8, 2, 0, 0, 0]) or w == 0 or h == 0 or 3 * w * h > 1 << 26:
|
||
raise ValueError("not 8-bit RGB without interlace, or a size past 2^26 samples")
|
||
ihdr = (w, h)
|
||
elif ihdr is None:
|
||
raise ValueError("a chunk before IHDR")
|
||
elif kind == b"PLTE":
|
||
if plte or idat or n == 0 or n % 3 or n > 768:
|
||
raise ValueError("PLTE: more than one, after the image data, or not 1 to 256 entries")
|
||
plte = True
|
||
elif kind == b"IDAT":
|
||
if idat == 2:
|
||
raise ValueError("IDAT chunks that are not consecutive")
|
||
idat = 1
|
||
z += data
|
||
elif kind == b"IEND":
|
||
if n:
|
||
raise ValueError("IEND with data")
|
||
ended = True
|
||
elif not kind[0] & 0x20:
|
||
raise ValueError("an unknown critical chunk")
|
||
at += 12 + n
|
||
if ihdr is None or not ended:
|
||
raise ValueError("no IHDR or no IEND")
|
||
w, h = ihdr
|
||
if not z or z[0] >> 4 > 7:
|
||
raise ValueError("the zlib stream: no header, or a window past 32 KiB")
|
||
d = zlib.decompressobj()
|
||
try:
|
||
raw = d.decompress(z, (3 * w + 1) * h + 1)
|
||
except zlib.error as x:
|
||
raise ValueError(f"the zlib stream: {x}")
|
||
if not d.eof or d.unused_data or d.unconsumed_tail or len(raw) != (3 * w + 1) * h:
|
||
raise ValueError("the rows are not the picture's size, or the stream is not whole")
|
||
stride, out = 3 * w, bytearray(3 * w * h)
|
||
for y in range(h):
|
||
f, row = raw[y * (stride + 1)], raw[y * (stride + 1) + 1:(y + 1) * (stride + 1)]
|
||
if f > 4:
|
||
raise ValueError(f"row {y}: filter {f}")
|
||
for i in range(stride):
|
||
a = out[y * stride + i - 3] if i >= 3 else 0
|
||
up = out[(y - 1) * stride + i] if y else 0
|
||
c = out[(y - 1) * stride + i - 3] if y and i >= 3 else 0
|
||
if f == 4:
|
||
pp = a + up - c
|
||
pa, pb, pc = abs(pp - a), abs(pp - up), abs(pp - c)
|
||
pr = a if pa <= pb and pa <= pc else (up if pb <= pc else c)
|
||
else:
|
||
pr = (0, a, up, (a + up) // 2)[f]
|
||
out[y * stride + i] = (row[i] + pr) & 255
|
||
return w, h, bytes(out)
|
||
|
||
|
||
# ---------------------------------------------------------------- delivery (§8.9)
|
||
|
||
|
||
def deliver(P, row, published, plan):
|
||
"""§8.9: a live packer's publications delivered to a subscriber whose class plan is
|
||
`row`, simulated by staged-measurement Appendix B's simulator (sim_verify.py's, written
|
||
from that text): the objects the class lists, on the frames as units, each published
|
||
at its frame's instant plus the encoder's delay plus its layer's lag, a refinement with
|
||
its track's timeout (deadline − lag); live, strict priority (§8.5) or fifo, a subgroup
|
||
each, the subscriber's cap, one worker, decodes of 1 µs, playout from the start-up, a
|
||
late refinement discarded. Per piece, at its first frame's instant: the highest layer
|
||
presented, its base's arrival and the layers dropped."""
|
||
comp = P["component"][0]
|
||
num, den = comp["timebase"]["num"], comp["timebase"]["den"]
|
||
if 1_000_000 * den % num:
|
||
raise ValueError("a frame that is not a whole number of microseconds")
|
||
frame_us = 1_000_000 * den // num
|
||
lag = P["live"]["layer_lag_ms"]
|
||
objects = []
|
||
for x in published:
|
||
listed = row[x["piece"]]
|
||
if listed == 0xFF or x["layer"] > listed:
|
||
continue
|
||
lg = lag[x["layer"]] if x["layer"] < len(lag) else 0
|
||
o = {"id": f"p{x['piece']}/l{x['layer']}", "base": x["piece"], "layer": x["layer"],
|
||
"units": [x["start"], x["duration"]], "bytes": x["bytes"],
|
||
"capture_us": x["at_frame"] * frame_us + plan["enc_us"] + lg * 1000}
|
||
if x["layer"]:
|
||
o["timeout_us"] = max(0, x["deadline_ms"] - lg) * 1000
|
||
objects.append(o)
|
||
m = {"unit_us": frame_us, "objects": objects}
|
||
sp = {"plan": "wai-sim/1", "mode": "live",
|
||
"link": {"rtt_us": plan["rtt_us"], "bytes_per_ms": plan["bytes_per_ms"], "chunk_bytes": plan["chunk_bytes"]},
|
||
"sender": {"schedule": "fifo" if plan.get("fifo") else "strict-priority", "carriage": "subgroup",
|
||
"layer_cap": plan.get("layer_cap"), "delivery_timeout_us": {"base": None, "refinement": None},
|
||
"vod_window_us": None},
|
||
"sink": {"workers": 1, "decode_order": "base-first", "decode_us": {"base": 1, "refinement": 1},
|
||
"playout_start_us": plan["startup_us"], "late_refinement": "discard"}}
|
||
# A class that lists nothing of any piece sends nothing.
|
||
r = SIM.simulate(m, sp) if objects else {"objects": [], "presentations": [], "delivered": 0, "dropped": 0,
|
||
"abandoned": 0, "never_sent_capped": 0}
|
||
pieces = {}
|
||
for x in published:
|
||
pieces.setdefault(x["piece"], {"piece": x["piece"], "due_us": plan["startup_us"] + x["start"] * frame_us,
|
||
"presented": None, "floor_only": row[x["piece"]] == 0xFF,
|
||
"base_arrival_us": None, "dropped": [], "_start": x["start"]})
|
||
for o, ev in zip(objects, r["objects"]):
|
||
pc = pieces[o["base"]]
|
||
if o["layer"] == 0:
|
||
pc["base_arrival_us"] = ev["arrived_us"]
|
||
if ev["state"] in ("dropped", "abandoned"):
|
||
pc["dropped"].append(o["layer"])
|
||
for pc in pieces.values():
|
||
at = [l for u, l, tt in r["presentations"] if u == pc["_start"] and tt <= pc["due_us"]]
|
||
if 1 in at:
|
||
pc["presented"] = max(at) - 1
|
||
del pc["_start"]
|
||
return {"label": "simulated", "pieces": [pieces[k] for k in sorted(pieces)], "delivered": r["delivered"],
|
||
"dropped": r["dropped"], "abandoned": r["abandoned"], "never_sent_capped": r["never_sent_capped"]}
|
||
|
||
|
||
def published_of(P, ix, n_frames):
|
||
"""What a live packer publishes for the package whose index is `ix` (§9.2, §4.3): each
|
||
piece's bases and refinements, published once the frame that decides the piece's end
|
||
has arrived — the frame lookahead_frames past the next piece's start, or the source's
|
||
last."""
|
||
L = P["segmentation"]["lookahead_frames"]
|
||
pcs, E = ix["pieces"], ix["entries"]
|
||
out = []
|
||
for k, pc in enumerate(pcs):
|
||
at = min(pcs[k + 1]["start"] + L, n_frames - 1) if k + 1 < len(pcs) else n_frames - 1
|
||
for e in E[pc["first_entry"]:pc["first_entry"] + pc["n_entries"]]:
|
||
if e["role"] in (BASE, REFINEMENT):
|
||
out.append({"piece": ix["first_piece"] + k, "start": pc["start"], "duration": pc["duration"], "at_frame": at,
|
||
"layer": e["layer"], "deadline_ms": e["deadline"], "bytes": e["byte_len"]})
|
||
return out
|
||
|
||
|
||
# ---------------------------------------------------------------- package receipts (§9)
|
||
|
||
|
||
PARENT_KINDS = ["none", "live-window", "re-pack"]
|
||
# energy-measurement §2.7: a class's tag, and the evidence members each class carries.
|
||
CLASS_TAGS = {"HwShunt": 1, "ModelBased": 2, "Estimator": 3, "OnChipCounter": 4, "CalibratedInstrument": 5}
|
||
FILTERING = {"Unknown": 0, "Off": 1, "On": 2}
|
||
EVIDENCE = {"OnChipCounter": {"energy_uncertainty", "energy_counter_filtering"},
|
||
"CalibratedInstrument": {"energy_uncertainty", "energy_calibration_ref"}}
|
||
EVIDENCE_MEMBERS = {"energy_uncertainty", "energy_counter_filtering", "energy_calibration_ref"}
|
||
V2_MEMBERS = {"asset_id", "components", "grant", "joules_micro", "kind", "packager", "parent_kind",
|
||
"parent_receipt_hash", "policy_digest", "receipt_hash", "sig", "signer_id", "signer_pubkey",
|
||
"stored_bytes", "total_bytes", "total_entries", "unique_objects", "version", "work_units"}
|
||
V1_MEMBERS = {"asset_id", "grant", "joules_micro", "kind", "parent_receipt_hash",
|
||
"receipt_hash", "segment_map_root", "segments", "sig", "signer_id", "signer_pubkey", "stored_bytes",
|
||
"total_bytes", "total_segments", "unique_segments", "work_segment_ops"}
|
||
RECEIPT_ID = b"wai:video-package-receipt-id\x01"
|
||
|
||
|
||
def be(v, n):
|
||
return v.to_bytes(n, "big")
|
||
|
||
|
||
def lp(s):
|
||
s = s.encode()
|
||
return be(len(s), 4) + s
|
||
|
||
|
||
def is_hex(s, n):
|
||
return isinstance(s, str) and len(s) == n and all(c in "0123456789abcdef" for c in s)
|
||
|
||
|
||
def is_u53(v):
|
||
return is_int(v) and 0 <= v <= MAX_INTEGER
|
||
|
||
|
||
def need(ok):
|
||
if not ok:
|
||
raise Refused("receipt-json")
|
||
|
||
|
||
def energy_label(j, strict):
|
||
"""energy-measurement §2.7: the class's signed encoding from its JSON members, or None
|
||
for no class. Evidence without its class, a class without its evidence, and evidence a
|
||
class does not carry are refused. Version 2 (`strict`, §9.1) writes the evidence exactly
|
||
as its class carries it, with no null in its place; version 1 (§9.3) reads a null
|
||
evidence member as absent, and an on-chip counter's filtering, absent, as Unknown."""
|
||
cls = j.get("energy_provenance")
|
||
if strict:
|
||
need(not any(k in j and j[k] is None for k in EVIDENCE_MEMBERS))
|
||
present = EVIDENCE_MEMBERS & set(j)
|
||
else:
|
||
present = {k for k in EVIDENCE_MEMBERS if j.get(k) is not None}
|
||
if cls is None:
|
||
need(not present)
|
||
return None
|
||
allowed = EVIDENCE.get(cls, set())
|
||
lenient = set() if strict else {"energy_counter_filtering"}
|
||
need(isinstance(cls, str) and cls in CLASS_TAGS and present <= allowed and (allowed - present) <= lenient)
|
||
out = bytes([CLASS_TAGS[cls]])
|
||
if cls in EVIDENCE:
|
||
u = j["energy_uncertainty"]
|
||
need(isinstance(u, dict) and set(u) == {"relative_ppm", "window_us"} and is_u53(u["relative_ppm"])
|
||
and u["relative_ppm"] <= U32 and is_u53(u["window_us"]))
|
||
out += be(u["relative_ppm"], 4) + be(u["window_us"], 8)
|
||
if cls == "OnChipCounter":
|
||
f = j.get("energy_counter_filtering") or "Unknown"
|
||
need(isinstance(f, str) and f in FILTERING)
|
||
out += bytes([FILTERING[f]])
|
||
else:
|
||
ref = j["energy_calibration_ref"]
|
||
need(isinstance(ref, str) and ref != "")
|
||
out += lp(ref)
|
||
return out
|
||
|
||
|
||
def grant_bytes(g):
|
||
"""The grant as §9.1 (and version 1) sign it: grant_hash ‖ u32 len ‖ capability ‖ u64
|
||
joule_ceiling_micro (null: 2^64 − 1) ‖ u8 0, or u8 1 ‖ u64 funds_ceiling."""
|
||
need(isinstance(g, dict) and set(g) == {"capability", "funds_ceiling", "grant_hash", "joule_ceiling_micro"})
|
||
need(isinstance(g["capability"], str) and is_hex(g["grant_hash"], 64))
|
||
need(g["joule_ceiling_micro"] is None or is_u53(g["joule_ceiling_micro"]))
|
||
need(g["funds_ceiling"] is None or is_u53(g["funds_ceiling"]))
|
||
out = bytes.fromhex(g["grant_hash"]) + lp(g["capability"])
|
||
out += be(U64 if g["joule_ceiling_micro"] is None else g["joule_ceiling_micro"], 8)
|
||
return out + (b"\x00" if g["funds_ceiling"] is None else b"\x01" + be(g["funds_ceiling"], 8))
|
||
|
||
|
||
def v1_leaf(s):
|
||
return blake3(b"wai:video-package-seg\x01" + be(s["index"], 8) + bytes.fromhex(s["content_hash"])
|
||
+ be(s["duration_ms"], 8) + be(s["byte_len"], 8))
|
||
|
||
|
||
def read_receipt(b):
|
||
"""§9.1 and §9.3: a receipt's JSON read as version 1 or 2, every member in its form,
|
||
its signing payload rebuilt and its receipt_hash checked; refuses receipt-version and
|
||
receipt-json. Returns the version, the JSON, the payload and the class encoding."""
|
||
# RFC 8259 with no member named twice; -0 is read as a number that is not an integer,
|
||
# so it is refused where an integer is required; a string that cannot be written in
|
||
# UTF-8 (a lone surrogate escape) has no form (receipt-json).
|
||
j = read_json(b, minus_zero=True)
|
||
need(isinstance(j, dict))
|
||
try:
|
||
r = receipt_payload(j)
|
||
need(blake3(RECEIPT_ID + r["payload"] + bytes.fromhex(j["sig"])).hex() == j["receipt_hash"])
|
||
except (UnicodeEncodeError, OverflowError, ValueError):
|
||
raise Refused("receipt-json")
|
||
return r
|
||
|
||
|
||
def receipt_payload(j):
|
||
"""A receipt's members read in their forms and its signing payload: version 2's
|
||
(§9.1), or version 1's (§9.3), the version a receipt without `version` has."""
|
||
v = j.get("version", 1)
|
||
if not is_int(v) or v not in (1, 2):
|
||
raise Refused("receipt-version")
|
||
need(isinstance(j.get("asset_id"), str) and j.get("kind") == "package")
|
||
# Version 2 writes every member, null where there is none (energy_provenance among
|
||
# them, §9.1); version 1 may leave energy_provenance out (§9.3).
|
||
if v == 2:
|
||
need(V2_MEMBERS | {"energy_provenance"} <= set(j) and set(j) <= V2_MEMBERS | {"energy_provenance"} | EVIDENCE_MEMBERS)
|
||
else:
|
||
need(V1_MEMBERS <= set(j) and set(j) <= V1_MEMBERS | {"version", "energy_provenance"} | EVIDENCE_MEMBERS)
|
||
label = energy_label(j, v == 2)
|
||
counts = ("total_entries", "unique_objects", "total_bytes", "stored_bytes", "work_units") if v == 2 else \
|
||
("total_segments", "unique_segments", "total_bytes", "stored_bytes", "work_segment_ops")
|
||
need(all(is_u53(j[k]) for k in counts + ("joules_micro",)))
|
||
need(is_hex(j["receipt_hash"], 64) and is_hex(j["sig"], 128) and is_hex(j["signer_pubkey"], 64))
|
||
need(isinstance(j["signer_id"], str))
|
||
grant = grant_bytes(j["grant"])
|
||
signer = bytes.fromhex(j["signer_pubkey"]) + lp(j["signer_id"])
|
||
figure = be(j["joules_micro"], 8)
|
||
if v == 2:
|
||
need(j["parent_kind"] in PARENT_KINDS if isinstance(j["parent_kind"], str) else False)
|
||
none = j["parent_kind"] == "none"
|
||
need(j["parent_receipt_hash"] is None if none else is_hex(j["parent_receipt_hash"], 64))
|
||
need(is_hex(j["policy_digest"], 64) and isinstance(j["packager"], str))
|
||
cs = j["components"]
|
||
need(isinstance(cs, list))
|
||
for c in cs:
|
||
need(isinstance(c, dict) and set(c) == {"id", "index", "index_digest", "media_class", "stage_root"})
|
||
need(isinstance(c["id"], str) and c["id"] != "" and c["index"] == f"index-{c['id']}.wsix")
|
||
need(is_hex(c["index_digest"], 64) and is_hex(c["stage_root"], 64))
|
||
need(isinstance(c["media_class"], str) and c["media_class"] in MEDIA_CLASSES)
|
||
payload = b"wai:pack-receipt" + (b"\x01" if label is None else b"\x02")
|
||
payload += lp(j["asset_id"]) + bytes.fromhex(j["policy_digest"])
|
||
payload += bytes([PARENT_KINDS.index(j["parent_kind"])])
|
||
payload += b"" if none else bytes.fromhex(j["parent_receipt_hash"])
|
||
payload += be(len(cs), 4)
|
||
for c in cs:
|
||
payload += lp(c["id"]) + bytes([MEDIA_CLASSES.index(c["media_class"])])
|
||
payload += bytes.fromhex(c["index_digest"]) + bytes.fromhex(c["stage_root"])
|
||
payload += b"".join(be(j[k], 8) for k in counts) + lp(j["packager"])
|
||
payload += figure + (label or b"") + grant + signer
|
||
else:
|
||
need(j["parent_receipt_hash"] is None or is_hex(j["parent_receipt_hash"], 64))
|
||
need(is_hex(j["segment_map_root"], 64) and isinstance(j["segments"], list))
|
||
for s in j["segments"]:
|
||
need(isinstance(s, dict) and set(s) == {"byte_len", "content_hash", "duration_ms", "index"})
|
||
need(is_u53(s["byte_len"]) and is_u53(s["duration_ms"]) and is_u53(s["index"]))
|
||
need(is_hex(s["content_hash"], 64))
|
||
payload = b"wai:video-package-receipt\x01" + lp(j["asset_id"]) + bytes.fromhex(j["segment_map_root"])
|
||
payload += b"".join(be(j[k], 8) for k in counts) + figure + (label or b"\x00") + grant
|
||
payload += b"\x00" if j["parent_receipt_hash"] is None else b"\x01" + bytes.fromhex(j["parent_receipt_hash"])
|
||
payload += be(len(j["segments"]), 8) + b"".join(v1_leaf(s) for s in j["segments"]) + signer
|
||
return {"version": v, "json": j, "payload": payload, "label": label}
|
||
|
||
|
||
def ed25519_verifies(pk, sig, msg):
|
||
"""WAI's strict rule (SPEC §7 *Signatures*): a canonical key of large order, S < L, R
|
||
not of small order, and the cofactorless equation with R compared byte for byte. The
|
||
`cryptography` package alone accepts a key of small order, a second spelling of a key,
|
||
and an R of small order."""
|
||
return ED.verifies(pk, msg, sig)
|
||
|
||
|
||
def ed25519_from_seed(seed, msg):
|
||
"""The public key of a 32-byte seed and its signature over msg (RFC 8032; deterministic)."""
|
||
from cryptography.hazmat.primitives import serialization
|
||
from cryptography.hazmat.primitives.asymmetric import ed25519
|
||
sk = ed25519.Ed25519PrivateKey.from_private_bytes(seed)
|
||
pk = sk.public_key().public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
|
||
return pk, sk.sign(msg)
|
||
|
||
|
||
def package_counts(ixs):
|
||
"""§9.1: entries, distinct content hashes, referenced and stored bytes, work units; None
|
||
when a content hash comes at two lengths."""
|
||
E = [e for ix in ixs for e in ix["entries"]]
|
||
first = {}
|
||
for e in E:
|
||
if first.setdefault(e["hash"], e["byte_len"]) != e["byte_len"]:
|
||
return None
|
||
return {"total_entries": len(E), "unique_objects": len(first), "total_bytes": sum(e["byte_len"] for e in E),
|
||
"stored_bytes": sum(first.values()), "work_units": len(E)}
|
||
|
||
|
||
def verify_receipt_v2(r, index_files):
|
||
"""§9.1's verification of a version-2 receipt against its indexes, in its components'
|
||
order: the first refusal that applies. Returns the parsed indexes."""
|
||
j = r["json"]
|
||
if r["version"] != 2:
|
||
raise Refused("receipt-version")
|
||
if not j["components"] or len(index_files) != len(j["components"]):
|
||
raise Refused("receipt-index-count")
|
||
ixs = []
|
||
for c, b in zip(j["components"], index_files):
|
||
ix = parse_index(b)
|
||
index_rules(ix)
|
||
if index_digest(b).hex() != c["index_digest"]:
|
||
raise Refused("receipt-index-digest")
|
||
if stage_root(ix).hex() != c["stage_root"]:
|
||
raise Refused("receipt-stage-root")
|
||
if ix["component"] != c["id"] or MEDIA_CLASSES[ix["media_class"]] != c["media_class"]:
|
||
raise Refused("receipt-component")
|
||
if ix["policy_digest"].hex() != j["policy_digest"]:
|
||
raise Refused("receipt-policy-digest")
|
||
ixs.append(ix)
|
||
if len({c["id"] for c in j["components"]}) != len(j["components"]):
|
||
raise Refused("receipt-component")
|
||
counts = package_counts(ixs)
|
||
if counts is None or any(counts[k] != j[k] for k in counts):
|
||
raise Refused("receipt-counts")
|
||
live = [any(ix["prev"]) for ix in ixs]
|
||
if not (all(live) if j["parent_kind"] == "live-window" else not any(live)):
|
||
raise Refused("receipt-parent-kind")
|
||
lab = r["label"]
|
||
if (lab is None) != (j["joules_micro"] == 0) or (lab is not None and lab[0] == CLASS_TAGS["HwShunt"]):
|
||
raise Refused("receipt-energy")
|
||
if not ed25519_verifies(bytes.fromhex(j["signer_pubkey"]), bytes.fromhex(j["sig"]), r["payload"]):
|
||
raise Refused("receipt-signature")
|
||
return ixs
|
||
|
||
|
||
# ---------------------------------------------------------------- the AV1 floor (§7)
|
||
|
||
|
||
class Malformed(Exception):
|
||
pass
|
||
|
||
|
||
def obus(b):
|
||
"""The low-overhead OBU format (AV1 §5.3): every OBU with its size field, read into
|
||
(type, the OBU's bytes, its payload)."""
|
||
out, p = [], 0
|
||
while p < len(b):
|
||
hdr = b[p]
|
||
if hdr & 0x80 or not hdr & 0x02:
|
||
raise Malformed("the forbidden bit, or an OBU without its size field")
|
||
q, size, shift = p + 1 + (hdr >> 2 & 1), 0, 0
|
||
for k in range(8):
|
||
if q >= len(b):
|
||
raise Malformed("a size field past the end")
|
||
size |= (b[q] & 0x7F) << shift
|
||
shift, q = shift + 7, q + 1
|
||
if not b[q - 1] & 0x80:
|
||
break
|
||
else:
|
||
raise Malformed("a size field over 8 bytes")
|
||
if q + size > len(b):
|
||
raise Malformed("an OBU past the end")
|
||
out.append((hdr >> 3 & 0x0F, b[p:q + size], b[q:q + size]))
|
||
p = q + size
|
||
return out
|
||
|
||
|
||
OBU_SEQ, OBU_TD, OBU_FRAME_HEADER, OBU_FRAME = 1, 2, 3, 6
|
||
|
||
|
||
def temporal_units(b):
|
||
"""A stream split at each temporal delimiter: (the unit's bytes, its OBUs)."""
|
||
units = []
|
||
for t, whole, payload in obus(b):
|
||
if t == OBU_TD:
|
||
units.append([b"", []])
|
||
elif not units:
|
||
raise Malformed("an OBU before the first temporal delimiter")
|
||
units[-1][0] += whole
|
||
units[-1][1].append((t, whole, payload))
|
||
return [(u, o) for u, o in units]
|
||
|
||
|
||
class Bits:
|
||
def __init__(self, b):
|
||
self.b, self.p = b, 0
|
||
|
||
def f(self, n):
|
||
v = 0
|
||
for _ in range(n):
|
||
if self.p >= 8 * len(self.b):
|
||
raise Malformed("a sequence header past its end")
|
||
v = v << 1 | (self.b[self.p >> 3] >> (7 - (self.p & 7)) & 1)
|
||
self.p += 1
|
||
return v
|
||
|
||
def uvlc(self):
|
||
zeros = 0
|
||
while not self.f(1):
|
||
zeros += 1
|
||
return (1 << 32) - 1 if zeros >= 32 else self.f(zeros) + (1 << zeros) - 1
|
||
|
||
|
||
def sequence_header(payload):
|
||
"""sequence_header_obu (AV1 §5.5): profile, operating point 0's level and tier, the
|
||
frame size, and color_config's bit depth, monochrome and chroma fields."""
|
||
r = Bits(payload)
|
||
s = {"profile": r.f(3), "still": r.f(1), "reduced": r.f(1)}
|
||
if s["reduced"]:
|
||
s["level"], s["tier"] = r.f(5), 0
|
||
else:
|
||
decoder_model = 0
|
||
if r.f(1): # timing_info_present_flag
|
||
r.f(32), r.f(32)
|
||
if r.f(1): # equal_picture_interval
|
||
r.uvlc()
|
||
decoder_model = r.f(1)
|
||
if decoder_model:
|
||
delay_bits = r.f(5) + 1
|
||
r.f(32), r.f(5), r.f(5)
|
||
initial_delay = r.f(1)
|
||
for i in range(r.f(5) + 1):
|
||
r.f(12)
|
||
level = r.f(5)
|
||
tier = r.f(1) if level > 7 else 0
|
||
if i == 0:
|
||
s["level"], s["tier"] = level, tier
|
||
if decoder_model and r.f(1):
|
||
r.f(delay_bits), r.f(delay_bits), r.f(1)
|
||
if initial_delay and r.f(1):
|
||
r.f(4)
|
||
wb, hb = r.f(4) + 1, r.f(4) + 1
|
||
s["max_w"], s["max_h"] = r.f(wb) + 1, r.f(hb) + 1
|
||
if not s["reduced"] and r.f(1): # frame_id_numbers_present_flag
|
||
r.f(4), r.f(3)
|
||
r.f(1), r.f(1), r.f(1) # 128x128 superblock, filter intra, intra edge filter
|
||
if not s["reduced"]:
|
||
r.f(1), r.f(1), r.f(1), r.f(1) # interintra, masked compound, warped motion, dual filter
|
||
order_hint = r.f(1)
|
||
if order_hint:
|
||
r.f(1), r.f(1) # jnt_comp, ref_frame_mvs
|
||
screen = 2 if r.f(1) else r.f(1) # seq_choose_screen_content_tools, else its value
|
||
if screen > 0 and not r.f(1): # seq_choose_integer_mv
|
||
r.f(1)
|
||
if order_hint:
|
||
r.f(3)
|
||
r.f(1), r.f(1), r.f(1) # superres, cdef, restoration
|
||
hbd = r.f(1)
|
||
s["high_bitdepth"], s["twelve_bit"] = hbd, 0
|
||
if s["profile"] == 2 and hbd:
|
||
s["twelve_bit"] = r.f(1)
|
||
s["bit_depth"] = 12 if s["twelve_bit"] else 10
|
||
else:
|
||
s["bit_depth"] = 10 if hbd else 8
|
||
s["mono"] = 0 if s["profile"] == 1 else r.f(1)
|
||
cp = tc = mc = 2
|
||
if r.f(1): # color_description_present_flag
|
||
cp, tc, mc = r.f(8), r.f(8), r.f(8)
|
||
s["csp"] = 0
|
||
if s["mono"]:
|
||
r.f(1)
|
||
s["ssx"] = s["ssy"] = 1
|
||
elif cp == 1 and tc == 13 and mc == 0:
|
||
s["ssx"] = s["ssy"] = 0
|
||
else:
|
||
r.f(1)
|
||
if s["profile"] == 0:
|
||
s["ssx"] = s["ssy"] = 1
|
||
elif s["profile"] == 1:
|
||
s["ssx"] = s["ssy"] = 0
|
||
elif s["bit_depth"] == 12:
|
||
s["ssx"] = r.f(1)
|
||
s["ssy"] = r.f(1) if s["ssx"] else 0
|
||
else:
|
||
s["ssx"], s["ssy"] = 1, 0
|
||
if s["ssx"] and s["ssy"]:
|
||
s["csp"] = r.f(2)
|
||
return s
|
||
|
||
|
||
def unit_frame(obu_list):
|
||
"""The first frame header or frame OBU of a unit, or None."""
|
||
return next((p for t, _, p in obu_list if t in (OBU_FRAME_HEADER, OBU_FRAME)), None)
|
||
|
||
|
||
def shown_key(obu_list, seq):
|
||
"""§7.3: a unit starts at a shown key frame when its first frame header has
|
||
show_existing_frame 0, frame_type KEY_FRAME and show_frame 1 (AV1 §5.9.2), or the
|
||
sequence uses the reduced still-picture header."""
|
||
p = unit_frame(obu_list)
|
||
if p is None:
|
||
return False
|
||
if seq["reduced"]:
|
||
return True
|
||
return len(p) > 0 and p[0] >> 7 == 0 and p[0] >> 5 & 3 == 0 and p[0] >> 4 & 1 == 1
|
||
|
||
|
||
def key_frame(obu_list, seq):
|
||
"""A unit whose first frame header is a key frame, shown or not."""
|
||
p = unit_frame(obu_list)
|
||
return p is not None and (seq["reduced"] or (len(p) > 0 and p[0] >> 7 == 0 and p[0] >> 5 & 3 == 0))
|
||
|
||
|
||
def av1c(seq, seq_obu):
|
||
"""§7.6: the AV1 binding's configuration record: marker and version 1, the profile,
|
||
level, tier, bit depth, monochrome and chroma fields, no initial presentation delay,
|
||
then the sequence header OBU."""
|
||
return bytes([0x81, seq["profile"] << 5 | seq["level"],
|
||
seq["tier"] << 7 | seq["high_bitdepth"] << 6 | seq["twelve_bit"] << 5 | seq["mono"] << 4
|
||
| seq["ssx"] << 3 | seq["ssy"] << 2 | seq["csp"], 0]) + seq_obu
|
||
|
||
|
||
def av1_codecs(seq):
|
||
"""§7.6: av01.<profile>.<level, two digits><M or H>.<bit depth, two digits>."""
|
||
return f"av01.{seq['profile']}.{seq['level']:02d}{'H' if seq['tier'] else 'M'}.{seq['bit_depth']:02d}"
|
||
|
||
|
||
def floor_payload(p):
|
||
"""§7.2: n, h, w, fps_num, fps_den as u32 LE, then n units, each a u32 LE length and its
|
||
bytes; None when it is not that."""
|
||
if len(p) < 20:
|
||
return None
|
||
n, h, w, fn, fd = (le(p, 4 * i, 4) for i in range(5))
|
||
units, at = [], 20
|
||
for _ in range(n):
|
||
if at + 4 > len(p) or at + 4 + le(p, at, 4) > len(p):
|
||
return None
|
||
units.append(p[at + 4:at + 4 + le(p, at, 4)])
|
||
at += 4 + le(p, at, 4)
|
||
return (n, h, w, fn, fd, units) if at == len(p) else None
|
||
|
||
|
||
# ---------------------------------------------------------------- CMAF (§7.6)
|
||
|
||
|
||
def u16(v):
|
||
return be(v, 2)
|
||
|
||
|
||
def u32(v):
|
||
return be(v, 4)
|
||
|
||
|
||
def box(kind, *parts):
|
||
body = b"".join(parts)
|
||
return u32(8 + len(body)) + kind + body
|
||
|
||
|
||
def full_box(kind, version, flags, *parts):
|
||
return box(kind, bytes([version]) + be(flags, 3), *parts)
|
||
|
||
|
||
UNITY = b"".join(u32(x) for x in (0x00010000, 0, 0, 0, 0x00010000, 0, 0, 0, 0x40000000))
|
||
|
||
|
||
def language(code):
|
||
"""ISO/IEC 14496-12 mdhd: three letters, each minus 0x60 in five bits."""
|
||
return (ord(code[0]) - 0x60) << 10 | (ord(code[1]) - 0x60) << 5 | (ord(code[2]) - 0x60)
|
||
|
||
|
||
def init_mp4(tb_num, w, h, config):
|
||
"""§7.6's init.mp4: ftyp, then moov (mvhd, trak, mvex), every time 0."""
|
||
ftyp = box(b"ftyp", b"cmfc", u32(0), b"cmfc", b"iso6", b"av01")
|
||
mvhd = full_box(b"mvhd", 0, 0, u32(0), u32(0), u32(tb_num), u32(0), u32(0x00010000), u16(0x0100),
|
||
bytes(10), UNITY, bytes(24), u32(2))
|
||
tkhd = full_box(b"tkhd", 0, 3, u32(0), u32(0), u32(1), u32(0), u32(0), bytes(8), u16(0), u16(0), u16(0),
|
||
u16(0), UNITY, u32(w << 16), u32(h << 16))
|
||
mdhd = full_box(b"mdhd", 0, 0, u32(0), u32(0), u32(tb_num), u32(0), u16(language("und")), u16(0))
|
||
hdlr = full_box(b"hdlr", 0, 0, u32(0), b"vide", bytes(12), b"video\x00")
|
||
vmhd = full_box(b"vmhd", 0, 1, u16(0), bytes(6))
|
||
dinf = box(b"dinf", full_box(b"dref", 0, 0, u32(1), full_box(b"url ", 0, 1)))
|
||
av01 = box(b"av01", bytes(6), u16(1), u16(0), u16(0), bytes(12), u16(w), u16(h), u32(0x00480000),
|
||
u32(0x00480000), u32(0), u16(1), bytes(32), u16(24), u16(0xFFFF), box(b"av1C", config))
|
||
stbl = box(b"stbl", full_box(b"stsd", 0, 0, u32(1), av01), full_box(b"stts", 0, 0, u32(0)),
|
||
full_box(b"stsc", 0, 0, u32(0)), full_box(b"stsz", 0, 0, u32(0), u32(0)),
|
||
full_box(b"stco", 0, 0, u32(0)))
|
||
trak = box(b"trak", tkhd, box(b"mdia", mdhd, hdlr, box(b"minf", vmhd, dinf, stbl)))
|
||
mvex = box(b"mvex", full_box(b"trex", 0, 0, u32(1), u32(1), u32(0), u32(0), u32(0)))
|
||
return ftyp + box(b"moov", mvhd, trak, mvex)
|
||
|
||
|
||
def fragment(piece, start, tb_den, samples):
|
||
"""§7.6's fragment: moof (mfhd, traf: tfhd, tfdt, trun with the offset of the mdat
|
||
payload), then mdat. samples: (bytes, at a shown key frame)."""
|
||
def moof(offset):
|
||
trun = full_box(b"trun", 0, 0x000701, u32(len(samples)), u32(offset),
|
||
*(u32(tb_den) + u32(len(s)) + u32(0x02000000 if key else 0x01010000) for s, key in samples))
|
||
traf = box(b"traf", full_box(b"tfhd", 0, 0x020000, u32(1)), full_box(b"tfdt", 1, 0, be(start * tb_den, 8)),
|
||
trun)
|
||
return box(b"moof", full_box(b"mfhd", 0, 0, u32(piece + 1)), traf)
|
||
m = moof(0)
|
||
return moof(len(m) + 8) + box(b"mdat", *(s for s, _ in samples))
|
||
|
||
|
||
def without_delimiters(obu_list):
|
||
return b"".join(whole for t, whole, _ in obu_list if t != OBU_TD)
|
||
|
||
|
||
# ---------------------------------------------------------------- classes and catalogs (§8)
|
||
|
||
|
||
def ms_text(units, num, den):
|
||
"""§8.3: floor(d · den · 1000 / num) milliseconds as S.mmm."""
|
||
ms = units * den * 1000 // num
|
||
return f"{ms // 1000}.{ms % 1000:03d}"
|
||
|
||
|
||
def bps(nbytes, units, num, den):
|
||
return -(-8 * nbytes * num // (units * den))
|
||
|
||
|
||
def listed_bytes(ix, k, top):
|
||
"""The bytes of piece k's bases and refinements up to layer `top`."""
|
||
pc = ix["pieces"][k]
|
||
return sum(e["byte_len"] for e in ix["entries"][pc["first_entry"]:pc["first_entry"] + pc["n_entries"]]
|
||
if e["role"] in (BASE, REFINEMENT) and e["layer"] <= top)
|
||
|
||
|
||
def piece_then(P, ix, k):
|
||
"""§8.1: piece k's matching rules and its moments as bits, and the rules' `then`
|
||
folded in document order (a later member overrides an earlier one)."""
|
||
num, den = ix["tb"]
|
||
pc = ix["pieces"][k]
|
||
s_ms, e_ms = pc["start"] * den * 1000 // num, (pc["start"] + pc["duration"]) * den * 1000 // num
|
||
moments = [i for i, m in enumerate(P["moment"]) if s_ms < m["to_ms"] and e_ms > m["from_ms"]]
|
||
tags = {t for i in moments for t in P["moment"][i]["tags"]}
|
||
rules, then = [], {}
|
||
for i, r in enumerate(P["rule"]):
|
||
w = r["when"]
|
||
if "piece_kind" in w and PIECE_KINDS[pc["kind"]] not in w["piece_kind"]:
|
||
continue
|
||
if "moment_tag" in w and not tags & set(w["moment_tag"]):
|
||
continue
|
||
rules.append(i)
|
||
then.update(r["then"])
|
||
return sum(1 << i for i in rules), sum(1 << i for i in moments), then
|
||
|
||
|
||
def piece_ladder(P, cid, then):
|
||
"""§5.7: the rungs a piece of component cid is coded in, (base, [refinements in layer
|
||
order], [floors]): the rules' `then.ladder` where it names rungs of cid, else the
|
||
component's first base rung in ladder order, the refinements over it and its floors."""
|
||
mine = [r for r in P["ladder"] if r["component"] == cid]
|
||
ids = then.get("ladder")
|
||
if ids is not None and all(any(r["id"] == i for r in mine) for i in ids):
|
||
mine = [r for r in mine if r["id"] in ids]
|
||
base = next(r for r in mine if r["role"] == "base")
|
||
chain, cur = [], base
|
||
while True:
|
||
nxt = next((r for r in mine if r["role"] == "refinement" and r.get("refines") == cur["id"]), None)
|
||
if nxt is None:
|
||
break
|
||
chain.append(nxt)
|
||
cur = nxt
|
||
return base, chain, [r for r in mine if r["role"] == "floor"]
|
||
|
||
|
||
def default_base_cap(P, cid):
|
||
"""§5.7: the capability of a component's first base rung, which its HTTP views carry."""
|
||
return next((r["capability"] for r in P["ladder"] if r["component"] == cid and r["role"] == "base"), "")
|
||
|
||
|
||
def class_plan(P, ix):
|
||
"""§8.1: each piece's moments and matching rules as bits, and per class the highest
|
||
layer it lists for each piece, 0xFF for the floor only."""
|
||
num, den = ix["tb"]
|
||
bits, rows = [], [[] for _ in P["class"]]
|
||
for k, pc in enumerate(ix["pieces"]):
|
||
rb, mb, then = piece_then(P, ix, k)
|
||
bits.append((rb, mb))
|
||
for row, cl in zip(rows, P["class"]):
|
||
if cl["floor_only"]:
|
||
row.append(0xFF)
|
||
continue
|
||
top = min(cl["max_layer"], then.get("max_layer", 255), highest_layer(ix, k))
|
||
if "max_kbps" in cl:
|
||
while top >= 0 and -(-8 * listed_bytes(ix, k, top) * num // (1000 * pc["duration"] * den)) \
|
||
> cl["max_kbps"]:
|
||
top -= 1
|
||
row.append(0xFF if top < 0 else top)
|
||
return bits, rows
|
||
|
||
|
||
def listed(ix, k, top):
|
||
"""The bases and refinements piece k lists up to layer `top`, in entry order."""
|
||
pc = ix["pieces"][k]
|
||
if top == 0xFF:
|
||
return []
|
||
return [e for e in ix["entries"][pc["first_entry"]:pc["first_entry"] + pc["n_entries"]]
|
||
if e["role"] in (BASE, REFINEMENT) and e["layer"] <= top]
|
||
|
||
|
||
def codecs_value(objects):
|
||
"""http-delivery §4.2 over a sequence of WAI1 objects (entries, in order; an object
|
||
listed twice is one object): each object that is not a layer is an element of one alt,
|
||
its capability; a layer's capability follows `+` in the alt of the object its base is.
|
||
The union: every alt by first appearance, with every capability joined to it in any
|
||
object; the floor: the alts every such object has, each with the capabilities joined
|
||
to it in every one."""
|
||
seen, seq = set(), []
|
||
for e in objects:
|
||
if e["hash"] not in seen:
|
||
seen.add(e["hash"])
|
||
seq.append(e)
|
||
els = [[e["cap"], []] if e["role"] != REFINEMENT else None for e in seq]
|
||
for e in seq:
|
||
if e["role"] == REFINEMENT:
|
||
k = next(k for k, b in enumerate(seq) if b["role"] != REFINEMENT and b["hash"] == e["base_hash"])
|
||
if e["cap"] not in els[k][1]:
|
||
els[k][1].append(e["cap"])
|
||
els = [x for x in els if x is not None]
|
||
union = []
|
||
for cap, plus in els:
|
||
u = next((a for a in union if a[0] == cap), None)
|
||
if u is None:
|
||
union.append([cap, list(plus)])
|
||
else:
|
||
u[1] += [p for p in plus if p not in u[1]]
|
||
floor = [[cap, [p for p in plus if all(p in x[1] for x in els if x[0] == cap)]] for cap, plus in union
|
||
if all(x[0] == cap for x in els)]
|
||
|
||
def alt(cap, plus):
|
||
return "+".join(x[len("wai."):] for x in [cap] + plus)
|
||
return "wai1." + "|".join(alt(*a) for a in union), "wai1." + "|".join(alt(*a) for a in floor)
|
||
|
||
|
||
def render_catalogs(P, ix, c, floor, size, policy_hex):
|
||
"""§8.3–§8.8 for one component c: every catalog file as the index, the objects and the
|
||
floor give it. floor: (codecs, width, height, [(fragment name, bytes)]); size: (W, H)."""
|
||
num, den = ix["tb"]
|
||
pieces, plan = ix["pieces"], ix["plan"]
|
||
F, FW, FH, frags = floor
|
||
W, H = size
|
||
first_piece, first_start = ix["first_piece"], pieces[0]["start"]
|
||
total = sum(pc["duration"] for pc in pieces)
|
||
tgt = max(-(-pc["duration"] * den // num) for pc in pieces)
|
||
out = {}
|
||
|
||
def text(lines):
|
||
return "".join(x + "\n" for x in lines).encode()
|
||
|
||
def rates(per):
|
||
"""(BANDWIDTH, AVERAGE-BANDWIDTH) over (bytes, units) pairs."""
|
||
return (max(bps(n, d, num, den) for n, d in per),
|
||
bps(sum(n for n, _ in per), sum(d for _, d in per), num, den))
|
||
fb, fa = rates([(len(fr), pc["duration"]) for (_, fr), pc in zip(frags, pieces)])
|
||
head = ["#EXTM3U", "#EXT-X-VERSION:7", "#EXT-X-INDEPENDENT-SEGMENTS"]
|
||
inf = f'#EXT-X-STREAM-INF:BANDWIDTH={fb},AVERAGE-BANDWIDTH={fa},CODECS="{F}",RESOLUTION={FW}x{FH}'
|
||
# The floor variant's URI resolves from hls/classic/ and from every class's directory
|
||
# alike, so a class's master is the classical one with at most one line added.
|
||
out["hls/classic/master.m3u8"] = text(head + [inf, f"../classic/floor-{c}.m3u8"])
|
||
media = ["#EXTM3U", "#EXT-X-VERSION:7", f"#EXT-X-TARGETDURATION:{tgt}", "#EXT-X-MEDIA-SEQUENCE:0",
|
||
"#EXT-X-PLAYLIST-TYPE:VOD", "#EXT-X-INDEPENDENT-SEGMENTS"]
|
||
lines = media + [f'#EXT-X-MAP:URI="../../floor/{c}/init.mp4"']
|
||
for (name, _), pc in zip(frags, pieces):
|
||
lines += [f"#EXTINF:{ms_text(pc['duration'], num, den)},", f"../../floor/{c}/{name}"]
|
||
out[f"hls/classic/floor-{c}.m3u8"] = text(lines + ["#EXT-X-ENDLIST"])
|
||
mpd_head = ['<?xml version="1.0" encoding="UTF-8"?>',
|
||
'<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" profiles="urn:mpeg:dash:profile:full:2011" '
|
||
f'type="static" mediaPresentationDuration="PT{ms_text(total, num, den)}S" minBufferTime="PT{tgt}S">',
|
||
' <Period id="p0" start="PT0S">']
|
||
mpd_tail = [" </AdaptationSet>", " </Period>", "</MPD>"]
|
||
|
||
def timeline(ks):
|
||
return [" <SegmentTimeline>"] + \
|
||
[f' <S t="{(pieces[k]["start"] - first_start) * den}" d="{pieces[k]["duration"] * den}"/>'
|
||
for k in ks] + [" </SegmentTimeline>"]
|
||
lines = mpd_head + [
|
||
f' <AdaptationSet id="1" contentType="video" mimeType="video/mp4" codecs="{F}" width="{FW}" '
|
||
f'height="{FH}" segmentAlignment="true" startWithSAP="1">',
|
||
f' <Representation id="{c}-floor" bandwidth="{fb}">', f' <SegmentList timescale="{num}">',
|
||
f' <Initialization sourceURL="../floor/{c}/init.mp4"/>'] + timeline(range(len(pieces)))
|
||
lines += [f' <SegmentURL media="../floor/{c}/{name}"/>' for name, _ in frags]
|
||
out["dash/classic.mpd"] = text(lines + [" </SegmentList>", " </Representation>"] + mpd_tail)
|
||
E = ix["entries"]
|
||
# §8.3: an HTTP view lists a piece only where its base is of the component's default
|
||
# base capability (http-delivery §4.2: no variant mixes bases with nothing in common).
|
||
base_cap = default_base_cap(P, c)
|
||
bases = [next(e for e in E[pc["first_entry"]:pc["first_entry"] + pc["n_entries"]] if e["role"] == BASE) for pc in pieces]
|
||
for ci, cl in enumerate(P["class"]):
|
||
name, row = cl["id"], plan[ci]
|
||
lists = [listed(ix, k, row[k]) for k in range(len(pieces))]
|
||
http = [lists[k] if bases[k]["cap"] == base_cap else [] for k in range(len(pieces))]
|
||
wai = any(http)
|
||
lines = head[:]
|
||
if wai:
|
||
lines.append('#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.multivariant",VALUE="wai.m3u8"')
|
||
out[f"hls/{name}/master.m3u8"] = text(lines + [inf, f"../classic/floor-{c}.m3u8"])
|
||
views = []
|
||
for k, pc in enumerate(pieces):
|
||
v = {"dur": pc["duration"], "p": first_piece + k, "start": pc["start"], "stages": []}
|
||
if row[k] == 0xFF:
|
||
v["floor_only"] = True
|
||
for e in lists[k]:
|
||
s = {"bytes": e["byte_len"], "h": e["hash"].hex(), "layer": e["layer"]}
|
||
if e["role"] == REFINEMENT:
|
||
s["refines"] = E[e["refines"]]["hash"].hex()
|
||
s["deadline_ms"] = e["deadline"]
|
||
v["stages"].append(s)
|
||
views.append(v)
|
||
comp = {"pieces": views}
|
||
if wai:
|
||
comp["media_playlist"] = f"../hls/{name}/wai-{c}.m3u8"
|
||
view = {"class": name, "components": {c: comp}, "policy_digest": policy_hex}
|
||
out[f"stages/{name}.json"] = (jcs(view) + "\n").encode()
|
||
if not wai:
|
||
continue
|
||
objs = []
|
||
for k in range(len(pieces)):
|
||
for e in http[k]:
|
||
objs.append(dict(e, base_hash=bases[k]["hash"]))
|
||
union, fl = codecs_value(objs)
|
||
wb, wa = rates([(sum(e["byte_len"] for e in http[k]), pieces[k]["duration"])
|
||
for k in range(len(pieces)) if http[k]])
|
||
out[f"hls/{name}/wai.m3u8"] = text(head + [
|
||
f'#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.class",VALUE="{name}"',
|
||
f'#EXT-X-SESSION-DATA:DATA-ID="science.transaction.wai.stages",URI="../../stages/{name}.json"',
|
||
f'#EXT-X-STREAM-INF:BANDWIDTH={wb},AVERAGE-BANDWIDTH={wa},CODECS="{union}",X-WAI-FLOOR="{fl}",'
|
||
f'RESOLUTION={W}x{H}', f"wai-{c}.m3u8"])
|
||
lines = media[:]
|
||
for k, pc in enumerate(pieces):
|
||
p0 = pc["first_entry"]
|
||
base = next(e for e in E[p0:p0 + pc["n_entries"]] if e["role"] == BASE)
|
||
if not http[k]:
|
||
lines.append("#EXT-X-GAP")
|
||
# A layer line follows its segment's EXTINF, before the segment's URI
|
||
# (http-delivery §10.4).
|
||
lines.append(f"#EXTINF:{ms_text(pc['duration'], num, den)},")
|
||
lines += [f'#WAI-LAYER:N={e["layer"]},URI="../../objects/{e["hash"].hex()}.wai#n={e["byte_len"]}"'
|
||
for e in http[k] if e["role"] == REFINEMENT]
|
||
lines.append(f"../../objects/{base['hash'].hex()}.wai#n={base['byte_len']}")
|
||
out[f"hls/{name}/wai-{c}.m3u8"] = text(lines + ["#EXT-X-ENDLIST"])
|
||
lines = mpd_head + [
|
||
f' <AdaptationSet id="1" contentType="video" mimeType="application/wai" codecs="{union}" '
|
||
f'width="{W}" height="{H}">',
|
||
' <EssentialProperty schemeIdUri="tag:transaction.science,2026:wai/http-delivery"/>',
|
||
f' <SupplementalProperty schemeIdUri="tag:transaction.science,2026:wai/floor" value="{fl}"/>',
|
||
f' <SupplementalProperty schemeIdUri="tag:transaction.science,2026:wai/class" value="{name}"/>',
|
||
' <SupplementalProperty schemeIdUri="tag:transaction.science,2026:wai/stages" '
|
||
f'value="../stages/{name}.json"/>']
|
||
for layer in range(max(row[k] for k in range(len(pieces)) if http[k]) + 1):
|
||
ks = [k for k in range(len(pieces)) if any(e["layer"] == layer for e in http[k])]
|
||
mine = [[e for e in http[k] if e["layer"] == layer] for k in ks]
|
||
peak = max((bps(sum(e["byte_len"] for e in m), pieces[k]["duration"], num, den)
|
||
for k, m in zip(ks, mine)), default=0)
|
||
dep = f' dependencyId="{c}-l{layer - 1}"' if layer else ""
|
||
lines += [f' <Representation id="{c}-l{layer}"{dep} bandwidth="{peak}">',
|
||
f' <SegmentList timescale="{num}">'] + timeline(ks)
|
||
lines += [f' <SegmentURL media="../objects/{e["hash"].hex()}.wai"/>' for m in mine for e in m]
|
||
lines += [" </SegmentList>", " </Representation>"]
|
||
out[f"dash/{name}.mpd"] = text(lines + mpd_tail)
|
||
has_floor = any(e["role"] == FLOOR for e in E)
|
||
out["moq/catalog.json"] = render_moq(c, E, P["live"]["layer_lag_ms"], P["defaults"]["deadline_ms"],
|
||
(F, FW, FH) if has_floor else None, P["asset_id"])
|
||
return out
|
||
|
||
|
||
def receipt_alias(ns, name):
|
||
"""§8.4: a track's wai_receipt_alias, the first eight bytes of
|
||
BLAKE3("wai:pack-receipt-alias\x01" ‖ namespace ‖ 0x00 ‖ name), little-endian, below 2^53."""
|
||
return int.from_bytes(blake3(b"wai:pack-receipt-alias\x01" + ns.encode() + b"\x00" + name.encode())[:8], "little") & MAX_INTEGER
|
||
|
||
|
||
def render_moq(c, E, lag, default_deadline, floor, ns):
|
||
"""§8.4: moq/catalog.json, the component's bases and refinements as a stage set of
|
||
moq-streaming-format §4.1. One track per (layer, deadline), by layer, then deadline
|
||
ascending, of namespace `ns`; each depends on every track carrying an entry below one
|
||
of its own down its chain; each with wai_staged, receipts declared with its alias and a
|
||
receipts track of its own; then the floor (cmaf, altGroup 1 with the base track), then
|
||
the receipts tracks. floor: (codecs, width, height) or None. The catalog must pass
|
||
§4.1's check (staged_verify.py's)."""
|
||
keys = sorted({(e["layer"], e["deadline"]) for e in E if e["role"] in (BASE, REFINEMENT)})
|
||
|
||
def name(key):
|
||
layer, deadline = key
|
||
return f"{c}/l{layer}" + (f"@{deadline}" if layer and deadline != default_deadline else "")
|
||
tracks = []
|
||
for ti, (layer, deadline) in enumerate(keys):
|
||
es = [e for e in E if e["role"] in (BASE, REFINEMENT) and (e["layer"], e["deadline"]) == (layer, deadline)]
|
||
caps = []
|
||
for e in es:
|
||
if e["cap"] not in caps:
|
||
caps.append(e["cap"])
|
||
deps = set()
|
||
for e in es:
|
||
cur = e
|
||
while cur["role"] == REFINEMENT:
|
||
cur = E[cur["refines"]]
|
||
dt = keys.index((cur["layer"], cur["deadline"]))
|
||
if dt >= ti:
|
||
raise ValueError("a refinement refines an entry on no earlier track")
|
||
deps.add(dt)
|
||
nm = name((layer, deadline))
|
||
t = {"namespace": ns, "name": nm, "packaging": "wai", "isLive": False, "role": "video", "renderGroup": 1,
|
||
"wai_capabilities": caps, "wai_receipts": True, "wai_receipt_alias": receipt_alias(ns, nm),
|
||
"wai_receipts_track": f"{nm}/receipts", "wai_staged": {"set": c, "layer": layer, "lead_ms": None}}
|
||
if deps:
|
||
t["depends"] = [name(keys[x]) for x in sorted(deps)]
|
||
weakest = min(e["tier"] for e in es)
|
||
if weakest:
|
||
t["wai_determinism"] = TIER_NAMES[weakest]
|
||
if layer:
|
||
t["wai_delivery_timeout_ms"] = max(0, deadline - (lag[layer] if layer < len(lag) else 0))
|
||
elif floor is not None:
|
||
t["altGroup"] = 1
|
||
tracks.append(t)
|
||
if floor is not None:
|
||
tracks.append({"altGroup": 1, "codec": floor[0], "height": floor[2], "name": f"{c}/floor", "namespace": ns,
|
||
"packaging": "cmaf", "renderGroup": 1, "width": floor[1]})
|
||
tracks += [{"namespace": ns, "name": f"{name(k)}/receipts", "packaging": "wai-receipts", "isLive": False} for k in keys]
|
||
if len({receipt_alias(ns, name(k)) for k in keys}) != len(keys):
|
||
raise ValueError("two tracks' receipt aliases are equal")
|
||
cat = {"tracks": tracks, "version": 1}
|
||
bad = [s for s in SV.check_catalog(cat, ns) if s["reason"] is not None]
|
||
if bad:
|
||
raise ValueError(f"the stage set fails moq-streaming-format §4.1: {bad}")
|
||
return (jcs(cat) + "\n").encode()
|
||
|
||
|
||
# ---------------------------------------------------------------- the corpus
|
||
|
||
|
||
class Report:
|
||
def __init__(self):
|
||
self.failures = 0
|
||
self.checked = 0
|
||
|
||
def check(self, ok, what):
|
||
self.checked += 1
|
||
if not ok:
|
||
self.failures += 1
|
||
print(f"FAIL {what}")
|
||
|
||
|
||
def load(path):
|
||
with open(path, "rb") as f:
|
||
return f.read()
|
||
|
||
|
||
def tree(root):
|
||
"""Every file below root, as paths, in byte order, read strictly (§11.3 step 1): every
|
||
name, one beginning with `.` and a `*.sha256` file included; None when an entry is a
|
||
symbolic link or neither a file nor a directory, when a directory cannot be read, or
|
||
when a directory below root holds nothing (§11.1: no other file or directory)."""
|
||
def unreadable(e):
|
||
raise e
|
||
out = []
|
||
try:
|
||
for at, dirs, files in os.walk(root, onerror=unreadable):
|
||
if at != root and not dirs and not files:
|
||
return None
|
||
for n in dirs + files:
|
||
full = os.path.join(at, n)
|
||
if os.path.islink(full) or not (os.path.isdir(full) or os.path.isfile(full)):
|
||
return None
|
||
out += [os.path.relpath(os.path.join(at, f), root).replace(os.sep, "/") for f in files]
|
||
except OSError:
|
||
return None
|
||
return sorted(out, key=lambda p: p.encode())
|
||
|
||
|
||
SEGMENT_FIRST = set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-")
|
||
|
||
|
||
def segment_ok(s):
|
||
"""SPEC §3.1: a path segment."""
|
||
return s != "" and s[0] in SEGMENT_FIRST and all(c in SEGMENT_FIRST | {"."} for c in s) and not s.endswith(".")
|
||
|
||
|
||
def listing(root, paths):
|
||
"""SPEC §3.1: a line per file, its SHA-256, two spaces, its path and a line feed."""
|
||
return "".join(hashlib.sha256(load(os.path.join(root, p))).hexdigest() + " " + p + "\n" for p in paths).encode()
|
||
|
||
|
||
def seg_params(P, tb):
|
||
s = P["segmentation"]
|
||
return {"min": units_from_ms(s["min_ms"], *tb), "target": units_from_ms(s["target_ms"], *tb),
|
||
"max": units_from_ms(s["max_ms"], *tb), "cut_abs": s["cut_abs_permille"], "cut_rel": s["cut_rel_permille"],
|
||
"window": s["cut_window"], "lookahead": s["lookahead_frames"]}
|
||
|
||
|
||
class Held:
|
||
"""A still's one frame held for `n` frames (§5.7): its length is `n` and it iterates
|
||
the frame `n` times, holding neither `n` copies nor `n` references."""
|
||
def __init__(self, frame, n):
|
||
self.frame, self.n = frame, n
|
||
|
||
def __len__(self):
|
||
return self.n
|
||
|
||
def __iter__(self):
|
||
return itertools.repeat(self.frame, self.n)
|
||
|
||
|
||
def prefix_digest_of(frames):
|
||
"""SPEC §7's clip digest, each frame's SHA-256 fed in turn: a still's frame, held, is
|
||
hashed once and its digest fed for every frame, a block of them at a time (§5.7)."""
|
||
h = hashlib.sha256()
|
||
if isinstance(frames, Held):
|
||
d = hashlib.sha256(frames.frame).digest()
|
||
block, left = d * 1024, frames.n
|
||
while left >= 1024:
|
||
h.update(block)
|
||
left -= 1024
|
||
h.update(d * left)
|
||
return h.digest()
|
||
for f in frames:
|
||
h.update(hashlib.sha256(f).digest())
|
||
return h.digest()
|
||
|
||
|
||
def clip_digest(frames):
|
||
"""SPEC §7, *Digests*: the SHA-256 of the frames' SHA-256 digests concatenated."""
|
||
return hashlib.sha256(b"".join(hashlib.sha256(f).digest() for f in frames)).digest()
|
||
|
||
|
||
def verify_pack(rep, d, name, pkg=None, inputs=True):
|
||
"""packs/<name>/: package/ (or `pkg`) verified in §11.3's order, without the source;
|
||
then, with `inputs`, the package's inputs: policy.toml as given, and what only the
|
||
source decides (the plan, each base's sse and step) and the supplied floor."""
|
||
root = os.path.join(d, "packs", name)
|
||
pkg = pkg or os.path.join(root, "package")
|
||
tag = f"packs/{name}"
|
||
files = tree(pkg)
|
||
if files is None:
|
||
rep.check(False, f"{tag}: a symbolic link or an entry that is neither file nor directory (unexpected-file)")
|
||
return
|
||
paths_ok = all(all(segment_ok(s) for s in p.split("/")) for p in files) and len({p.lower() for p in files}) == len(files)
|
||
rep.check(paths_ok, f"{tag}: every file, read strictly, has a listing path (SPEC §3.1) (unexpected-file)")
|
||
if not paths_ok:
|
||
return
|
||
if "LISTING" not in files:
|
||
rep.check(False, f"{tag}: no LISTING (listing-missing)")
|
||
return
|
||
others = [p for p in files if p != "LISTING"]
|
||
ok = load(os.path.join(pkg, "LISTING")) == listing(pkg, others)
|
||
rep.check(ok, f"{tag}: LISTING is the listing of the other files (listing-mismatch)")
|
||
if not ok:
|
||
return
|
||
def get(p):
|
||
return load(os.path.join(pkg, p)) if p in files else b""
|
||
raw = get("package.receipt.json")
|
||
try:
|
||
r = read_receipt(raw)
|
||
if r["version"] != 2:
|
||
raise Refused("receipt-version")
|
||
except Refused as e:
|
||
rep.check(False, f"{tag}: package.receipt.json does not read as version 2 ({e.code})")
|
||
return
|
||
j = r["json"]
|
||
ok = raw == jcs(j).encode()
|
||
rep.check(ok, f"{tag}: package.receipt.json is RFC 8785 canonical JSON, its receipt's own form (receipt-json)")
|
||
if not ok:
|
||
return
|
||
|
||
# The policy: as given, its effective canonical JSON, its digest (§3.2, §11.1).
|
||
toml_b, pj = get("policy.toml"), get("policy.json")
|
||
if inputs:
|
||
rep.check(toml_b == load(os.path.join(root, "policy.toml")), f"{tag}: policy.toml is the policy as given")
|
||
rep.check(blake3(b"wai:pack-policy\x01" + pj).hex() == j["policy_digest"],
|
||
f"{tag}: policy.json hashes to the receipt's policy digest (policy-digest-mismatch)")
|
||
# policy.toml parsed (§3.2–§3.4); the override applied, policy.json's
|
||
# royalty_free_only, which only ever forces it on (§3.2); then §3.6 under that
|
||
# effective policy, below.
|
||
try:
|
||
P = parse_policy_bytes(toml_b)
|
||
except Refused as e:
|
||
rep.check(False, f"{tag}: policy.toml refused ({e.code})")
|
||
return
|
||
given = read_json(pj)
|
||
if isinstance(given, dict) and given.get("royalty_free_only") is True:
|
||
P["royalty_free_only"] = True
|
||
ok = jcs(P).encode() == pj
|
||
rep.check(ok, f"{tag}: policy.json is policy.toml's effective canonical JSON (policy-json-mismatch)")
|
||
if not ok:
|
||
return
|
||
ids = [c["id"] for c in P["component"]] + [c["id"] for c in P["class"]]
|
||
cls = [c["id"].lower() for c in P["class"]]
|
||
ok = all(segment_ok(i) for i in ids) and len(set(cls)) == len(cls) and "classic" not in cls
|
||
rep.check(ok, f"{tag}: component and class ids are path segments, class ids distinct in any case, none `classic` (pack-id)")
|
||
if not ok:
|
||
return
|
||
# §3.6 under the effective policy: royalty_free_only as policy.json states it.
|
||
refused = next((code for code in (rung_refusal(x["capability"], x["role"], P["royalty_free_only"]) for x in P["ladder"]) if code), None)
|
||
rep.check(refused is None, f"{tag}: the effective policy asks for nothing a packager may not emit ({refused})")
|
||
if refused:
|
||
return
|
||
# The receipt's components are the policy's, by id, order and media (§11.3).
|
||
ok = [(c["id"], c["media_class"]) for c in j["components"]] == [(x["id"], x["media"]) for x in P["component"]]
|
||
rep.check(ok, f"{tag}: the receipt's components are the policy's, by id, order and media (policy-component-mismatch)")
|
||
if not ok:
|
||
return
|
||
|
||
# The indexes and the receipt's own refusals (§9.1).
|
||
index_files = []
|
||
for c in j["components"]:
|
||
present = c["index"] in files
|
||
rep.check(present, f"{tag}: {c['index']} present (index-missing)")
|
||
if not present:
|
||
return
|
||
index_files.append(load(os.path.join(pkg, c["index"])))
|
||
try:
|
||
ixs = verify_receipt_v2(r, index_files)
|
||
rep.check(True, f"{tag}: the receipt verifies against its indexes (§9.1)")
|
||
except Refused as e:
|
||
rep.check(False, f"{tag}: the receipt against its indexes ({e.code})")
|
||
return
|
||
seed = bytes.fromhex(load(os.path.join(root, "key.hex")).decode().strip())
|
||
pk, sig = ed25519_from_seed(seed, r["payload"])
|
||
rep.check(pk.hex() == j["signer_pubkey"], f"{tag}: the receipt's signer is the package key")
|
||
rep.check(sig.hex() == j["sig"], f"{tag}: the signature is the package key's over the payload (§11.2)")
|
||
|
||
named = {"LISTING", "policy.toml", "policy.json", "package.receipt.json"} | {c["index"] for c in j["components"]}
|
||
for c, ix in zip(j["components"], ixs):
|
||
try:
|
||
named |= verify_component(rep, tag, root, pkg, files, P, j, c, ix, inputs)
|
||
except (Refused, Malformed, ValueError, OverflowError, UnicodeError, KeyError, IndexError) as x:
|
||
# A package this verifier cannot read further is refused, never a crash.
|
||
rep.check(False, f"{tag} {c['id']}: refused ({type(x).__name__}: {x})")
|
||
return
|
||
|
||
# §9.4: a metered package publishes its measurement claim; an unmetered one, none.
|
||
claim = f"claims/{j['receipt_hash']}.measurement.json"
|
||
if j["joules_micro"]:
|
||
rep.check(claim in files, f"{tag}: a metered package's measurement claim (measurement-claim-missing)")
|
||
rep.check(False, f"{tag}: this verifier does not check a measurement claim (measurement-claim-invalid)")
|
||
named.add(claim)
|
||
else:
|
||
rep.check(not any(p.startswith("claims/") for p in others),
|
||
f"{tag}: an unmetered package publishes no measurement claim (§9.4)")
|
||
unlisted = [p for p in others if p.startswith("objects/") and p not in named]
|
||
rep.check(not unlisted, f"{tag}: object files no entry names: {unlisted} (object-unlisted)")
|
||
catalogs = ("hls/", "dash/", "moq/", "stages/")
|
||
extra = [p for p in others if p.startswith(catalogs) and p not in named]
|
||
rep.check(not extra, f"{tag}: catalogs past the ones the index gives: {extra} (catalog-unexpected)")
|
||
other = [p for p in others if p not in named and not p.startswith(("objects/",) + catalogs)]
|
||
rep.check(not other, f"{tag}: other files: {other} (unexpected-file)")
|
||
|
||
|
||
class Capture(Report):
|
||
"""A report that keeps what fails, in order, and prints nothing."""
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.failed = []
|
||
|
||
def check(self, ok, what):
|
||
self.checked += 1
|
||
if not ok:
|
||
self.failures += 1
|
||
self.failed.append(what)
|
||
|
||
|
||
REASON_AT_END = re.compile(r"\(([a-z0-9][a-z0-9.:-]*)\)$")
|
||
|
||
|
||
def package_verdict(d, name, pkg):
|
||
"""The reason §11.3 gives the package in `pkg` (the first check that fails, by the
|
||
reason it names), or `ok`; the inputs of packs/<name>/ are not consulted."""
|
||
cap = Capture()
|
||
verify_pack(cap, d, name, pkg=pkg, inputs=False)
|
||
if not cap.failed:
|
||
return "ok"
|
||
m = REASON_AT_END.search(cap.failed[0])
|
||
return m.group(1) if m else f"no reason named: {cap.failed[0]}"
|
||
|
||
|
||
def verify_package_mutations(rep, d, sub="package-mutations"):
|
||
"""package-mutations/: each case the golden less the files it removes (and the
|
||
directories that leaves with nothing in them), with its overlay and the directories
|
||
it adds that hold nothing (`empty_dirs`) or cannot be read (`unreadable_dirs`, mode 0
|
||
where the platform has modes), written out and verified as a package (§11.3); refused
|
||
with the corpus's reason, or accepted where it says `ok`."""
|
||
m = os.path.join(d, sub)
|
||
held = tree(m)
|
||
if held is None:
|
||
rep.check(False, f"{sub}/: a symbolic link or an entry that is neither file nor directory")
|
||
return
|
||
pm = json.loads(load(os.path.join(m, "expected.json")))
|
||
golden = os.path.join(d, "packs", pm["pack"], "package")
|
||
used = {"expected.json"}
|
||
with tempfile.TemporaryDirectory() as tmp:
|
||
for c in pm["cases"]:
|
||
pkg = os.path.join(tmp, c["name"])
|
||
shutil.copytree(golden, pkg)
|
||
for p in c["remove"]:
|
||
os.remove(os.path.join(pkg, p))
|
||
for at, dirs, files in os.walk(pkg, topdown=False):
|
||
if at != pkg and not os.listdir(at):
|
||
os.rmdir(at)
|
||
for p in held:
|
||
if p.startswith(c["name"] + "/"):
|
||
used.add(p)
|
||
dst = os.path.join(pkg, p[len(c["name"]) + 1:])
|
||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
||
shutil.copyfile(os.path.join(m, p), dst)
|
||
for p in c.get("empty_dirs", []) + c.get("unreadable_dirs", []):
|
||
os.makedirs(os.path.join(pkg, p))
|
||
for p in c.get("unreadable_dirs", []):
|
||
os.chmod(os.path.join(pkg, p), 0)
|
||
got = package_verdict(d, pm["pack"], pkg)
|
||
for p in c.get("unreadable_dirs", []):
|
||
os.chmod(os.path.join(pkg, p), 0o755)
|
||
rep.check(got == c["reason"], f"{sub} {c['name']}: {got}, the corpus says {c['reason']}")
|
||
rest = [p for p in held if p not in used]
|
||
rep.check(not rest, f"{sub}/: files no case uses: {rest}")
|
||
|
||
|
||
def unreached(files, c):
|
||
"""The floor and catalog files of a component whose verification stopped at an earlier
|
||
refusal: named, so that refusal is not reported again as unexpected files."""
|
||
return {p for p in files if p.startswith((f"floor/{c}/", "hls/", "dash/", "moq/", "stages/"))}
|
||
|
||
|
||
def index_against_policy(P, comp, c, ix):
|
||
"""§11.3 step 6, the index against its component's policy, in its order: its timebase
|
||
and media; its string table (the component's id, the class ids, each capability at its
|
||
first use); its class records (the policy's classes); each entry's role, capability
|
||
and layer a rung's of the component; each piece's kind one a video piece has, and its
|
||
rules, moments and class plan §8.1's. None when it holds, else what fails."""
|
||
if ix["tb"] != (comp["timebase"]["num"], comp["timebase"]["den"]) or MEDIA_CLASSES[ix["media_class"]] != comp["media"]:
|
||
return "the timebase or media class"
|
||
caps = []
|
||
for e in ix["entries"]:
|
||
if e["cap"] not in caps:
|
||
caps.append(e["cap"])
|
||
if ix["strings"] != [c["id"]] + [x["id"] for x in P["class"]] + caps or ix["strings"][0] != ix["component"]:
|
||
return "the string table"
|
||
want = [(x["id"], min(x["max_layer"], 255), int(x["floor_only"]), min(x.get("max_kbps", 0), U32)) for x in P["class"]]
|
||
if [(x["name"], x["max_layer"], x["flags"], x["max_kbps"]) for x in ix["classes"]] != want:
|
||
return "the class records"
|
||
# Each piece: its kind, its rules and moments, its entries the rungs of its ladder
|
||
# (§5.7) with the plan's deadline on each refinement (§8.6), then the class plan.
|
||
E = ix["entries"]
|
||
# §4.4: no piece lasts longer than max_ms; first, so nothing is decoded for one that does.
|
||
mx = units_from_ms(P["segmentation"]["max_ms"], *ix["tb"])
|
||
long = next((k for k, pc in enumerate(ix["pieces"]) if pc["duration"] > mx), None)
|
||
if long is not None:
|
||
return f"piece {long}: past the policy's max_ms"
|
||
for k, pc in enumerate(ix["pieces"]):
|
||
if pc["kind"] >= len(KINDS):
|
||
return f"piece {k}: a kind a video piece does not have"
|
||
rb, mb, then = piece_then(P, ix, k)
|
||
if (rb, mb) != (pc["rules"], pc["moments"]):
|
||
return f"piece {k}: the rules and moments"
|
||
base, chain, floors = piece_ladder(P, c["id"], then)
|
||
want = [(BASE, base["capability"], 0)] + [(REFINEMENT, r["capability"], j + 1) for j, r in enumerate(chain)] \
|
||
+ [(FLOOR, r["capability"], 0) for r in floors]
|
||
mine = E[pc["first_entry"]:pc["first_entry"] + pc["n_entries"]]
|
||
if [(e["role"], e["cap"], e["layer"]) for e in mine] != want:
|
||
return f"piece {k}: its entries are not the rungs of its ladder"
|
||
deadline = then.get("deadline_ms", P["defaults"]["deadline_ms"])
|
||
if any(e["role"] == REFINEMENT and e["deadline"] != deadline for e in mine):
|
||
return f"piece {k}: a refinement's deadline is not its plan's"
|
||
_, rows = class_plan(P, ix)
|
||
if rows != ix["plan"]:
|
||
return "the class plan"
|
||
return None
|
||
|
||
|
||
def verify_component(rep, tag, root, pkg, files, P, j, c, ix, inputs=True):
|
||
"""§11.3 per component, then (with `inputs`) the source's side. Returns the files it
|
||
names."""
|
||
t = f"{tag} {c['id']}"
|
||
comp = next((x for x in P["component"] if x["id"] == c["id"]), None)
|
||
if comp is None:
|
||
rep.check(False, f"{t}: the receipt's component is not the policy's")
|
||
return set()
|
||
tb = (comp["timebase"]["num"], comp["timebase"]["den"])
|
||
num, den = tb
|
||
pieces, E = ix["pieces"], ix["entries"]
|
||
why = index_against_policy(P, comp, c, ix)
|
||
rep.check(why is None, f"{t}: the index against the policy: {why} (index-policy-mismatch)")
|
||
if why is not None:
|
||
return set(unreached(files, c["id"])) | {f"plan-{c['id']}.txt"} | {f"objects/{e['hash'].hex()}.wai" for e in E}
|
||
starts = [pc["start"] for pc in pieces]
|
||
plan_file = f"plan-{c['id']}.txt"
|
||
rep.check(plan_file in files and load(os.path.join(pkg, plan_file)) == "".join(f"{s}\n" for s in starts).encode(),
|
||
f"{t}: {plan_file} is the index's plan (plan-mismatch)")
|
||
rep.check(ix["plan_digest"].hex() == plan_digest(starts), f"{t}: the index's plan_digest is its starts' (§4.2)")
|
||
if inputs:
|
||
# Of the golden, not a rule a package is held to: a live window is a package too
|
||
# (§6.1, §9.2).
|
||
rep.check(ix["first_piece"] == 0 and ix["first_entry"] == 0 and not any(ix["prev"]),
|
||
f"{t}: the golden is on-demand content, one window (§6.1)")
|
||
named = {plan_file}
|
||
|
||
# Every entry's object (§6.3), the decode-equivalent prefixes (§6.5).
|
||
objs, mans, decoded = {}, {}, {}
|
||
for i, e in enumerate(E):
|
||
path = f"objects/{e['hash'].hex()}.wai"
|
||
named.add(path)
|
||
if path not in files:
|
||
rep.check(False, f"{t}: entry {i}: {path} (object-missing)")
|
||
continue
|
||
objs[i] = load(os.path.join(pkg, path))
|
||
try:
|
||
mans[i] = object_rules(ix, i, objs[i])
|
||
rep.check(True, f"{t}: entry {i}: the object rules (§6.3)")
|
||
except Refused as x:
|
||
rep.check(False, f"{t}: entry {i}: the object rules (§6.3) ({x.code})")
|
||
if len(mans) != len(E):
|
||
return named | unreached(files, c['id'])
|
||
# Before any frame is decoded (§11.3): each piece's floor object, where the policy has a
|
||
# floor rung and the payload reads as §7.2's framing, holds one unit per frame.
|
||
if any(r["component"] == c["id"] and r["role"] == "floor" for r in P["ladder"]):
|
||
own = owners(ix)
|
||
for k, pc in enumerate(pieces):
|
||
fl = [i for i in range(len(E)) if own[i] == k and E[i]["role"] == FLOOR]
|
||
fp = floor_payload(objs[fl[0]][12 + E[fl[0]]["man_len"]:]) if len(fl) == 1 else None
|
||
if fp is not None and len(fp[5]) != pc["duration"]:
|
||
rep.check(False, f"{t}: piece {k}: {len(fp[5])} floor units for {pc['duration']} frames, before any frame "
|
||
f"is decoded (floor-object-mismatch)")
|
||
return named | unreached(files, c['id'])
|
||
size = None
|
||
own = owners(ix)
|
||
stage = {} # entry index → the frames of the prefix it completes, decoded
|
||
for i, e in enumerate(E):
|
||
ml = e["man_len"]
|
||
payload = objs[i][12 + ml:]
|
||
k = own[i]
|
||
n = pieces[k]["duration"]
|
||
if e["role"] == BASE:
|
||
try:
|
||
if e["cap"] == "wai.image.png":
|
||
# §5.7: a still, its one frame held for the piece: the frame and its
|
||
# count, not a list of n frames.
|
||
pw, ph, rgb = png_frame(payload)
|
||
wv, frames = None, Held(rgb, n)
|
||
else:
|
||
wv = IPV.parse_wiv1(payload, 0)
|
||
frames, pw, ph = IPV.decode_wiv1_k0(wv), wv["w"], wv["h"]
|
||
except (IPV.Refused, ValueError, TypeError, IndexError, struct.error, zlib.error) as x:
|
||
rep.check(False, f"{t}: entry {i}: the base does not decode ({x}) (prefix-digest)")
|
||
return named | unreached(files, c['id'])
|
||
decoded.setdefault(e["hash"], (wv, frames))
|
||
stage[i] = frames
|
||
size = size or (pw, ph)
|
||
# §5.2: the reference packager's base manifest, as SPEC §3 orders it.
|
||
want = {"wai": "1.0", "media": comp["media"], "intent": "replicate",
|
||
"model_requirement": {"capability": e["cap"]}, "conditioning": {"kind": e["cap"].split(".")[-1]},
|
||
"target": {"frames": n, "h": ph, "w": pw}}
|
||
rep.check(objs[i][8:8 + ml] == json.dumps(want, separators=(",", ":")).encode(),
|
||
f"{t}: entry {i}: the base's manifest is §5.2's (object-manifest)")
|
||
elif e["role"] == REFINEMENT:
|
||
base = E[pieces[k]["first_entry"]]
|
||
# §5.4: the refinement's manifest, as `wai stage refine` writes it.
|
||
want = {"wai": "1.0", "media": comp["media"], "intent": "replicate",
|
||
"model_requirement": {"capability": e["cap"]}, "conditioning": {"kind": "wir1"},
|
||
"refines": {"base": base["hash"].hex(), "prev": E[e["refines"]]["hash"].hex(), "layer": e["layer"]},
|
||
"target": None}
|
||
rep.check(objs[i][8:8 + ml] == json.dumps(want, separators=(",", ":")).encode(),
|
||
f"{t}: entry {i}: the refinement's manifest is §5.4's (object-manifest)")
|
||
try:
|
||
stage[i] = wir1_apply(payload, stage[e["refines"]])
|
||
except (SV.Refused, KeyError, IndexError, struct.error) as x:
|
||
rep.check(False, f"{t}: entry {i}: the layer does not apply to the stage below it ({x}) (prefix-digest)")
|
||
return named | unreached(files, c['id'])
|
||
for i, e in enumerate(E):
|
||
if e["tier"] == 2:
|
||
rep.check(prefix_digest_of(stage[i]) == e["prefix"],
|
||
f"{t}: entry {i}: the prefix decodes to its prefix_digest (prefix-digest)")
|
||
|
||
# The floor objects (§7.2) and the CMAF files their temporal units give (§7.6).
|
||
rung = next((r for r in P["ladder"] if r["component"] == c["id"] and r["role"] == "floor"), None)
|
||
sup = P["floor"].get("supplied_listing")
|
||
settings = blake3(b"wai:render-settings\x01" + jcs({"source": "supplied", "supplied_listing": sup}).encode()).hex() \
|
||
if sup else None
|
||
seq = seq_obu = None
|
||
units_of, frags = [], []
|
||
own = owners(ix)
|
||
for k, pc in enumerate(pieces):
|
||
mine = [i for i in range(len(E)) if own[i] == k]
|
||
bases = [i for i in mine if E[i]["role"] == BASE]
|
||
fl = [i for i in mine if E[i]["role"] == FLOOR]
|
||
rep.check(len(bases) == 1 and rung is not None and len(fl) == 1 and E[fl[0]]["cap"] == rung["capability"],
|
||
f"{t}: piece {k}: one base, and one floor object of the floor rung's capability (§5, §7.2) (floor-object-mismatch)")
|
||
if len(bases) != 1 or len(fl) != 1:
|
||
return named | unreached(files, c['id'])
|
||
base, i = bases[0], fl[0]
|
||
df = mans[i]["derived_from"]
|
||
rep.check(df["motif"] == E[base]["hash"].hex() and df["motif_capability"] == E[base]["cap"],
|
||
f"{t}: piece {k}: the floor presents its piece's base (floor-motif-mismatch)")
|
||
rep.check(sup is not None and df["renderer"] == f"supplied@{sup}" and df["settings"] == settings,
|
||
f"{t}: piece {k}: a supplied floor's renderer and settings (§7.2) (floor-object-mismatch)")
|
||
fp = floor_payload(objs[i][12 + E[i]["man_len"]:])
|
||
if fp is None:
|
||
rep.check(False, f"{t}: piece {k}: the floor payload is not §7.2's framing (floor-object-mismatch)")
|
||
return named | unreached(files, c['id'])
|
||
rep.check(len(fp[5]) == pc["duration"], f"{t}: piece {k}: one temporal unit per frame (floor-object-mismatch)")
|
||
try:
|
||
units = [obus(u) for u in fp[5]]
|
||
if any(not o or o[0][0] != OBU_TD or sum(x[0] == OBU_TD for x in o) != 1 or unit_frame(o) is None for o in units):
|
||
raise Malformed("a unit that is not one temporal unit holding a frame")
|
||
if seq is None:
|
||
seq_obu = next((whole for typ, whole, _ in units[0] if typ == OBU_SEQ), None) if units else None
|
||
if seq_obu is None:
|
||
raise Malformed("the first unit holds no sequence header")
|
||
seq = sequence_header(obus(seq_obu)[0][2])
|
||
# §7.3 against the package: every sequence header the first's bytes, a shown key
|
||
# frame with one.
|
||
for o in units:
|
||
if any(typ == OBU_SEQ and whole != seq_obu for typ, whole, _ in o):
|
||
raise Malformed("a sequence header other than the first")
|
||
if shown_key(o, seq) and not any(typ == OBU_SEQ for typ, _, _ in o):
|
||
raise Malformed("a shown key frame without a sequence header")
|
||
except Malformed as x:
|
||
rep.check(False, f"{t}: piece {k}: the floor payload ({x}) (floor-av1-malformed)")
|
||
return named | unreached(files, c['id'])
|
||
rep.check(fp[:5] == (pc["duration"], seq["max_h"], seq["max_w"], num, den),
|
||
f"{t}: piece {k}: the floor payload's n, h, w, fps_num and fps_den (§7.2) (floor-object-mismatch)")
|
||
rep.check(mans[i].get("target") == {"frames": pc["duration"], "h": seq["max_h"], "w": seq["max_w"]},
|
||
f"{t}: piece {k}: the floor's target is its piece's frames and its size (§5.2, §7.2) (floor-object-mismatch)")
|
||
keys = [shown_key(o, seq) for o in units]
|
||
ok = bool(keys) and keys[0] and (P["floor"]["extra_keyframes"] or not any(keys[1:]))
|
||
rep.check(ok, f"{t}: piece {k}: it opens on a shown key frame, with no other unless the policy allows (floor-keyframe-mismatch)")
|
||
if not ok:
|
||
return named | unreached(files, c['id'])
|
||
units_of.append(fp[5])
|
||
frag = fragment(ix["first_piece"] + k, pc["start"], den, [(without_delimiters(o), shown_key(o, seq)) for o in units])
|
||
frags.append((hashlib.sha256(frag).hexdigest() + ".m4s", frag))
|
||
init = init_mp4(num, seq["max_w"], seq["max_h"], av1c(seq, seq_obu))
|
||
for p, want in [("init.mp4", init)] + frags:
|
||
path = f"floor/{c['id']}/{p}"
|
||
named.add(path)
|
||
rep.check(path in files and load(os.path.join(pkg, path)) == want,
|
||
f"{t}: {path} is the one the floor's temporal units give (floor-cmaf-mismatch)")
|
||
|
||
# The catalogs (§8.3–§8.8).
|
||
try:
|
||
cats = render_catalogs(P, ix, c["id"], (av1_codecs(seq), seq["max_w"], seq["max_h"], frags), size,
|
||
j["policy_digest"])
|
||
except (ValueError, OverflowError) as x:
|
||
# A time or count past the canonical range (a start past 2^53 − 1): no catalog can
|
||
# be written for this index (§11.3).
|
||
rep.check(False, f"{t}: no catalog can be written for the index ({x}) (catalog-mismatch)")
|
||
return named | unreached(files, c['id'])
|
||
for path in sorted(cats):
|
||
named.add(path)
|
||
rep.check(path in files and load(os.path.join(pkg, path)) == cats[path],
|
||
f"{t}: {path} is the one the index, the objects and the floor give (catalog-mismatch)")
|
||
http_views(rep, t, c["id"], cats, {f"{e['hash'].hex()}.wai": objs[i] for i, e in enumerate(E)})
|
||
if inputs:
|
||
verify_sources(rep, t, root, P, comp, ix, stage, units_of, objs)
|
||
return named
|
||
|
||
|
||
def http_views(rep, t, c, cats, objmap):
|
||
"""§8.3 read by http-delivery's own verifier (http_verify.py): each class's WAI media
|
||
playlist adjacent to the classical one, the union and floor of the objects it lists
|
||
(an object listed twice is one, a layer joins every occurrence of its base,
|
||
http-delivery §4.2) equal to its master's CODECS and X-WAI-FLOOR, and its MPD adjacent
|
||
to the classical MPD."""
|
||
floor_text = cats[f"hls/classic/floor-{c}.m3u8"].decode()
|
||
for path in sorted(cats):
|
||
m = re.fullmatch(r"hls/([^/]+)/wai-" + re.escape(c) + r"\.m3u8", path)
|
||
if not m:
|
||
continue
|
||
cls, text = m.group(1), cats[path].decode()
|
||
try:
|
||
HV.check_adjacent(text, floor_text)
|
||
adjacent = True
|
||
except HV.Refusal:
|
||
adjacent = False
|
||
rep.check(adjacent, f"{t}: {path} is adjacent to the floor's playlist (http-delivery §11.2)")
|
||
try:
|
||
_, _, every = HV.listed_objects(objmap, HV.parse_media_playlist(text))
|
||
union, floor = HV.union_floor_of(every)
|
||
got = (HV.render(union), HV.render(floor))
|
||
except (HV.Refusal, SystemExit) as x:
|
||
got = (f"refused {x}", "")
|
||
master = cats[f"hls/{cls}/wai.m3u8"].decode()
|
||
want = (re.search(r'[,:]CODECS="([^"]+)"', master).group(1), re.search(r'X-WAI-FLOOR="([^"]+)"', master).group(1))
|
||
rep.check(got == want, f"{t}: hls/{cls}/wai.m3u8's CODECS and X-WAI-FLOOR are http-delivery §4.2's over what it lists: {got}, {want}")
|
||
mpd = cats.get(f"dash/{cls}.mpd")
|
||
if mpd is not None:
|
||
try:
|
||
HV.check_dash_adjacent(mpd.decode(), cats["dash/classic.mpd"].decode())
|
||
adjacent = True
|
||
except HV.Refusal:
|
||
adjacent = False
|
||
rep.check(adjacent, f"{t}: dash/{cls}.mpd is adjacent to the classical MPD (http-delivery §11.2)")
|
||
|
||
|
||
def verify_sources(rep, t, root, P, comp, ix, stage, units_of, objs):
|
||
"""What only the source decides: the pieces (§4.3, §4.6) and their rules, moments and
|
||
class plan (§8.1); each piece's ladder (§5.7) re-derived from the source: an
|
||
`int_motion` base's step (§5.6) and choices (§5.5), a still's bytes (§5.7), each
|
||
refinement's bytes and reconstruction (§5.4), and every entry's sse (§6.6); and the
|
||
supplied floor (§7.3). `stage` is each entry's prefix as the package decodes."""
|
||
num, den = comp["timebase"]["num"], comp["timebase"]["den"]
|
||
source = comp.get("source", "")
|
||
name = source[:-len(".y4m")]
|
||
if not source.endswith(".y4m") or fixture_spans(name) is None:
|
||
rep.check(False, f"{t}: the source {source!r} is no fixture this verifier generates (pack-source-missing)")
|
||
return
|
||
w, h, rate, _, frames = read_y4m(fixture(name))
|
||
rep.check(rate == (num, den), f"{t}: the source's frame rate is its timebase (pack-source-rate)")
|
||
pieces, _ = segment(frames, w, h, seg_params(P, (num, den)))
|
||
got = [(pc["start"], pc["len"], pc["flags"], KINDS.index(classify(pc, P["classify"])[0])) for pc in pieces]
|
||
rep.check(got == [(pc["start"], pc["duration"], pc["flags"], pc["kind"]) for pc in ix["pieces"]],
|
||
f"{t}: every piece's start, duration, flags and kind are the segmenter's (§4.3, §4.6)")
|
||
bits, rows = class_plan(P, ix)
|
||
rep.check(bits == [(pc["rules"], pc["moments"]) for pc in ix["pieces"]],
|
||
f"{t}: every piece's rules and moments (§8.1)")
|
||
rep.check(rows == ix["plan"], f"{t}: the class plan is §8.1's")
|
||
E = ix["entries"]
|
||
cache, chosen = {}, set()
|
||
|
||
def load_object_payload(ix, i):
|
||
return objs[i][12 + E[i]["man_len"]:]
|
||
for k, pc in enumerate(ix["pieces"]):
|
||
_, _, then = piece_then(P, ix, k)
|
||
base_rung, chain, _ = piece_ladder(P, comp["id"], then)
|
||
src = frames[pc["start"]:pc["start"] + pc["duration"]]
|
||
n = len(src)
|
||
i = pc["first_entry"]
|
||
e = E[i]
|
||
recon = stage[i]
|
||
rep.check(sum((a - b) ** 2 for s, f in zip(src, recon) for a, b in zip(s, f)) == e["sse"] and len(recon) == n,
|
||
f"{t}: entry {i}: sse against the source (§6.6)")
|
||
if base_rung["capability"] == "wai.image.png":
|
||
# §5.7: the still is the piece's first frame, written as the module states.
|
||
m = load_object_payload(ix, i)
|
||
rep.check(m == png_still(src[0], w, h), f"{t}: entry {i}: the still is the piece's first frame, §5.7's bytes")
|
||
else:
|
||
p = base_rung["params"]
|
||
wv = IPV.parse_wiv1(load_object_payload(ix, i), 0)
|
||
rep.check((wv["block"], wv["mv_precision"]) == ((p["block"], p["mv_precision"]) if n > 1 else (1, 1)),
|
||
f"{t}: entry {i}: the payload's block and precision are the rung's (§5.5)")
|
||
key = hashlib.sha256(b"".join(src)).digest()
|
||
|
||
def sse_of(q, key=key, src=src, p=p):
|
||
if (key, q, p["block"], p["search"], p["mv_precision"]) not in cache:
|
||
cache[key, q, p["block"], p["search"], p["mv_precision"]] = closed_loop(src, h, w, p["block"], p["search"], p["mv_precision"], q)
|
||
return sse_px_milli(cache[key, q, p["block"], p["search"], p["mv_precision"]][1], h, w, len(src))
|
||
q = choose_q(sse_of, p["q_candidates"], p["max_sse_px_milli"])
|
||
rep.check(wv["q"] == q, f"{t}: entry {i}: the step is §5.6's choice ({wv['q']}, the rule {q})")
|
||
rep.check([bytes(f) for f in cache[key, q, p["block"], p["search"], p["mv_precision"]][0]] == recon,
|
||
f"{t}: entry {i}: the payload decodes to the encoder's reconstruction at that step (§5.5)")
|
||
if (e["hash"], key) not in chosen:
|
||
chosen.add((e["hash"], key))
|
||
bad = encoder_choices(wv, p, src, recon)
|
||
rep.check(not bad, f"{t}: entry {i}: every vector, coded residual and the table are the encoder's (§5.5)"
|
||
+ (f": {bad[0]}" if bad else ""))
|
||
# §5.4: each refinement, written again from the source and the stage below.
|
||
below = recon
|
||
for j, rung in enumerate(chain):
|
||
i = pc["first_entry"] + 1 + j
|
||
e = E[i]
|
||
mine, nxt = wir1_layer(below, src, h, w, rung["params"]["q"])
|
||
rep.check(load_object_payload(ix, i) == mine, f"{t}: entry {i}: the layer is §5.4's bytes from the source")
|
||
rep.check(nxt == stage[i], f"{t}: entry {i}: it reconstructs what the package decodes")
|
||
rep.check(sum((a - b) ** 2 for s, f in zip(src, nxt) for a, b in zip(s, f)) == e["sse"],
|
||
f"{t}: entry {i}: sse against the source (§6.6)")
|
||
below = nxt
|
||
|
||
# The supplied floor (§7.3): its listing, its temporal units and its key frames.
|
||
sup = os.path.join(root, "floor")
|
||
names = tree(sup)
|
||
rep.check(hashlib.sha256(listing(sup, names)).hexdigest() == P["floor"].get("supplied_listing"),
|
||
f"{t}: the supplied floor's listing hashes to floor.supplied_listing (floor-listing-mismatch)")
|
||
stream = f"{comp['id']}.obu"
|
||
if stream not in names:
|
||
rep.check(False, f"{t}: no {stream} in the supplied floor (floor-stream-missing)")
|
||
return
|
||
try:
|
||
units = temporal_units(load(os.path.join(sup, stream)))
|
||
if not units or not any(typ == OBU_SEQ for typ, _, _ in units[0][1]):
|
||
raise Malformed("the first unit holds no sequence header")
|
||
first = next(whole for typ, whole, _ in units[0][1] if typ == OBU_SEQ)
|
||
seq = sequence_header(obus(first)[0][2])
|
||
for u, o in units:
|
||
if unit_frame(o) is None:
|
||
raise Malformed("a unit holding no frame")
|
||
if any(typ == OBU_SEQ and whole != first for typ, whole, _ in o):
|
||
raise Malformed("a sequence header other than the first")
|
||
if shown_key(o, seq) and not any(typ == OBU_SEQ for typ, _, _ in o):
|
||
raise Malformed("a shown key frame without a sequence header")
|
||
rep.check(True, f"{t}: the supplied stream splits into temporal units, each a frame (floor-av1-malformed)")
|
||
except Malformed as x:
|
||
rep.check(False, f"{t}: the supplied stream ({x}) (floor-av1-malformed)")
|
||
return
|
||
rep.check(len(units) == len(frames), f"{t}: one temporal unit per source frame (floor-frame-count)")
|
||
keys = [f for f, (_, o) in enumerate(units) if shown_key(o, seq)]
|
||
starts = [pc["start"] for pc in ix["pieces"]]
|
||
ok = set(starts) <= set(keys) and (P["floor"]["extra_keyframes"] or keys == starts)
|
||
rep.check(ok, f"{t}: shown key frames at the piece starts {starts}, and only there: {keys} (floor-keyframe-mismatch)")
|
||
rep.check([[u for u, _ in units[pc["start"]:pc["start"] + pc["duration"]]] for pc in ix["pieces"]] == units_of,
|
||
f"{t}: each floor object holds its piece's temporal units of the supplied stream (§7.2)")
|
||
|
||
|
||
def mutation_verdict(m, c, used):
|
||
"""A case of mutations/: the index's verdict (§6.2, §6.3), then each entry's object's
|
||
(§6.3), or the receipt's (§9.1)."""
|
||
def get(p):
|
||
used.add(p)
|
||
return load(os.path.join(m, p))
|
||
try:
|
||
if c["kind"] in ("index", "objects"):
|
||
ix = parse_index(get(c["index"]))
|
||
index_rules(ix)
|
||
if c["kind"] == "objects":
|
||
for i, e in enumerate(ix["entries"]):
|
||
p = os.path.join(c["objects"], e["hash"].hex() + ".wai")
|
||
if not os.path.isfile(os.path.join(m, p)):
|
||
return "object-missing"
|
||
object_rules(ix, i, get(p))
|
||
return "ok"
|
||
r = read_receipt(get(c["receipt"]))
|
||
verify_receipt_v2(r, [get(p) for p in c["indexes"]])
|
||
return "ok"
|
||
except Refused as e:
|
||
return e.code
|
||
|
||
|
||
def verify_v1_receipt(rep, name, b, want_hash, refused=False):
|
||
"""§9.3: a version-1 receipt read, its payload rebuilt, its hash, counts, segment map
|
||
root and signature; with `refused`, everything but a signature that every verifier
|
||
refuses (SPEC §7 *Signatures*)."""
|
||
try:
|
||
r = read_receipt(b)
|
||
except Refused as e:
|
||
rep.check(False, f"{name}: refused ({e.code})")
|
||
return
|
||
j = r["json"]
|
||
rep.check(r["version"] == 1, f"{name}: version 1")
|
||
rep.check(j["receipt_hash"] == want_hash, f"{name}: receipt_hash is the corpus's")
|
||
rep.check(blake3(RECEIPT_ID + r["payload"] + bytes.fromhex(j["sig"])).hex() == want_hash,
|
||
f"{name}: receipt_hash is BLAKE3 over the payload and signature")
|
||
segs = j["segments"]
|
||
rep.check(merkle_root([v1_leaf(s) for s in segs]).hex() == j["segment_map_root"],
|
||
f"{name}: segment_map_root is the Merkle root of the segment leaves")
|
||
first = {}
|
||
for s in segs:
|
||
first.setdefault(s["content_hash"], s["byte_len"])
|
||
rep.check([s["index"] for s in segs] == list(range(len(segs))), f"{name}: segment indices 0..n-1")
|
||
rep.check((j["total_segments"], j["unique_segments"], j["total_bytes"], j["stored_bytes"], j["work_segment_ops"])
|
||
== (len(segs), len(first), sum(s["byte_len"] for s in segs), sum(first.values()), len(segs)),
|
||
f"{name}: the segment counts")
|
||
rep.check(ed25519_verifies(bytes.fromhex(j["signer_pubkey"]), bytes.fromhex(j["sig"]), r["payload"]) != refused,
|
||
f"{name}: the signature " + ("is refused" if refused else "verifies"))
|
||
|
||
|
||
LIVE_PIECE_FIELDS = ("start", "duration", "kind", "flags", "rules", "moments")
|
||
# An entry's fields a window shares with the on-demand index; its flags (a duplicate is
|
||
# one within its window) and its refines (a window-relative index) are the window's own.
|
||
LIVE_ENTRY_FIELDS = ("hash", "byte_len", "piece", "cap", "role", "layer", "tier", "deadline", "prefix", "sse",
|
||
"man_len", "payload_len")
|
||
|
||
|
||
def verify_live(rep, d):
|
||
lv = json.loads(load(os.path.join(d, "live", "expected.json")))
|
||
gpkg = os.path.join(d, "packs", lv["pack"], "package")
|
||
comp = lv["component"]
|
||
gix = parse_index(load(os.path.join(gpkg, f"index-{comp}.wsix")))
|
||
gkey = read_receipt(load(os.path.join(gpkg, "package.receipt.json")))["json"]["signer_pubkey"]
|
||
names, prev, covered = ["expected.json"], None, 0
|
||
for w in lv["windows"]:
|
||
n, tag = w["window"], f"live window {w['window']}"
|
||
ipath, rpath = f"windows/{n}/index-{comp}.wsix", f"windows/{n}/package.receipt.json"
|
||
names += [ipath, rpath]
|
||
ib, rb = load(os.path.join(d, "live", ipath)), load(os.path.join(d, "live", rpath))
|
||
try:
|
||
r = read_receipt(rb)
|
||
[ix] = verify_receipt_v2(r, [ib])
|
||
except Refused as x:
|
||
rep.check(False, f"{tag}: refused {x.code}")
|
||
return
|
||
j = r["json"]
|
||
rep.check(j["receipt_hash"] == w["receipt_hash"] and index_digest(ib).hex() == w["index_digest"],
|
||
f"{tag}: its receipt hash and index digest are the corpus's")
|
||
rep.check(j["signer_pubkey"] == gkey, f"{tag}: signed by the golden's key")
|
||
rep.check((ix["first_piece"], ix["first_entry"], len(ix["pieces"])) == (w["first_piece"], w["first_entry"], w["pieces"]),
|
||
f"{tag}: its first piece, first entry and pieces are the corpus's")
|
||
if prev is None:
|
||
rep.check(j["parent_kind"] == "none" and not any(ix["prev"]) and ix["first_piece"] == 0 and ix["first_entry"] == 0,
|
||
f"{tag}: the first window starts the chain")
|
||
else:
|
||
pj, pix, pib = prev
|
||
rep.check(j["parent_kind"] == "live-window" and j["parent_receipt_hash"] == pj["receipt_hash"],
|
||
f"{tag}: its parent is the window before's receipt (live-window)")
|
||
rep.check(ix["prev"] == index_digest(pib), f"{tag}: prev_index_digest is the window before's index digest")
|
||
rep.check(ix["first_piece"] == pix["first_piece"] + len(pix["pieces"])
|
||
and ix["first_entry"] == pix["first_entry"] + len(pix["entries"]),
|
||
f"{tag}: first_piece and first_entry continue the chain")
|
||
last = pix["pieces"][-1]
|
||
rep.check(ix["pieces"][0]["start"] == last["start"] + last["duration"], f"{tag}: its first piece starts where the window before ends")
|
||
a = ix["first_piece"]
|
||
rep.check(len(ix["pieces"]) == lv["window_pieces"] or w is lv["windows"][-1], f"{tag}: {lv['window_pieces']} pieces, or the last window")
|
||
for k, pc in enumerate(ix["pieces"]):
|
||
gpc = gix["pieces"][a + k] if a + k < len(gix["pieces"]) else None
|
||
rep.check(gpc is not None and all(pc[f] == gpc[f] for f in LIVE_PIECE_FIELDS), f"{tag}: piece {a + k} is the golden's")
|
||
if gpc is None:
|
||
continue
|
||
got = [tuple(e[f] for f in LIVE_ENTRY_FIELDS) for e in ix["entries"][pc["first_entry"]:pc["first_entry"] + pc["n_entries"]]]
|
||
want = [tuple(e[f] for f in LIVE_ENTRY_FIELDS) for e in gix["entries"][gpc["first_entry"]:gpc["first_entry"] + gpc["n_entries"]]
|
||
if e["role"] != FLOOR]
|
||
rep.check(got == want, f"{tag}: piece {a + k}'s bases and refinements are the golden's, entry for entry")
|
||
rep.check(ix["plan"] == [row[a:a + len(ix["pieces"])] for row in gix["plan"]], f"{tag}: its class plan is the golden's for its pieces")
|
||
rep.check(ix["plan_digest"].hex() == plan_digest([pc["start"] for pc in ix["pieces"]]), f"{tag}: its plan_digest is its starts' (§4.2)")
|
||
prev, covered = (j, ix, ib), covered + len(ix["pieces"])
|
||
rep.check(covered == len(gix["pieces"]), "live/: the windows hold every piece of the golden")
|
||
rep.check(tree(os.path.join(d, "live")) == sorted(names), "live/: every file is a window's")
|
||
|
||
|
||
def verify(d):
|
||
rep = Report()
|
||
pol = json.loads(load(os.path.join(d, "policy", "expected.json")))
|
||
for c in pol["cases"]:
|
||
P = parse_policy_bytes(load(os.path.join(d, "policy", c["file"])))
|
||
if c.get("overrides", {}).get("royalty_free_only") is True:
|
||
P["royalty_free_only"] = True
|
||
rep.check(jcs(P) == c["canonical"], f"policy/{c['file']}: canonical JSON")
|
||
rep.check(digest(P) == c["digest"], f"policy/{c['file']}: digest")
|
||
names = sorted(n for n in os.listdir(os.path.join(d, "policy")) if n.endswith(".toml"))
|
||
rep.check(sorted({c["file"] for c in pol["cases"]}) == names, "policy/: every policy is a case")
|
||
for n in names:
|
||
try:
|
||
admit(load(os.path.join(d, "policy", n)))
|
||
got = "ok"
|
||
except Refused as r:
|
||
got = r.code
|
||
rep.check(got == "ok", f"policy/{n}: a packager accepts it ({got})")
|
||
for a, b in pol["equal"]:
|
||
da, db = (digest(parse_policy_bytes(load(os.path.join(d, "policy", f)))) for f in (a, b))
|
||
rep.check(da == db, f"policy/{a} and {b}: one policy, one digest")
|
||
|
||
ref = json.loads(load(os.path.join(d, "refusals", "expected.json")))
|
||
listed = set()
|
||
for c in ref["cases"]:
|
||
listed.add(c["file"])
|
||
b = load(os.path.join(d, "refusals", c["file"]))
|
||
if c["file"].startswith("policy/"):
|
||
try:
|
||
admit(b)
|
||
got = "ok"
|
||
except Refused as r:
|
||
got = r.code
|
||
else:
|
||
got = source_verdict(c["file"], b)
|
||
rep.check(got == c["reason"] and got != "ok", f"refusals/{c['file']}: {got}, the corpus says {c['reason']}")
|
||
for sub in ("policy", "source"):
|
||
for n in os.listdir(os.path.join(d, "refusals", sub)):
|
||
rep.check(f"{sub}/{n}" in listed, f"refusals/{sub}/{n} is a case")
|
||
|
||
conv = json.loads(load(os.path.join(d, "conversions.json")))
|
||
for c in conv["ms_to_units"]:
|
||
rep.check(units_from_ms(c["ms"], c["num"], c["den"]) == c["units"], f"ms_to_units {c}")
|
||
for c in conv["align"]:
|
||
rep.check(align(c["start"], c["lead"], c["component"]) == c["units"], f"align {c}")
|
||
for c in conv["y4m_rgb"]:
|
||
_, _, _, _, frames = read_y4m(load(os.path.join(d, c["file"])))
|
||
rep.check([hashlib.sha256(f).hexdigest() for f in frames] == c["rgb_sha256"], f"{c['file']}: RGB8 frames")
|
||
|
||
sc = json.loads(load(os.path.join(d, "scenecut", "expected.json")))
|
||
P = parse_policy_bytes(load(os.path.join(d, "scenecut", sc["policy"])))
|
||
comp = next(c for c in P["component"] if c["id"] == sc["component"])
|
||
tb = (comp["timebase"]["num"], comp["timebase"]["den"])
|
||
s = P["segmentation"]
|
||
p = {"min": units_from_ms(s["min_ms"], *tb), "target": units_from_ms(s["target_ms"], *tb),
|
||
"max": units_from_ms(s["max_ms"], *tb), "cut_abs": s["cut_abs_permille"], "cut_rel": s["cut_rel_permille"],
|
||
"window": s["cut_window"], "lookahead": s["lookahead_frames"]}
|
||
cut = {}
|
||
for fx in sc["fixtures"]:
|
||
b = fixture(fx["name"])
|
||
rep.check(hashlib.sha256(b).hexdigest() == fx["sha256"], f"fixture {fx['name']}: SHA-256")
|
||
w, h, rate, _, frames = read_y4m(b)
|
||
rep.check(len(frames) == fx["frames"] and rate == (25, 1), f"fixture {fx['name']}: frames and rate")
|
||
pieces, scores = segment(frames, w, h, p)
|
||
rep.check(scores == fx["scores"], f"scenecut {fx['name']}: scores")
|
||
got = []
|
||
for pc in pieces:
|
||
kind, mad, chg = classify(pc, P["classify"])
|
||
got.append({"start": pc["start"], "len": pc["len"], "flags": pc["flags"], "kind": kind,
|
||
"mad_milli": mad, "changed_permille": chg, "occupancy": pc["occupancy"]})
|
||
rep.check(got == fx["pieces"], f"scenecut {fx['name']}: pieces")
|
||
rep.check(plan_digest([x["start"] for x in got]) == fx["plan_digest"], f"scenecut {fx['name']}: plan digest")
|
||
cut[fx["name"]] = (frames, got, w, h)
|
||
|
||
enc = json.loads(load(os.path.join(d, "encode", "expected.json")))
|
||
want = []
|
||
for fx in sc["fixtures"]:
|
||
for i, pc in enumerate(cut[fx["name"]][1]):
|
||
for s in ("a", "b"):
|
||
want.append((f"{fx['name']}-p{i}-{s}.wiv1", fx["name"], pc["start"], min(pc["len"], ENCODE_FRAMES)))
|
||
rep.check([(v["file"], v["fixture"], v["first"], v["frames"]) for v in enc["vectors"]] == want,
|
||
"encode: one vector per piece of each fixture and parameter set, at the piece's first frames")
|
||
names = sorted(n for n in os.listdir(os.path.join(d, "encode")) if n.endswith(".wiv1"))
|
||
rep.check(names == sorted(v["file"] for v in enc["vectors"]), "encode/: every payload is a vector")
|
||
for v in enc["vectors"]:
|
||
b = load(os.path.join(d, "encode", v["file"]))
|
||
rep.check(blake3(b).hex() == v["blake3"], f"encode/{v['file']}: BLAKE3")
|
||
try:
|
||
w = IPV.parse_wiv1(b, 0)
|
||
frames = IPV.decode_wiv1_k0(w)
|
||
except IPV.Refused as e:
|
||
rep.check(False, f"encode/{v['file']}: refused {e}")
|
||
continue
|
||
p = v["params"]
|
||
head = (w["block"], w["mv_precision"], w["q"], w["n_frames"])
|
||
rep.check(head == ((p["block"], p["mv_precision"], p["q"], v["frames"]) if v["frames"] > 1 else (1, 1, p["q"], 1)),
|
||
f"encode/{v['file']}: the header's block, precision, step and frames")
|
||
rep.check([hashlib.sha256(f).hexdigest() for f in frames] == v["frame_sha256"], f"encode/{v['file']}: decoded frames")
|
||
src = cut[v["fixture"]][0][v["first"]:v["first"] + v["frames"]]
|
||
sse = sum((a - b) ** 2 for s, f in zip(src, frames) for a, b in zip(s, f))
|
||
rep.check(sse == v["sse"], f"encode/{v['file']}: SSE against the source")
|
||
for why in encoder_choices(w, p, src, frames):
|
||
rep.check(False, f"encode/{v['file']}: {why}")
|
||
rep.check(True, f"encode/{v['file']}: the encoder's choices")
|
||
|
||
qc = json.loads(load(os.path.join(d, "qchoice", "expected.json")))
|
||
cache = {}
|
||
for s in qc["sets"]:
|
||
p = s["params"]
|
||
for pc in s["pieces"]:
|
||
frames, _, w, h = cut[pc["fixture"]]
|
||
src = frames[pc["first"]:pc["first"] + pc["frames"]]
|
||
|
||
def sse_of(q, pc=pc, src=src, p=p, w=w, h=h):
|
||
key = (pc["fixture"], pc["first"], pc["frames"], p["block"], p["search"], p["mv_precision"], q)
|
||
if key not in cache:
|
||
_, sse = closed_loop(src, h, w, p["block"], p["search"], p["mv_precision"], q)
|
||
cache[key] = (sse, sse_px_milli(sse, h, w, len(src)))
|
||
return cache[key]
|
||
got = [{"q": q, "sse": sse_of(q)[0], "sse_px_milli": sse_of(q)[1]} for q in p["q_candidates"]]
|
||
rep.check(got == pc["candidates"], f"qchoice {pc['fixture']} piece {pc['piece']}: each candidate's SSE")
|
||
rep.check(choose_q(lambda q: sse_of(q)[1], p["q_candidates"], p["max_sse_px_milli"]) == pc["q"],
|
||
f"qchoice {pc['fixture']} piece {pc['piece']} bound {p['max_sse_px_milli']}: the step chosen")
|
||
|
||
for name in sorted(os.listdir(os.path.join(d, "packs"))):
|
||
verify_pack(rep, d, name)
|
||
|
||
m = os.path.join(d, "mutations")
|
||
used = {"expected.json"}
|
||
for c in json.loads(load(os.path.join(m, "expected.json")))["cases"]:
|
||
got = mutation_verdict(m, c, used)
|
||
rep.check(got == c["reason"], f"mutations {c['name']}: {got}, the corpus says {c['reason']}")
|
||
rest = [p for p in tree(m) if p not in used]
|
||
rep.check(not rest, f"mutations/: files no case uses: {rest}")
|
||
|
||
rc = json.loads(load(os.path.join(d, "receipts", "expected.json")))
|
||
for c in rc["cases"]:
|
||
verify_v1_receipt(rep, f"receipts/{c['file']}", load(os.path.join(d, "receipts", c["file"])), c["receipt_hash"],
|
||
c.get("signature") == "refused")
|
||
rep.check(tree(os.path.join(d, "receipts")) == sorted([c["file"] for c in rc["cases"]] + ["expected.json"]),
|
||
"receipts/: every receipt is a case")
|
||
|
||
# stills/: PNG files a still's reader reads or refuses (§5.7), each read by this
|
||
# verifier's own reader.
|
||
st = json.loads(load(os.path.join(d, "stills", "expected.json")))
|
||
for c in st["cases"]:
|
||
try:
|
||
w, h, px = png_frame(load(os.path.join(d, "stills", c["file"])))
|
||
got = ("ok", w, h, hashlib.sha256(px).hexdigest())
|
||
except ValueError as x:
|
||
got = ("refused", str(x))
|
||
want = ("ok", st["width"], st["height"], c.get("rgb_sha256")) if c["verdict"] == "ok" else ("refused",)
|
||
rep.check(got[:len(want)] == want, f"stills/{c['file']}: {got[0]}, the corpus says {c['verdict']}")
|
||
rep.check(tree(os.path.join(d, "stills")) == sorted([c["file"] for c in st["cases"]] + ["expected.json"]),
|
||
"stills/: every file is a case")
|
||
|
||
# live/: the staged golden's source packed live in windows (§9.2). Each window is read
|
||
# as an index under §6.3's rules with the receipt that seals it (§9.1, signature by
|
||
# WAI's strict rule), chained to the window before by its parent receipt,
|
||
# prev_index_digest, first_piece and first_entry, signed by the golden's key, and
|
||
# holding the golden's pieces with their bases and refinements, entry for entry.
|
||
verify_live(rep, d)
|
||
|
||
# catalogs/: MoQ catalogs of indexes with refinements on more than one deadline (§8.4).
|
||
cc = json.loads(load(os.path.join(d, "catalogs", "expected.json")))
|
||
for c in cc["cases"]:
|
||
ix = parse_index(load(os.path.join(d, c["index"])))
|
||
index_rules(ix)
|
||
f = c["floor"]
|
||
got = render_moq(ix["component"], ix["entries"], c["layer_lag_ms"], c["default_deadline"], (f["codec"], f["w"], f["h"]),
|
||
c["namespace"])
|
||
rep.check(got == c["moq"].encode(), f"{c['index']}: the MoQ catalog §8.4 gives")
|
||
names = {t["name"] for t in json.loads(got)["tracks"]}
|
||
rep.check(all(x in names for t in json.loads(got)["tracks"] for x in t.get("depends", [])),
|
||
f"{c['index']}: no dependency names a track the catalog does not hold")
|
||
rep.check(tree(os.path.join(d, "catalogs")) == sorted([c["index"][len("catalogs/"):] for c in cc["cases"]] + ["expected.json"]),
|
||
"catalogs/: every index is a case")
|
||
# delivery/: the staged golden packed live and its delivery simulated (§8.9), each case
|
||
# re-derived from the fixture's cut and the package's index by this verifier's own
|
||
# mapping onto sim_verify.py's simulator.
|
||
dl = json.loads(load(os.path.join(d, "delivery", "expected.json")))
|
||
pkg = os.path.join(d, "packs", dl["pack"], "package")
|
||
P = parse_policy_bytes(load(os.path.join(pkg, "policy.toml")))
|
||
comp = P["component"][0]
|
||
ix = parse_index(load(os.path.join(pkg, f"index-{comp['id']}.wsix")))
|
||
n_frames = len(read_y4m(fixture(comp["source"][:-len(".y4m")]))[4])
|
||
pub = published_of(P, ix, n_frames)
|
||
ci = [c["id"] for c in P["class"]].index(dl["class"])
|
||
for c in dl["cases"]:
|
||
got = deliver(P, ix["plan"][ci], pub, c["plan"])
|
||
rep.check(got == c["outcome"], f"delivery {c['name']}: the outcome sim_verify.py's simulator gives")
|
||
rep.check([0xFF if x["presented"] is None else x["presented"] for x in got["pieces"]] == c["presented"],
|
||
f"delivery {c['name']}: the presented vector")
|
||
if not c["plan"].get("fifo"):
|
||
rep.check(all(x["base_arrival_us"] is not None and x["base_arrival_us"] <= x["due_us"] for x in got["pieces"]),
|
||
f"delivery {c['name']}: under §8.5's priorities every base arrives by its piece's instant")
|
||
rep.check(tree(os.path.join(d, "delivery")) == ["expected.json"], "delivery/: no file that is no case")
|
||
verify_package_mutations(rep, d)
|
||
# staged-mutations/: the staged golden altered (§5.4, §5.7, §6.5, §8.6).
|
||
verify_package_mutations(rep, d, "staged-mutations")
|
||
return rep
|
||
|
||
|
||
def main(argv):
|
||
if len(argv) != 2:
|
||
print(__doc__)
|
||
return 2
|
||
rep = verify(argv[1])
|
||
check_reader_limit(rep)
|
||
print(f"pack_verify: {rep.checked - rep.failures}/{rep.checked} checks hold")
|
||
return 0 if rep.failures == 0 else 1
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main(sys.argv))
|