mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-24 10:10:05 -06:00
* security: validate CLI paths/args in dev tools (SonarCloud S2083/S8707/S8705/S7044) Resolve and validate command-line path arguments before use, enforce HTTPS at download sinks, and confirm path containment. Hardens local codegen/fetch tools against path-traversal and command-argument-injection findings. * ci: exclude dev tool scripts from SonarCloud coverage gate The extract_tables.py codegen tools aren't part of the unit-tested surface; exclude tools/, docs/, examples/ from coverage so the new-code coverage gate doesn't block hardening-only changes.
134 lines
5.8 KiB
Python
134 lines
5.8 KiB
Python
#!/usr/bin/env python3
|
||
"""Extract the VC-3 constant tables (Annex D quantization weights, Annex E VLC tables) from the
|
||
text of the public SMPTE ST 2019-1:2016 standard.
|
||
|
||
Usage:
|
||
curl -LO https://pub.smpte.org/doc/st2019-1/20160518-pub/st2019-1-20160518-pub.zip
|
||
unzip st2019-1-20160518-pub.zip
|
||
pdftotext -layout st2019-1-2016.pdf vc3.txt
|
||
python3 extract_tables.py vc3.txt > ../src/spec_tables.rs
|
||
|
||
Only the standard's text is read. Every table's size is checked (63 AC weights per matrix, 4*64+1
|
||
amplitude codewords including EOB, 62 run codewords) and every VLC table must be a complete
|
||
prefix code.
|
||
"""
|
||
import re
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
|
||
def _spec_path(arg):
|
||
"""Resolve a CLI path argument and require it to be an existing regular file."""
|
||
path = Path(arg).resolve()
|
||
if not path.is_file():
|
||
sys.exit(f"not a readable file: {arg}")
|
||
return path
|
||
|
||
|
||
text = open(_spec_path(sys.argv[1]), encoding="utf-8").read()
|
||
lines = text.split("\n")
|
||
|
||
|
||
def section(start_pat, end_pat):
|
||
s = next(i for i, l in enumerate(lines) if re.search(start_pat, l))
|
||
e = next(i for i in range(s + 1, len(lines)) if re.search(end_pat, lines[i]))
|
||
return lines[s + 1:e]
|
||
|
||
|
||
def weights(sec):
|
||
"""The 63 AC weights of an 8x8 table printed in raster order with '-' for the DC entry."""
|
||
nums = []
|
||
for l in sec:
|
||
if "Page" in l or "SMPTE" in l or "Compression" in l:
|
||
continue
|
||
nums += [int(x) for x in re.findall(r"\b\d+\b", l)]
|
||
return nums
|
||
|
||
|
||
def check_prefix(codes):
|
||
"""Kraft sum of a prefix code (1.0 when complete)."""
|
||
for a in codes:
|
||
for b in codes:
|
||
if a is not b and b.startswith(a):
|
||
raise SystemExit("not a prefix code: %s %s" % (a, b))
|
||
return sum(2.0 ** -len(c) for c in codes)
|
||
|
||
|
||
out = ["// Generated by tools/extract_tables.py from SMPTE ST 2019-1:2016 (VC-3), Annexes D and E.",
|
||
"// Do not edit by hand.", ""]
|
||
|
||
# ---- Annex D: weights in raster order (v*8+u), DC entry omitted ----
|
||
dtables = [("D_1", r"Table D\.1 – Compression ID 1235"), ("D_2", r"Table D\.2 – Compression ID 1237"),
|
||
("D_3", r"^Table D\.3 – Compression"), ("D_4", r"Table D\.4 – Compression"),
|
||
("D_5", r"Table D\.5 – Compression"), ("D_6", r"Table D\.6 – Compression"),
|
||
("D_7", r"^Table D\.7 – Compression"), ("D_8", r"Table D\.8 – Compression"),
|
||
("D_9", r"Table D\.9 – Compression"), ("D_10", r"Table D\.10 – Compression"),
|
||
("D_11", r"^Table D\.11 – Compression")]
|
||
for i, (name, pat) in enumerate(dtables):
|
||
end = dtables[i + 1][1] if i + 1 < len(dtables) else r"^Annex E VLC Tables"
|
||
sec = section(pat, end)
|
||
if name == "D_11":
|
||
sec = [l for l in sec if "Compression ID" not in l]
|
||
nums = weights(sec)
|
||
assert len(nums) == 63, (name, len(nums))
|
||
out.append("pub const W_%s: [[u8; 63]; 2] = [%s, %s];" % (name, nums, nums))
|
||
continue
|
||
li = next(j for j, l in enumerate(sec) if "Luma Quantization" in l)
|
||
ci = next(j for j, l in enumerate(sec) if "Chroma Quantization" in l)
|
||
luma = weights(sec[li + 1:ci])
|
||
chroma = weights(sec[ci + 1:])
|
||
assert len(luma) == 63 and len(chroma) == 63, (name, len(luma), len(chroma))
|
||
out.append("pub const W_%s: [[u8; 63]; 2] = [%s, %s];" % (name, luma, chroma))
|
||
out.append("")
|
||
|
||
# ---- Annex E ----
|
||
etables = ["E.1", "E.4", "E.7", "E.10", "E.13", "E.16"]
|
||
for n, t in enumerate(etables):
|
||
a = int(t[2:])
|
||
amp = section(r"^\s*Table E\.%d – Amplitude" % a, r"Table E\.%d – Run" % (a + 1))
|
||
run = section(r"Table E\.%d – Run" % (a + 1), r"Table E\.%d – DC" % (a + 2))
|
||
nxt = r"Table E\.%d – Amplitude" % (a + 3) if n + 1 < len(etables) else r"^Annex F System"
|
||
dc = section(r"Table E\.%d – DC" % (a + 2), nxt)
|
||
ent = []
|
||
for l in amp:
|
||
for m in re.finditer(r"\b([01]+)\s+(\d+)\s+(EOB1?|\d+)\s+([01])\s+([01])\b", l):
|
||
code, ln, am, fr, fi = m.groups()
|
||
assert len(code) == int(ln), m.group(0)
|
||
ent.append((code, 0 if am.startswith("EOB") else int(am), int(fr), int(fi), am.startswith("EOB")))
|
||
k = check_prefix([e[0] for e in ent])
|
||
assert abs(k - 1.0) < 1e-12, (t, k, len(ent))
|
||
assert sum(e[4] for e in ent) == 1 and len(ent) == 4 * 64 + 1, (t, len(ent))
|
||
rent = []
|
||
for l in run:
|
||
for m in re.finditer(r"\b([01]+)\s+(\d+)\s+(\d+)\b", l):
|
||
code, ln, r = m.groups()
|
||
if len(code) != int(ln):
|
||
continue
|
||
rent.append((code, int(r)))
|
||
assert len(rent) == 62 and sorted(r for _, r in rent) == list(range(1, 63)), (t, len(rent))
|
||
assert abs(check_prefix([c for c, _ in rent]) - 1.0) < 1e-12
|
||
dent = []
|
||
for l in dc:
|
||
m = re.match(r"^\s+([01]+)\s+(\d+)\s*$", l)
|
||
if m:
|
||
dent.append((m.group(1), int(m.group(2))))
|
||
assert [e for _, e in dent] == list(range(len(dent))), (t, dent)
|
||
assert abs(check_prefix([c for c, _ in dent]) - 1.0) < 1e-12, (t, dent)
|
||
name = t.replace(".", "_")
|
||
out.append("/// Table %s: (codeword, length, amplitude (0 = EOB), Frun, Findex)." % t)
|
||
out.append("pub const AMP_%s: [(u16, u8, u8, u8, u8); %d] = [" % (name, len(ent)))
|
||
for c, a_, fr, fi, _ in ent:
|
||
out.append(" (0b%s, %d, %d, %d, %d)," % (c, len(c), a_, fr, fi))
|
||
out.append("];")
|
||
out.append("/// Table E.%d: (codeword, length, run)." % (a + 1))
|
||
out.append("pub const RUN_%s: [(u16, u8, u8); 62] = [" % name)
|
||
for c, r in rent:
|
||
out.append(" (0b%s, %d, %d)," % (c, len(c), r))
|
||
out.append("];")
|
||
out.append("/// Table E.%d: (codeword, length) indexed by the DC size.", )
|
||
out[-1] = "/// Table E.%d: (codeword, length) indexed by the DC size (eta)." % (a + 2)
|
||
out.append("pub const DC_%s: [(u16, u8); %d] = [%s];" % (name, len(dent), ", ".join("(0b%s, %d)" % (c, len(c)) for c, _ in dent)))
|
||
out.append("")
|
||
|
||
print("\n".join(out))
|