mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 06:30:03 +00:00
fix(engine): resolve root-absolute media from project (#2399)
This commit is contained in:
@@ -438,6 +438,38 @@ describe("processCompositionAudio", () => {
|
|||||||
const prepareArgs = runFfmpegMock.mock.calls[0]?.[0];
|
const prepareArgs = runFfmpegMock.mock.calls[0]?.[0];
|
||||||
expect(prepareArgs).toContain(join(baseDir, "assets", filename));
|
expect(prepareArgs).toContain(join(baseDir, "assets", filename));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("prepares browser root-absolute audio srcs from the project root", async () => {
|
||||||
|
const baseDir = mkdtempSync(join(tmpdir(), "hf-audio-base-"));
|
||||||
|
const workDir = mkdtempSync(join(tmpdir(), "hf-audio-work-"));
|
||||||
|
tempDirs.push(baseDir, workDir);
|
||||||
|
|
||||||
|
mkdirSync(join(baseDir, ".media"), { recursive: true });
|
||||||
|
writeFileSync(join(baseDir, ".media", "tone.wav"), "stub");
|
||||||
|
|
||||||
|
const result = await processCompositionAudio(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: "tone",
|
||||||
|
src: "/.media/tone.wav",
|
||||||
|
start: 0,
|
||||||
|
end: 1,
|
||||||
|
mediaStart: 0,
|
||||||
|
layer: 0,
|
||||||
|
volume: 1,
|
||||||
|
type: "audio",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
baseDir,
|
||||||
|
workDir,
|
||||||
|
join(baseDir, "out.m4a"),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(result.error).toBeUndefined();
|
||||||
|
expect(runFfmpegMock.mock.calls[0]?.[0]).toContain(join(baseDir, ".media", "tone.wav"));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("parseAudioElements — relative data-start resolution", () => {
|
describe("parseAudioElements — relative data-start resolution", () => {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { closeSync, existsSync, mkdirSync, mkdtempSync, openSync, rmSync, writeFileSync } from "fs";
|
import { closeSync, existsSync, mkdirSync, mkdtempSync, openSync, rmSync, writeFileSync } from "fs";
|
||||||
import { isAbsolute, join, dirname } from "path";
|
import { join, dirname } from "path";
|
||||||
import { parseHTML } from "linkedom";
|
import { parseHTML } from "linkedom";
|
||||||
import { extractAudioMetadata } from "../utils/ffprobe.js";
|
import { extractAudioMetadata } from "../utils/ffprobe.js";
|
||||||
import { downloadToTemp, isHttpUrl } from "../utils/urlDownloader.js";
|
import { downloadToTemp, isHttpUrl } from "../utils/urlDownloader.js";
|
||||||
@@ -530,7 +530,7 @@ export async function processCompositionAudio(
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
let srcPath = element.src;
|
let srcPath = element.src;
|
||||||
if (!isAbsolute(srcPath) && !isHttpUrl(srcPath)) {
|
if (!isHttpUrl(srcPath)) {
|
||||||
// Same browser-vs-filesystem path semantics as videos — see
|
// Same browser-vs-filesystem path semantics as videos — see
|
||||||
// resolveProjectRelativeSrc in videoFrameExtractor for the full why.
|
// resolveProjectRelativeSrc in videoFrameExtractor for the full why.
|
||||||
srcPath = resolveProjectRelativeSrc(element.src, baseDir, compiledDir);
|
srcPath = resolveProjectRelativeSrc(element.src, baseDir, compiledDir);
|
||||||
|
|||||||
@@ -143,6 +143,13 @@ describe("resolveProjectRelativeSrc — sub-composition path clamping", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("resolves a browser root-absolute URL from the project root", () => {
|
||||||
|
const projectDir = join(tmp, "project");
|
||||||
|
expect(resolveProjectRelativeSrc("/assets/foo.mp4", projectDir)).toBe(
|
||||||
|
join(projectDir, "assets/foo.mp4"),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("clamps a leading `../` so `../assets/foo.mp4` resolves to assets/foo.mp4", () => {
|
it("clamps a leading `../` so `../assets/foo.mp4` resolves to assets/foo.mp4", () => {
|
||||||
const projectDir = join(tmp, "project");
|
const projectDir = join(tmp, "project");
|
||||||
expect(resolveProjectRelativeSrc("../assets/foo.mp4", projectDir)).toBe(
|
expect(resolveProjectRelativeSrc("../assets/foo.mp4", projectDir)).toBe(
|
||||||
|
|||||||
@@ -670,6 +670,12 @@ export function resolveProjectRelativeSrc(
|
|||||||
): string {
|
): string {
|
||||||
const qIdx = src.indexOf("?");
|
const qIdx = src.indexOf("?");
|
||||||
const cleanSrc = qIdx >= 0 ? src.slice(0, qIdx) : src;
|
const cleanSrc = qIdx >= 0 ? src.slice(0, qIdx) : src;
|
||||||
|
|
||||||
|
// Preserve explicit filesystem paths when they really exist. Otherwise a
|
||||||
|
// leading slash is a browser origin-root URL (`/assets/foo.mp4`), which the
|
||||||
|
// file server serves from the project root rather than the host filesystem.
|
||||||
|
if (isAbsolute(cleanSrc) && existsSync(cleanSrc)) return cleanSrc;
|
||||||
|
|
||||||
const candidates: string[] = [];
|
const candidates: string[] = [];
|
||||||
|
|
||||||
const addCandidate = (candidate: string): void => {
|
const addCandidate = (candidate: string): void => {
|
||||||
@@ -746,11 +752,7 @@ export async function extractAllVideoFrames(
|
|||||||
if (signal?.aborted) break;
|
if (signal?.aborted) break;
|
||||||
try {
|
try {
|
||||||
let videoPath = video.src;
|
let videoPath = video.src;
|
||||||
// Use isAbsolute() rather than startsWith("/"). On Windows, absolute paths
|
if (!isHttpUrl(videoPath)) {
|
||||||
// like "C:\…" are not detected by the latter, so we'd re-join them under
|
|
||||||
// baseDir and produce duplicated, nonexistent paths
|
|
||||||
// (e.g. C:\tmp\hf-vfr-test-X\C:\tmp\hf-vfr-test-X\vfr_screen.mp4).
|
|
||||||
if (!isAbsolute(videoPath) && !isHttpUrl(videoPath)) {
|
|
||||||
videoPath = resolveProjectRelativeSrc(video.src, baseDir, compiledDir);
|
videoPath = resolveProjectRelativeSrc(video.src, baseDir, compiledDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user