mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(producer): copy extracted frames on Windows to avoid symlink EPERM
The local render path (renderOrchestrator → runExtractVideosStage) materialized each video's extracted frames into the compiled dir via symlinkSync, with no materializeSymlinks flag. On Windows without Developer Mode/Administrator, symlinkSync throws EPERM, so local video renders failed at the video_extract stage (users worked around it with materializeSymlinks patches / snapshot-frame hacks). The distributed plan() path already copies (materializeSymlinks: true). Pass materializeSymlinks: shouldCopyExtractedFrames(process.platform) at the local caller — copy on win32 (symlinks unavailable), symlink elsewhere (cheap). New pure shouldCopyExtractedFrames() helper + unit tests.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { appendAutoDetectedVideoAudio } from "./extractVideosStage.js";
|
||||
import { appendAutoDetectedVideoAudio, shouldCopyExtractedFrames } from "./extractVideosStage.js";
|
||||
import type { ExtractedFrames, VideoElement } from "@hyperframes/engine";
|
||||
|
||||
function makeVideo(overrides: Partial<VideoElement> = {}): VideoElement {
|
||||
@@ -81,3 +81,14 @@ describe("appendAutoDetectedVideoAudio", () => {
|
||||
expect(composition.audios).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldCopyExtractedFrames", () => {
|
||||
it("copies frames on Windows (symlinkSync throws EPERM without Developer Mode)", () => {
|
||||
expect(shouldCopyExtractedFrames("win32")).toBe(true);
|
||||
});
|
||||
|
||||
it("symlinks on macOS and Linux (cheaper, symlinks allowed)", () => {
|
||||
expect(shouldCopyExtractedFrames("darwin")).toBe(false);
|
||||
expect(shouldCopyExtractedFrames("linux")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -96,6 +96,18 @@ export interface ExtractVideosStageResult {
|
||||
videoExtractMs: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the extract stage should COPY frames into the compiled dir instead of
|
||||
* symlinking them. Windows without Developer Mode / Administrator can't create
|
||||
* symlinks (`symlinkSync` throws EPERM), which failed local video renders; copy
|
||||
* there instead. Elsewhere symlinking is cheaper, so keep it. (The distributed
|
||||
* `plan()` path already forces copying for a different reason — a self-contained
|
||||
* planDir — by passing `materializeSymlinks: true` explicitly.)
|
||||
*/
|
||||
export function shouldCopyExtractedFrames(platform: NodeJS.Platform): boolean {
|
||||
return platform === "win32";
|
||||
}
|
||||
|
||||
export async function runExtractVideosStage(
|
||||
input: ExtractVideosStageInput,
|
||||
): Promise<ExtractVideosStageResult> {
|
||||
|
||||
@@ -110,7 +110,10 @@ import {
|
||||
import { type HdrPerfCollector, type HdrPerfSummary } from "./render/hdrPerf.js";
|
||||
import { runCompileStage } from "./render/stages/compileStage.js";
|
||||
import { runProbeStage } from "./render/stages/probeStage.js";
|
||||
import { runExtractVideosStage } from "./render/stages/extractVideosStage.js";
|
||||
import {
|
||||
runExtractVideosStage,
|
||||
shouldCopyExtractedFrames,
|
||||
} from "./render/stages/extractVideosStage.js";
|
||||
import { runAudioStage } from "./render/stages/audioStage.js";
|
||||
import { runCaptureStage } from "./render/stages/captureStage.js";
|
||||
import { runCaptureStreamingStage } from "./render/stages/captureStreamingStage.js";
|
||||
@@ -1350,6 +1353,9 @@ export async function executeRenderJob(
|
||||
composition,
|
||||
abortSignal,
|
||||
assertNotAborted,
|
||||
// Copy (don't symlink) extracted frames on Windows — symlinkSync throws
|
||||
// EPERM there without Developer Mode/admin, which failed local renders.
|
||||
materializeSymlinks: shouldCopyExtractedFrames(process.platform),
|
||||
}),
|
||||
);
|
||||
const {
|
||||
|
||||
Reference in New Issue
Block a user