mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
## Summary Replace the trivial `hdr-pq` and `hdr-image-only` tests with two consolidated, time-windowed regression suites that exercise the full HDR pipeline. These goldens are the safety net for every other PR in this stack. ## Why The pre-existing HDR tests covered only a single full-bleed video or image with a static text label — none of the features that the HDR pipeline has to handle differently from SDR (opacity animation, z-ordered multi-layer compositing, transforms, border-radius clipping, shader transitions, multiple HDR sources, object-fit modes, mixed HDR+SDR layering, HLG transfer). This PR builds the missing safety net first so every subsequent fix can be proven correct. ## What changed - New `packages/producer/tests/hdr-regression/` (PQ, BT.2020, ~20 s, 1080p, 8 windows A–H): - A: static baseline (HDR video + DOM overlay) - B: wrapper-opacity fade - C: direct-on-`<video>` opacity tween (documents the Chunk 1 bug) - D: z-order sandwich (DOM → HDR → DOM) - E: two HDR videos side-by-side (pins PR #289) - F: rotation + scale + border-radius (documents the Chunk 4 bug) - G: `object-fit: contain` - H: shader crossfade between HDR video and HDR image - New `packages/producer/tests/hdr-hlg-regression/` (HLG, ARIB STD-B67, ~5 s, 2 windows A–B) — exercises the separate HLG LUT/OETF code path that previously had **zero** coverage. - New `scripts/generate-hdr-photo-pq.py` synthesizes `hdr-photo-pq.png` with a cICP chunk for BT.2020/PQ/full. - Removed `tests/hdr-pq/` and `tests/hdr-image-only/`. - Updated `.github/workflows/regression.yml` HDR shard to run the new pair sequentially. - All compositions follow the documented timed-element pattern (`data-start`, `data-duration`, `class="clip"` directly on each timed leaf — no wrapper inheritance). ## Test plan - [x] Goldens generated with `bun run test:update --sequential`. - [x] `ffprobe` confirms HEVC/yuv420p10le/bt2020nc/smpte2084 (PQ) and arib-std-b67 (HLG). - [x] Suite green with `maxFrameFailures` budgets that absorb the documented Chunk 1 / Chunk 4 known-fails — tightened in follow-up PRs in this stack. ## Stack Foundational PR for the HDR follow-ups stack (Chunk 0 of `plans/hdr-followups.md`). Every subsequent PR builds on this safety net.
73 lines
2.1 KiB
Python
73 lines
2.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Generate the deterministic 16-bit BT.2020 PQ PNG fixture used by the
|
|
hdr-regression test (window H scene B).
|
|
|
|
Why a custom script (instead of ffmpeg)?
|
|
ffmpeg writes 16-bit RGB PNGs but does not embed a cICP chunk, so
|
|
Chromium does not treat the file as HDR. We synthesize a small RGB48
|
|
bitmap and inject a `cICP` chunk (primaries=BT.2020, transfer=PQ,
|
|
matrix=GBR, range=full) right after IHDR.
|
|
|
|
Output:
|
|
packages/producer/tests/hdr-regression/src/hdr-photo-pq.png
|
|
"""
|
|
|
|
import os
|
|
import struct
|
|
import sys
|
|
import zlib
|
|
|
|
WIDTH = 256
|
|
HEIGHT = 144
|
|
OUT_PATH = os.path.normpath(
|
|
os.path.join(os.path.dirname(__file__), "..", "src", "hdr-photo-pq.png")
|
|
)
|
|
|
|
|
|
def make_image_bytes() -> bytes:
|
|
"""A simple horizontal gradient with super-bright PQ peaks at the right edge."""
|
|
rows = []
|
|
for y in range(HEIGHT):
|
|
row = bytearray()
|
|
for x in range(WIDTH):
|
|
t = x / max(WIDTH - 1, 1)
|
|
r = int(20000 + 45000 * t)
|
|
g = int(15000 + 50000 * (1.0 - abs(2 * t - 1)))
|
|
b = int(60000 - 50000 * t)
|
|
r = max(0, min(65535, r))
|
|
g = max(0, min(65535, g))
|
|
b = max(0, min(65535, b))
|
|
row += struct.pack(">HHH", r, g, b)
|
|
rows.append(b"\x00" + bytes(row))
|
|
return b"".join(rows)
|
|
|
|
|
|
def chunk(ctype: bytes, data: bytes) -> bytes:
|
|
crc = zlib.crc32(ctype + data) & 0xFFFFFFFF
|
|
return struct.pack(">I", len(data)) + ctype + data + struct.pack(">I", crc)
|
|
|
|
|
|
def main() -> int:
|
|
raw = make_image_bytes()
|
|
compressed = zlib.compress(raw, level=9)
|
|
|
|
sig = b"\x89PNG\r\n\x1a\n"
|
|
ihdr = chunk(
|
|
b"IHDR",
|
|
struct.pack(">IIBBBBB", WIDTH, HEIGHT, 16, 2, 0, 0, 0),
|
|
)
|
|
cicp = chunk(b"cICP", bytes([9, 16, 0, 1]))
|
|
idat = chunk(b"IDAT", compressed)
|
|
iend = chunk(b"IEND", b"")
|
|
|
|
os.makedirs(os.path.dirname(OUT_PATH), exist_ok=True)
|
|
with open(OUT_PATH, "wb") as f:
|
|
f.write(sig + ihdr + cicp + idat + iend)
|
|
print(f"wrote {OUT_PATH} ({os.path.getsize(OUT_PATH)} bytes)")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|