From 95837a3df7c10199a83fa1bd6ff92eafc2d1e750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Wed, 14 Oct 2026 03:32:35 +0000 Subject: [PATCH] fix(core): keep the colour grade on a graded video's first rendered frame (#5575) * fix(core): keep the colour grade on a graded video's first rendered frame A graded clip rendered ungraded for one frame wherever a capture session first drew it: the clip's own first frame, a parallel worker's first frame inside it, or a capture segment's first frame inside it. The grade dropped the clip while its injected frame was still decoding, so the redraw after decode had nothing to draw. Part of #5548 * chore: pin the producer test-classification hash and drop an added comment --- .../core/src/runtime/colorGrading.test.ts | 23 +++ packages/core/src/runtime/colorGrading.ts | 2 +- .../producer/scripts/test-classification.mjs | 1 + .../segmentedColorGrading.integration.test.ts | 195 ++++++++++++++++++ scripts/test-reachability.json | 2 +- 5 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 packages/producer/src/services/render/segmentedColorGrading.integration.test.ts diff --git a/packages/core/src/runtime/colorGrading.test.ts b/packages/core/src/runtime/colorGrading.test.ts index 014eb8826d..dd27d64a16 100644 --- a/packages/core/src/runtime/colorGrading.test.ts +++ b/packages/core/src/runtime/colorGrading.test.ts @@ -1041,6 +1041,29 @@ describe("createColorGradingRuntime", () => { expect(document.querySelector("[data-hf-color-grading-canvas]")).not.toBeNull(); }); + it("grades a clip's first producer frame when the DOM observer runs before it decodes", () => { + const video = makeDrawableVideo(); + video.style.visibility = "hidden"; + document.body.appendChild(video); + runtime = createColorGradingRuntime(); + + let decoded = false; + const frame = document.createElement("img"); + frame.id = "__render_frame_hero-video__"; + frame.className = "__render_frame__"; + frame.style.visibility = "visible"; + Object.defineProperty(frame, "complete", { get: () => decoded, configurable: true }); + Object.defineProperty(frame, "naturalWidth", { get: () => (decoded ? 640 : 0) }); + Object.defineProperty(frame, "naturalHeight", { get: () => (decoded ? 360 : 0) }); + video.after(frame); + runtime.refresh(); + decoded = true; + + expect(runtime.redraw()).toBe(1); + expect(texImage2DCalls.at(-1)).toContain(frame); + expect(frame.nextSibling).toBe(document.querySelector("[data-hf-color-grading-canvas]")); + }); + it("does not recreate an inactive clip from its hidden producer frame", () => { const { video, canvas } = startRuntimeWithVideo(); const frame = document.createElement("img"); diff --git a/packages/core/src/runtime/colorGrading.ts b/packages/core/src/runtime/colorGrading.ts index f0c5ae4a32..f72079ba96 100644 --- a/packages/core/src/runtime/colorGrading.ts +++ b/packages/core/src/runtime/colorGrading.ts @@ -2494,7 +2494,7 @@ function findRenderFrameImage(video: HTMLVideoElement): HTMLImageElement | null function hasInjectedRenderFrame(element: ColorGradingMediaElement): boolean { if (!isVideoElement(element)) return false; - const frame = findRenderFrameImage(element); + const frame = findInjectedRenderFrame(element); if (!frame) return false; const style = window.getComputedStyle(frame); return style.display !== "none" && style.visibility !== "hidden"; diff --git a/packages/producer/scripts/test-classification.mjs b/packages/producer/scripts/test-classification.mjs index 2aa4f1673b..0bf0e4f5d2 100644 --- a/packages/producer/scripts/test-classification.mjs +++ b/packages/producer/scripts/test-classification.mjs @@ -33,6 +33,7 @@ const INTEGRATION_TEST_FILES = new Set([ "src/services/render/hlsRender.integration.test.ts", "src/services/render/motionBlurVideo.integration.test.ts", "src/services/render/renderCancel.integration.test.ts", + "src/services/render/segmentedColorGrading.integration.test.ts", "src/services/render/stages/compileStage.mediaType.test.ts", "src/services/render/stages/gifEncodeArgs.test.ts", "src/services/videoFrameBorderClip.test.ts", diff --git a/packages/producer/src/services/render/segmentedColorGrading.integration.test.ts b/packages/producer/src/services/render/segmentedColorGrading.integration.test.ts new file mode 100644 index 0000000000..57356f9698 --- /dev/null +++ b/packages/producer/src/services/render/segmentedColorGrading.integration.test.ts @@ -0,0 +1,195 @@ +// A graded