mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 23:00:03 +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:
@@ -35,6 +35,7 @@ import {
|
||||
import { fetchRemoteTemplate } from "../templates/remote.js";
|
||||
import { trackInitTemplate } from "../telemetry/events.js";
|
||||
import { hasFFmpeg } from "../whisper/manager.js";
|
||||
import { findFFmpeg, findFFprobe, getFFmpegInstallHint } from "../browser/ffmpeg.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import {
|
||||
CANVAS_DIMENSIONS,
|
||||
@@ -75,8 +76,10 @@ const TAILWIND_BROWSER_INTEGRITY =
|
||||
|
||||
function probeVideo(filePath: string): VideoMeta | undefined {
|
||||
try {
|
||||
const ffprobePath = findFFprobe();
|
||||
if (!ffprobePath) return undefined;
|
||||
const raw = execFileSync(
|
||||
"ffprobe",
|
||||
ffprobePath,
|
||||
["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", filePath],
|
||||
{ encoding: "utf-8", timeout: 15_000 },
|
||||
);
|
||||
@@ -134,8 +137,13 @@ function isWebCompatible(codec: string): boolean {
|
||||
|
||||
function transcodeToMp4(inputPath: string, outputPath: string): Promise<boolean> {
|
||||
return new Promise((resolvePromise) => {
|
||||
const ffmpegPath = findFFmpeg();
|
||||
if (!ffmpegPath) {
|
||||
resolvePromise(false);
|
||||
return;
|
||||
}
|
||||
const child = spawn(
|
||||
"ffmpeg",
|
||||
ffmpegPath,
|
||||
[
|
||||
"-i",
|
||||
inputPath,
|
||||
@@ -345,8 +353,7 @@ async function handleVideoFile(
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const msg =
|
||||
"ffprobe not found — using defaults (1920x1080, 5s, 30fps). Install: brew install ffmpeg";
|
||||
const msg = `ffprobe not found — using defaults (1920x1080, 5s, 30fps). Install: ${getFFmpegInstallHint()}`;
|
||||
if (interactive) {
|
||||
clack.log.warn(msg);
|
||||
} else {
|
||||
@@ -409,10 +416,10 @@ async function handleVideoFile(
|
||||
} else {
|
||||
if (interactive) {
|
||||
clack.log.warn(c.dim("ffmpeg not installed — cannot transcode."));
|
||||
clack.log.info(c.accent("Install: brew install ffmpeg"));
|
||||
clack.log.info(c.accent(`Install: ${getFFmpegInstallHint()}`));
|
||||
} else {
|
||||
console.log(c.warn("ffmpeg not installed — cannot transcode. Copying original."));
|
||||
console.log(c.dim("Install: ") + c.accent("brew install ffmpeg"));
|
||||
console.log(c.dim("Install: ") + c.accent(getFFmpegInstallHint()));
|
||||
}
|
||||
copyFileSync(videoPath, resolve(destDir, localVideoName));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user