diff --git a/.github/workflows/regression.yml b/.github/workflows/regression.yml index c8dd04eb..98045c63 100644 --- a/.github/workflows/regression.yml +++ b/.github/workflows/regression.yml @@ -2,21 +2,10 @@ name: regression on: pull_request: - branches: [main] push: branches: - main -concurrency: - group: regression-${{ github.ref }} - cancel-in-progress: true - -# Least-privilege token: only reading code. Jobs that need more (e.g. GHA -# cache reads/writes from docker/build-push-action with `type=gha`) elevate -# their own permissions inline. -permissions: - contents: read - jobs: changes: name: Detect changes @@ -36,56 +25,11 @@ jobs: - "packages/engine/**" - "Dockerfile*" - # Build the regression Docker image once, export it as a tarball, and upload - # as an artifact. Each matrix shard then downloads + `docker load`s it instead - # of rebuilding from cache. Measured on PR #419: the Docker build step takes - # ~4 min per shard even with GHA cache, so 11 shards = ~44 min of redundant - # build time per run. This job replaces that with a single ~4 min build plus - # ~15s of artifact download per shard. - build-image: - name: Build regression test image - needs: changes - if: needs.changes.outputs.code == 'true' - runs-on: ubuntu-latest - timeout-minutes: 20 - permissions: - contents: read - actions: write # docker/build-push-action `type=gha` cache reads + writes - steps: - - name: Checkout - uses: actions/checkout@v4 - # No LFS needed here — Dockerfile.test only copies source + package manifests, - # not the golden baselines under packages/producer/tests/**/output. - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Build test image to tarball - uses: docker/build-push-action@v6 - with: - context: . - file: Dockerfile.test - tags: hyperframes-producer:test - cache-from: type=gha,scope=regression-test-image - cache-to: type=gha,mode=max,scope=regression-test-image - outputs: type=docker,dest=/tmp/regression-test-image.tar - - - name: Report image size - run: ls -lh /tmp/regression-test-image.tar - - - name: Upload image artifact - uses: actions/upload-artifact@v4 - with: - name: regression-test-image - path: /tmp/regression-test-image.tar - retention-days: 1 - compression-level: 1 - regression-shards: - needs: [changes, build-image] + needs: changes if: needs.changes.outputs.code == 'true' runs-on: ubuntu-latest - timeout-minutes: 40 + timeout-minutes: 60 strategy: fail-fast: false matrix: @@ -95,7 +39,7 @@ jobs: - shard: render-compat args: "--sequential gsap-letters-render-compat css-spinner-render-compat raf-ball-render-compat iframe-render-compat" - shard: hdr - args: "--sequential hdr-pq hdr-image-only" + args: "--sequential hdr-regression hdr-hlg-regression" - shard: styles-a args: "style-1-prod style-2-prod style-3-prod" - shard: styles-b @@ -130,16 +74,18 @@ jobs: fi done - - name: Download test image artifact - uses: actions/download-artifact@v4 - with: - name: regression-test-image - path: /tmp + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - - name: Load test image - run: | - docker load -i /tmp/regression-test-image.tar - docker image ls hyperframes-producer:test + - name: Build test Docker image (cached) + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile.test + load: true + tags: hyperframes-producer:test + cache-from: type=gha,scope=regression-test-image + cache-to: type=gha,mode=min,scope=regression-test-image - name: "Run regression shard: ${{ matrix.shard }}" run: | diff --git a/packages/engine/src/utils/ffprobe.test.ts b/packages/engine/src/utils/ffprobe.test.ts index 97bc1f69..20ebd588 100644 --- a/packages/engine/src/utils/ffprobe.test.ts +++ b/packages/engine/src/utils/ffprobe.test.ts @@ -55,7 +55,7 @@ describe("extractVideoMetadata", () => { it("reads HDR PNG cICP metadata when ffprobe color fields are absent", async () => { const fixturePath = resolve( __dirname, - "../../../producer/tests/hdr-image-only/src/hdr-photo.png", + "../../../producer/tests/hdr-regression/src/hdr-photo-pq.png", ); const metadata = await extractVideoMetadata(fixturePath); @@ -102,7 +102,7 @@ describe("extractPngMetadataFromBuffer", () => { it("continues to parse the checked-in HDR PNG fixture", () => { const fixture = readFileSync( - resolve(__dirname, "../../../producer/tests/hdr-image-only/src/hdr-photo.png"), + resolve(__dirname, "../../../producer/tests/hdr-regression/src/hdr-photo-pq.png"), ); expect(extractPngMetadataFromBuffer(fixture)?.colorSpace?.colorTransfer).toBe("smpte2084"); }); diff --git a/packages/producer/tests/hdr-hlg-regression/README.md b/packages/producer/tests/hdr-hlg-regression/README.md new file mode 100644 index 00000000..1ce0abdc --- /dev/null +++ b/packages/producer/tests/hdr-hlg-regression/README.md @@ -0,0 +1,52 @@ +# hdr-hlg-regression + +Regression test that locks down end-to-end **HDR HLG (BT.2020 ARIB STD-B67)** +video rendering. Companion to `hdr-regression` (PQ), kept as a separate suite +so the HLG-specific encoder/metadata path stays tested in isolation. + +## What it covers + +| Window | Time | Shape | Expected | +| ------ | ------------ | ------------------------------------- | -------- | +| A | 0.0 – 2.5 s | Baseline HLG video + DOM overlay | pass | +| B | 2.5 – 5.0 s | Wrapper opacity fade around HLG video | pass | + +The test pins the contract that: + +- `extractVideoMetadata` reports `bt2020/arib-std-b67/limited` for the HLG + source (i.e. HLG is detected and not silently coerced to PQ). +- `isHdrColorSpace` flips the orchestrator into the layered HDR path on the + HLG signal. +- The HLG source is decoded into `rgb48le` and blitted under the SDR DOM + overlay on every frame. +- Wrapper-opacity composition (window B) does not break HLG pass-through. +- `hdrEncoder` writes HEVC Main10 / `yuv420p10le` / BT.2020 HLG with the + correct color tags (no PQ mastering display metadata for HLG). + +The suite is intentionally short (5 s, two windows) — it exists to detect +regressions in the HLG-specific code path, not to enumerate every composition +shape (those live in `hdr-regression`). + +## Tolerance + +`maxFrameFailures` is **0** here. HLG is a pure pass-through path — no known +failures, no transcoder workarounds — and HEVC encoding against the rendered +`rgb48le` buffer is byte-deterministic on the same fixture. Any drift is a +real regression, not codec noise, so the budget is the strictest possible. + +## Fixture + +`src/hdr-hlg-clip.mp4` — last 5 seconds of a user-recorded HEVC HLG clip, +remuxed (no re-encode) so the HLG color tags survive verbatim. + +## Running + +```bash +cd packages/producer +bun run test hdr-hlg-regression + +bun run test:update hdr-hlg-regression +``` + +In CI it runs in the `hdr` shard alongside `hdr-regression` +(see `.github/workflows/regression.yml`). diff --git a/packages/producer/tests/hdr-hlg-regression/meta.json b/packages/producer/tests/hdr-hlg-regression/meta.json new file mode 100644 index 00000000..95c16379 --- /dev/null +++ b/packages/producer/tests/hdr-hlg-regression/meta.json @@ -0,0 +1,14 @@ +{ + "name": "hdr-hlg-regression", + "description": "Regression test for HDR HLG (BT.2020 ARIB STD-B67) video pass-through. Two windows: (A) baseline HLG video + SDR DOM overlay, (B) wrapper-opacity fade applied to an HLG video. Verifies that an HLG HEVC source drives the layered HDR pipeline end-to-end (extractVideoMetadata reports hlg, ffmpegFrameSource decodes correctly, hdrEncoder writes HEVC Main10 / yuv420p10le / BT.2020 HLG with the appropriate mastering metadata) and that opacity composition does not break HLG signal pass-through.", + "tags": ["regression", "hdr"], + "minPsnr": 28, + "maxFrameFailures": 0, + "minAudioCorrelation": 0, + "maxAudioLagWindows": 1, + "renderConfig": { + "fps": 30, + "workers": 1, + "hdr": true + } +} diff --git a/packages/producer/tests/hdr-pq/output/compiled.html b/packages/producer/tests/hdr-hlg-regression/output/compiled.html similarity index 54% rename from packages/producer/tests/hdr-pq/output/compiled.html rename to packages/producer/tests/hdr-hlg-regression/output/compiled.html index 7e6ea014..de20c611 100644 --- a/packages/producer/tests/hdr-pq/output/compiled.html +++ b/packages/producer/tests/hdr-hlg-regression/output/compiled.html @@ -24,7 +24,21 @@ font-display: block; } - HDR PQ Regression + HDR HLG Regression Suite + -
- +
+ + +
+ A · HLG baseline + DOM overlay +
-
HDR PQ
+ +
+ +
+
+ B · HLG wrapper opacity fade +
+ - + diff --git a/packages/producer/tests/hdr-hlg-regression/output/output.mp4 b/packages/producer/tests/hdr-hlg-regression/output/output.mp4 new file mode 100644 index 00000000..124db64d --- /dev/null +++ b/packages/producer/tests/hdr-hlg-regression/output/output.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a55a505de180ef3eea4311848bc58d56778a0a291713726b8c27f07e80d5bfe +size 6156319 diff --git a/packages/producer/tests/hdr-hlg-regression/src/hdr-hlg-clip.mp4 b/packages/producer/tests/hdr-hlg-regression/src/hdr-hlg-clip.mp4 new file mode 100644 index 00000000..b630e4fe --- /dev/null +++ b/packages/producer/tests/hdr-hlg-regression/src/hdr-hlg-clip.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6f0be49c970a41111a10f146e6b19177da7d42c7791aeb70fdabf3cbf8e06d2 +size 4533211 diff --git a/packages/producer/tests/hdr-hlg-regression/src/index.html b/packages/producer/tests/hdr-hlg-regression/src/index.html new file mode 100644 index 00000000..d125dab3 --- /dev/null +++ b/packages/producer/tests/hdr-hlg-regression/src/index.html @@ -0,0 +1,112 @@ + + + + + HDR HLG Regression Suite + + + + +
+ + +
+ A · HLG baseline + DOM overlay +
+ + +
+ +
+
+ B · HLG wrapper opacity fade +
+
+ + + + diff --git a/packages/producer/tests/hdr-image-only/README.md b/packages/producer/tests/hdr-image-only/README.md deleted file mode 100644 index 0317f3fc..00000000 --- a/packages/producer/tests/hdr-image-only/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# hdr-image-only - -Regression test that locks down end-to-end **HDR still-image** rendering — no -HDR video source involved. - -## What it covers - -- `parseImageElements` discovers the `` source. -- `extractStillImageMetadata` (via `ffprobe`) probes the PNG and reads the - `cICP` chunk, surfacing `colorPrimaries=bt2020`, `colorTransfer=smpte2084`. -- `isHdrColorSpace` flips the orchestrator into the layered HDR path even - though `hdrVideoColorSpace` is null (image-only compositions must still - trigger HDR). -- The image is decoded once into `rgb48le` and blitted under the SDR DOM - overlay on every frame. -- The encoder writes HEVC Main10 / `yuv420p10le` / BT.2020 PQ with HDR10 - mastering display + content light level metadata. -- The harness then visually compares the output against the golden - `output/output.mp4` (PSNR ≥ 28). - -## Fixture - -`src/hdr-photo.png` — 256×144, 16-bit RGB, with a hand-injected `cICP` chunk -(primaries=BT.2020, transfer=SMPTE ST 2084, matrix=GBR, range=full). - -ffmpeg is **not** used here because it does not embed `cICP` in PNGs — without -that chunk Chromium would not treat the file as HDR and the test would silently -fall back to SDR. - -To regenerate the fixture: - -```bash -python3 packages/producer/tests/hdr-image-only/scripts/generate-fixture.py -``` - -The script is deterministic (fixed dimensions, fixed gradient), so the PNG -hashes byte-for-byte across runs — safe to commit and diff in CI. - -## Running - -```bash -cd packages/producer -bun run regression hdr-image-only - -bun run regression --update hdr-image-only -``` - -In CI it runs in the `hdr` shard alongside `hdr-pq` -(see `.github/workflows/regression.yml`). diff --git a/packages/producer/tests/hdr-image-only/meta.json b/packages/producer/tests/hdr-image-only/meta.json deleted file mode 100644 index fece16de..00000000 --- a/packages/producer/tests/hdr-image-only/meta.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "hdr-image-only", - "description": "Regression test for HDR still-image support. Verifies that a 16-bit BT.2020 PQ PNG (tagged via cICP) drives the layered HDR compositing pipeline end-to-end with no HDR video sources present: image is decoded once into rgb48le, blitted under the SDR DOM overlay, and encoded as HEVC Main10 with HDR10 mastering metadata.", - "tags": ["regression", "hdr"], - "minPsnr": 28, - "maxFrameFailures": 0, - "minAudioCorrelation": 0, - "maxAudioLagWindows": 1, - "renderConfig": { - "fps": 30, - "workers": 1, - "hdr": true - } -} diff --git a/packages/producer/tests/hdr-image-only/output/compiled.html b/packages/producer/tests/hdr-image-only/output/compiled.html deleted file mode 100644 index 1d677aac..00000000 --- a/packages/producer/tests/hdr-image-only/output/compiled.html +++ /dev/null @@ -1,72 +0,0 @@ - - - - - HDR Image-Only Regression - - - -
- HDR PNG -
HDR PNG
-
- - - diff --git a/packages/producer/tests/hdr-image-only/output/output.mp4 b/packages/producer/tests/hdr-image-only/output/output.mp4 deleted file mode 100644 index 85f03611..00000000 --- a/packages/producer/tests/hdr-image-only/output/output.mp4 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e74b69e34c2c2b30a4f8cb82abc50f8cc8590fd209fd94e7c84f7f02230e2c5c -size 21760 diff --git a/packages/producer/tests/hdr-image-only/src/index.html b/packages/producer/tests/hdr-image-only/src/index.html deleted file mode 100644 index 43f021c1..00000000 --- a/packages/producer/tests/hdr-image-only/src/index.html +++ /dev/null @@ -1,60 +0,0 @@ - - - - - HDR Image-Only Regression - - - -
- HDR PNG -
HDR PNG
-
- - - diff --git a/packages/producer/tests/hdr-pq/meta.json b/packages/producer/tests/hdr-pq/meta.json deleted file mode 100644 index 05789088..00000000 --- a/packages/producer/tests/hdr-pq/meta.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "hdr-pq", - "description": "Regression test for HDR10 (BT.2020 PQ) video pass-through with a static SDR DOM overlay. Verifies that an HDR HEVC Main10 source plus a single white text label renders end-to-end through the layered HDR compositing pipeline (rgb48le DOM-over-video blit, x265 Main10 encode, HDR10 mastering metadata).", - "tags": ["regression", "hdr"], - "minPsnr": 28, - "maxFrameFailures": 0, - "minAudioCorrelation": 0, - "maxAudioLagWindows": 1, - "renderConfig": { - "fps": 30, - "workers": 1, - "hdr": true - } -} diff --git a/packages/producer/tests/hdr-pq/output/output.mp4 b/packages/producer/tests/hdr-pq/output/output.mp4 deleted file mode 100644 index a4be70ce..00000000 --- a/packages/producer/tests/hdr-pq/output/output.mp4 +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:00c0ee1bf4e19c372898683c4b65d346a58c71ad590a03b7db1388d1255d680a -size 1732052 diff --git a/packages/producer/tests/hdr-pq/src/index.html b/packages/producer/tests/hdr-pq/src/index.html deleted file mode 100644 index 3a891ee4..00000000 --- a/packages/producer/tests/hdr-pq/src/index.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - - HDR PQ Regression - - - -
- - -
HDR PQ
-
- - - diff --git a/packages/producer/tests/hdr-pq/NOTICE.md b/packages/producer/tests/hdr-regression/NOTICE.md similarity index 100% rename from packages/producer/tests/hdr-pq/NOTICE.md rename to packages/producer/tests/hdr-regression/NOTICE.md diff --git a/packages/producer/tests/hdr-regression/README.md b/packages/producer/tests/hdr-regression/README.md new file mode 100644 index 00000000..b9efb3a7 --- /dev/null +++ b/packages/producer/tests/hdr-regression/README.md @@ -0,0 +1,37 @@ +# HDR Regression Suite + +HDR10 (BT.2020 PQ) regression suite with four back-to-back windows (A–D) +covering the highest-value HDR compositing shapes. 10s / 300 frames at 30fps. + +## Windows + +| # | Window | Pipeline aspect under test | +| - | ----------------------------------- | --------------------------------------------------------------------------------------------------------- | +| A | Baseline HDR + direct opacity | HDR pass-through with a GSAP opacity tween directly on the `