diff --git a/packages/engine/src/index.ts b/packages/engine/src/index.ts index d2dff7f6f..28e533c76 100644 --- a/packages/engine/src/index.ts +++ b/packages/engine/src/index.ts @@ -115,6 +115,7 @@ export { parseImageElements, extractVideoFramesRange, extractAllVideoFrames, + resolveProjectRelativeSrc, getFrameAtTime, createFrameLookupTable, FrameLookupTable, diff --git a/packages/engine/src/services/audioMixer.ts b/packages/engine/src/services/audioMixer.ts index a3ccb804c..b204a79f1 100644 --- a/packages/engine/src/services/audioMixer.ts +++ b/packages/engine/src/services/audioMixer.ts @@ -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)) { diff --git a/packages/engine/src/services/videoFrameExtractor.test.ts b/packages/engine/src/services/videoFrameExtractor.test.ts index f03c63fdf..84635f12b 100644 --- a/packages/engine/src/services/videoFrameExtractor.test.ts +++ b/packages/engine/src/services/videoFrameExtractor.test.ts @@ -9,6 +9,7 @@ import { parseImageElements, extractAllVideoFrames, createFrameLookupTable, + resolveProjectRelativeSrc, type VideoElement, type ExtractedFrames, } from "./videoFrameExtractor.js"; @@ -23,6 +24,67 @@ import { runFfmpeg } from "../utils/runFfmpeg.js"; // synthesized VFR fixture. const HAS_FFMPEG = spawnSync("ffmpeg", ["-version"]).status === 0; +// Regression: a long-standing footgun where `