From 8951ed939cef929c562790dde5d851d2cc2cd206 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 12 May 2026 00:31:08 +0000 Subject: [PATCH] refactor(producer): extract extractVideosStage + add materializeSymlinks param MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the video frame extraction sub-stage out of `executeRenderJob` into `services/render/stages/extractVideosStage.ts`. The stage covers HDR color-space pre-detection for videos and images, the `extractAllVideoFrames` call, frame-lookup-table construction, video readiness skip-id collection, video metadata hints, and the auto-detect of audio tracks from video files. Hard constraints preserved verbatim: - `composition.audios` is still mutated in place to add audio entries auto-discovered from video files via ffprobe. - `perfStages.videoExtractMs` is set at the same end-of-stage point. - `materializeExtractedFramesForCompiledDir` is still called once when `extractionResult.extracted` is non-empty. - `force-sdr` mode still skips ALL ffprobe overhead. New for distributed mode (`materializeSymlinks: boolean`, default false): - Plumbs through to `materializeExtractedFramesForCompiledDir` via a new option of the same name. When `true`, the helper invokes `cpSync(recursive)` instead of `symlinkSync` so the staged frames are real files inside `compiledDir`. Symlinks don't survive S3 / GCS round-trips, so distributed `plan()` will pass `true` once it lands. Default `false` preserves the in-process renderer's symlink behavior. - New unit test covers the copy path and asserts `symlinkSync` is NOT invoked; the existing symlink test was updated with the parallel guard that `cpSync` is NOT invoked. Removes the imports the orchestrator no longer needs after the extraction: `extractAllVideoFrames`, `resolveProjectRelativeSrc`, `createFrameLookupTable`, `FrameLookupTable`, `detectTransfer`, `isHdrColorSpace`, `extractMediaMetadata`, `VideoColorSpace` (oxlint flagged each). Verified: - `bunx oxlint` + `bunx oxfmt --check` clean - `bun run --filter @hyperframes/producer typecheck` + `build` clean - `bun test packages/producer/src/services/` — 176 pass, 1 pre-existing unrelated failure - `docker run hyperframes-producer:test` against `font-variant-numeric`, `many-cuts`, `variables-prod`, `sub-composition-video` — 4/4 PASS with correlations 1.000 / 0.994 / 0.947 / 0.975 (sub-composition-video is the one that exercises video frame extraction end-to-end) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../render/stages/extractVideosStage.ts | 244 ++++++++++++++++++ .../src/services/renderOrchestrator.test.ts | 37 +++ .../src/services/renderOrchestrator.ts | 166 +++--------- 3 files changed, 320 insertions(+), 127 deletions(-) create mode 100644 packages/producer/src/services/render/stages/extractVideosStage.ts diff --git a/packages/producer/src/services/render/stages/extractVideosStage.ts b/packages/producer/src/services/render/stages/extractVideosStage.ts new file mode 100644 index 000000000..b181c149f --- /dev/null +++ b/packages/producer/src/services/render/stages/extractVideosStage.ts @@ -0,0 +1,244 @@ +/** + * extractVideosStage — pre-extract source-video JPEG sequences, plus the + * HDR color-space pre-detection that runs against the originals. + * + * The stage runs the existing video-frame extraction pipeline + * (`extractAllVideoFrames`) but also probes BOTH videos and images for + * native HDR color spaces before extraction (since extraction may convert + * SDR → HDR). The HDR maps are returned so the downstream HDR auto-detect + * block and the HDR composite path can identify which sources are natively + * HDR vs. converted-SDR. + * + * Hard constraints preserved verbatim from the in-process renderer: + * - `composition.audios` is mutated in place to add audio entries + * auto-discovered from video files via ffprobe (preserves the + * "video had audio, no explicit