Files
filmcraft/crates/vp8/tools/extract_tables.py
MrCloudy2andClaude Opus 5.5 53a0d959fe #432: VP8 video decodes (clean-room pure-Rust VP8 decoder)
VP8 WebM showed black video: there was no VP8 decoder. New L0 crate
filmcraft-vp8, implemented from RFC 6386 sections 1-19 (tables extracted
from the RFC text, nothing from the attached reference decoder): every
coding tool, versions 0-3, hidden altref frames, size changes. Bit-exact
with libvpx on 62 libvpx VP8 test vectors, real-world FATE samples and
generated fixtures; never panics on corrupted input.

Wired into codecs for Matroska / WebM (V_VP8, including the alpha layer
of transparent WebM) and MP4 (vp08). VP8 colour is BT.601 (RFC 6386 9.2).
A hidden altref frame is a WebM block of its own with the time of the
frame that follows it: the VP8 time lookup now moves on to that shown
frame instead of repeating the previous one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 15:13:07 +02:00

85 lines
3.5 KiB
Python

#!/usr/bin/env python3
"""Extract the VP8 constant tables from the text of RFC 6386 ("VP8 Data Format and Decoding Guide").
Usage:
curl -L -o rfc6386.txt https://www.rfc-editor.org/rfc/rfc6386.txt
python3 extract_tables.py rfc6386.txt > ../src/spec_tables.rs
Only sections 1-19 (the format description) are read: the text is cut at section 20 ("Attachment
One: Reference Decoder Source Code"), so nothing comes from the reference decoder. Every table is
emitted flat (row-major, as printed) with its element count checked against the declared dimensions.
"""
import re
import sys
text = open(sys.argv[1], encoding="utf-8").read()
cut = text.find("\n20. Attachment One")
if cut < 0:
sys.exit("section 20 not found")
text = text[:cut]
# page breaks: footer / header lines and form feeds
text = "\n".join(l for l in text.split("\n") if not l.startswith("Bankoski") and not l.startswith("RFC 6386 ") and "\f" not in l)
def table(name, count, occurrence=-1):
"""Integers of the table `name ... = { ... }` (by default the last definition in the text)."""
found = []
# a definition: `name [dim] [dim] ... = {` (uses of the table never have `= {` after the brackets)
for m in re.finditer(r"\b%s\s*(?:\[[^\]\n]*\]\s*)+=\s*\{" % re.escape(name), text):
brace = m.end() - 1
depth = 0
i = brace
while True:
ch = text[i]
if ch == "{":
depth += 1
elif ch == "}":
depth -= 1
if depth == 0:
break
i += 1
body = re.sub(r"/\*.*?\*/", "", text[brace:i], flags=re.S)
body = re.sub(r"//[^\n]*", "", body)
found.append([int(v) for v in re.findall(r"-?\d+", body)])
if not found:
sys.exit("table %s not found" % name)
vals = found[occurrence]
if len(vals) != count:
sys.exit("table %s: %d values, expected %d" % (name, len(vals), count))
return vals
TABLES = [
# (spec name, rust name, rust type, count)
("coeff_update_probs", "COEFF_UPDATE_PROBS", "u8", 4 * 8 * 3 * 11),
("default_coeff_probs", "DEFAULT_COEFF_PROBS", "u8", 4 * 8 * 3 * 11),
("kf_bmode_prob", "KF_BMODE_PROBS", "u8", 10 * 10 * 9),
("dc_qlookup", "DC_QLOOKUP", "u16", 128),
("ac_qlookup", "AC_QLOOKUP", "u16", 128),
("vp8_mode_contexts", "MODE_CONTEXTS", "u8", 6 * 4),
("sub_mv_ref_prob", "SUB_MV_REF_PROBS", "u8", 5 * 3),
("vp8_mv_update_probs", "MV_UPDATE_PROBS", "u8", 2 * 19),
("default_mv_context", "DEFAULT_MV_PROBS", "u8", 2 * 19),
("bmode_prob", "BMODE_PROBS", "u8", 9),
("coeff_bands", "COEFF_BANDS", "u8", 16),
("kf_ymode_prob", "KF_YMODE_PROBS", "u8", 4),
("kf_uv_mode_prob", "KF_UV_MODE_PROBS", "u8", 3),
("ymode_prob", "YMODE_PROBS", "u8", 4),
("uv_mode_prob", "UV_MODE_PROBS", "u8", 3),
("mvpartition_probs", "MVPARTITION_PROBS", "u8", 3),
("BilinearFilters", "BILINEAR_FILTERS", "i32", 8 * 6),
("filters", "SIXTAP_FILTERS", "i32", 8 * 6),
]
print("// Generated by tools/extract_tables.py from the text of RFC 6386 (sections 1-19). Do not edit.")
print("// Every table is flat, row-major, in the order the specification prints it.")
print()
for spec, rust, ty, count in TABLES:
vals = table(spec, count)
print("/// `%s` (RFC 6386), %d entries." % (spec, count))
print("pub const %s: [%s; %d] = [" % (rust, ty, count))
for i in range(0, count, 16):
print(" " + ", ".join(str(v) for v in vals[i:i + 16]) + ",")
print("];")
print()