fix(engine,producer): URL-clamp sub-comp src paths and warn on silent extraction misses

A <video src='../assets/foo.mp4'> inside a sub-composition silently dropped
from extraction; the rendered output froze on the first decoded frame for
the entire clip, with no error in stdout.

Root cause: browser URL resolver clamps '..' at origin root (studio preview
loads fine), but path.join(projectDir, '../assets/foo.mp4') normalizes to
parent-of-project/assets/foo.mp4, which usually doesn't exist. existsSync
returns false, extraction is skipped, no frame lookup is built, the
per-frame injector has nothing to swap, and the <video> element's first
decoded frame paints every screenshot.

- Adds resolveProjectRelativeSrc in videoFrameExtractor that mirrors browser
  clamping (literal join first, then leading '..' stripped).
- Surfaces a loud stderr warning when the resolver misses.
- Mirrors fix in audioMixer.ts (same bug for <audio src='../'>) and
  renderOrchestrator HDR probe loop.
- +6 regression tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
James
2026-05-04 20:27:56 -07:00
co-authored by Claude Opus 4.7
parent 0e541673e0
commit 2f96d5c7ab
5 changed files with 118 additions and 16 deletions
+4 -6
View File
@@ -12,6 +12,7 @@ import { downloadToTemp, isHttpUrl } from "../utils/urlDownloader.js";
import { DEFAULT_CONFIG, type EngineConfig } from "../config.js";
import { runFfmpeg } from "../utils/runFfmpeg.js";
import { unwrapTemplate } from "../utils/htmlTemplate.js";
import { resolveProjectRelativeSrc } from "./videoFrameExtractor.js";
import type { AudioElement, AudioTrack, MixResult } from "./audioMixer.types.js";
export type { AudioElement, AudioTrack, MixResult } from "./audioMixer.types.js";
@@ -325,13 +326,10 @@ export async function processCompositionAudio(
}
try {
let srcPath = element.src;
// Use isAbsolute() rather than startsWith("/"). On Windows, absolute paths
// like "C:\…" are not detected by the latter, so we'd re-join them under
// baseDir and produce duplicated, nonexistent paths.
if (!isAbsolute(srcPath) && !isHttpUrl(srcPath)) {
const fromCompiled = compiledDir ? join(compiledDir, srcPath) : null;
srcPath =
fromCompiled && existsSync(fromCompiled) ? fromCompiled : join(baseDir, srcPath);
// Same browser-vs-filesystem path semantics as videos — see
// resolveProjectRelativeSrc in videoFrameExtractor for the full why.
srcPath = resolveProjectRelativeSrc(element.src, baseDir, compiledDir);
}
if (isHttpUrl(srcPath)) {