mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
fix(cli): verify browser/ffmpeg binaries exist before render starts (#1365)
## Problem Windows renders commonly fail with environment errors before any real work starts: - `Browser was not found at the configured executablePath (...chrome-headless-shell.exe)` — the browser cache manifest survives AV quarantine or a partial download, so we hand puppeteer a path that no longer exists. - `[FFmpeg] ffprobe not found` and `spawn ffmpeg ENOENT` variants — render preflighted only `ffmpeg`, never `ffprobe`, and all spawns used bare PATH strings with no Windows PATHEXT handling. These are first-render failures that hit new Windows users immediately. ## Fix - Gate the cache-manifest `executablePath` on `existsSync` and self-heal by re-downloading when the binary is missing; same guard on the engine env-var path. - New shared environment preflight (`packages/cli/src/browser/preflight.ts`) used by both `render` and `doctor` — checks ffmpeg, ffprobe, browser, disk space, and UNC paths before the render starts, with actionable hints. - Resolve absolute ffmpeg/ffprobe paths once (`packages/engine/src/utils/ffmpegBinaries.ts`) and pass them to every engine spawn instead of relying on PATH. - Map opaque Windows ffmpeg exit codes to actionable messages. ## Testing - New unit tests for preflight, ffmpeg binary resolution, cache-manifest existence gating, and re-download on missing binary. - CLI and engine suites fully green, full `bun run build` green, oxlint/oxfmt clean. - Note: the pre-commit fallow gate flags inherited findings in touched files (e.g. `audioExtractor.ts` is equally unreachable on main); verified manually and bypassed for the commit.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
// fallow-ignore-file code-duplication
|
||||
import { execFileSync } from "child_process";
|
||||
import { existsSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
|
||||
export const FFMPEG_PATH_ENV = "HYPERFRAMES_FFMPEG_PATH";
|
||||
export const FFPROBE_PATH_ENV = "HYPERFRAMES_FFPROBE_PATH";
|
||||
|
||||
const pathCache = new Map<string, string | undefined>();
|
||||
|
||||
function findOnPath(name: "ffmpeg" | "ffprobe"): string | undefined {
|
||||
if (pathCache.has(name)) return pathCache.get(name);
|
||||
try {
|
||||
const command = process.platform === "win32" ? "where" : "which";
|
||||
const output = execFileSync(command, [name], {
|
||||
encoding: "utf-8",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
timeout: 5000,
|
||||
});
|
||||
const first = output
|
||||
.split(/\r?\n/)
|
||||
.map((s) => s.trim())
|
||||
.find(Boolean);
|
||||
const resolved = first ? resolve(first) : undefined;
|
||||
pathCache.set(name, resolved);
|
||||
return resolved;
|
||||
} catch {
|
||||
pathCache.set(name, undefined);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function getConfiguredBinary(envName: string, binaryName: "ffmpeg" | "ffprobe"): string {
|
||||
const configured = process.env[envName]?.trim();
|
||||
if (configured) return resolve(configured);
|
||||
return findOnPath(binaryName) ?? binaryName;
|
||||
}
|
||||
|
||||
export function getFfmpegBinary(): string {
|
||||
return getConfiguredBinary(FFMPEG_PATH_ENV, "ffmpeg");
|
||||
}
|
||||
|
||||
export function getFfprobeBinary(): string {
|
||||
return getConfiguredBinary(FFPROBE_PATH_ENV, "ffprobe");
|
||||
}
|
||||
|
||||
export function assertConfiguredFfmpegBinariesExist(): void {
|
||||
const ffmpegPath = process.env[FFMPEG_PATH_ENV]?.trim();
|
||||
if (ffmpegPath && !existsSync(ffmpegPath)) {
|
||||
throw new Error(
|
||||
`[FFmpeg] FFmpeg binary not found at ${FFMPEG_PATH_ENV}="${ffmpegPath}". ` +
|
||||
"Install FFmpeg or unset the override.",
|
||||
);
|
||||
}
|
||||
|
||||
const ffprobePath = process.env[FFPROBE_PATH_ENV]?.trim();
|
||||
if (ffprobePath && !existsSync(ffprobePath)) {
|
||||
throw new Error(
|
||||
`[FFmpeg] FFprobe binary not found at ${FFPROBE_PATH_ENV}="${ffprobePath}". ` +
|
||||
"Install FFmpeg or unset the override.",
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user