From 13c867267e50030e786bf209164d4817ff12869a Mon Sep 17 00:00:00 2001 From: Vance Ingalls Date: Thu, 20 Aug 2026 23:39:11 -0700 Subject: [PATCH] fix(producer): decode percent-encoded video src in HDR pre-extract (#2759) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(producer): decode percent-encoded video src in HDR pre-extract (PRINFRA-349) * fix(producer): decode percent-encoded src in HDR image probe The HDR image probe still hand-rolled the path join the video probe had already delegated to resolveProjectRelativeSrc, so a percent-encoded non-ASCII `` (`图1.png` -> `%E5%9B%BE1.png`) never resolved: the image never entered nativeHdrImageIds, resolveEffectiveHdrMode saw no HDR sources, and the composition rendered through the SDR fallback with wrong color -- silently, unlike the video path which errored at ffmpeg. Both probes now call resolveProjectRelativeSrc directly, with no isAbsolute() pre-check. The resolver already returns an absolute path that exists and otherwise treats a leading slash as a browser origin-root URL, so a pre-check would hand back `/assets/%E5%9B%BE1.png` undecoded and re-open the same bug for root-relative srcs. This matches planHdrResources, so the two halves of the fix can no longer disagree. Widening resolution also makes previously-unresolvable files reachable for the first time, including truncated or 0-byte assets on which ffprobe exits non-zero. These probes run inside a bare Promise.all, so an unguarded throw aborted the whole render over one unreadable image; probeColorSpaceSafely now logs and treats such a source as SDR. Tests cover percent-encoded CJK, origin-root percent-encoded CJK, compiledDir-over-projectDir precedence, and existing-absolute passthrough, with distinct projectDir/compiledDir so the precedence is actually pinned. Fault-injection verified: reintroducing the isAbsolute short-circuit fails the origin-root test. Refs PRINFRA-349 Co-Authored-By: Claude Opus 5 (1M context) * fix(producer): restore the vitest runner import in extractVideosStage tests The rebase merged the new `node:fs` / `node:os` / `node:path` imports into line 1 and took the incoming side, so `import { describe, expect, it } from "vitest"` was replaced rather than kept alongside. The file still uses all three, and `bun run test:classification` regex-matches `/\bfrom\s+["']vitest["']/` to route each test file to a runner — so the file matched neither and hard-failed the gate, taking Producer unit + integration and the required Test check with it. --------- Co-authored-by: Claude Opus 5 (1M context) --- .../render/stages/captureHdrResources.test.ts | 53 ++++++++++++++ .../render/stages/captureHdrResources.ts | 16 +++-- .../services/render/stages/captureHdrStage.ts | 1 - .../render/stages/extractVideosStage.test.ts | 72 +++++++++++++++++++ .../render/stages/extractVideosStage.ts | 66 +++++++++++------ 5 files changed, 180 insertions(+), 28 deletions(-) diff --git a/packages/producer/src/services/render/stages/captureHdrResources.test.ts b/packages/producer/src/services/render/stages/captureHdrResources.test.ts index d2c7deda2..2040c665b 100644 --- a/packages/producer/src/services/render/stages/captureHdrResources.test.ts +++ b/packages/producer/src/services/render/stages/captureHdrResources.test.ts @@ -26,11 +26,13 @@ import { estimateHdrExtractionBytes, extractHdrVideoFrames, getHdrExtractionReservedBytes, + planHdrResources, reserveHdrExtractionBytes, resolveHdrExtractionActiveBudgetBytes, resolveHdrExtractionBudgetBytes, resolveHdrExtractionWindow, } from "./captureHdrResources.js"; +import type { CompositionMetadata } from "../shared.js"; afterEach(() => { vi.unstubAllEnvs(); @@ -115,6 +117,57 @@ function hdrExtractionFixture(videos: VideoElement[], framesDir: string) { }; } +function videoComposition(src: string): CompositionMetadata { + return { + duration: 5, + width: 1920, + height: 1080, + audios: [], + images: [], + videos: [{ id: "a-roll", src, start: 0, end: 5, mediaStart: 0, loop: false, hasAudio: false }], + }; +} + +describe("planHdrResources non-ASCII src resolution (PRINFRA-349)", () => { + it("decodes a percent-encoded CJK