fix(render): diagnose unlaunchable Windows FFmpeg (#2430)

This commit is contained in:
Miguel Ángel
2026-07-14 12:55:46 -04:00
committed by GitHub
parent fb0d823500
commit 3d7e26aabf
4 changed files with 84 additions and 7 deletions
@@ -4,6 +4,13 @@ import { parseToolVersion, runEnvironmentChecks } from "./preflight.js";
import * as manager from "./manager.js";
import * as linuxDeps from "./linuxDeps.js";
const execFileSync = vi.hoisted(() => vi.fn());
vi.mock("node:child_process", async (importOriginal) => ({
...(await importOriginal<typeof import("node:child_process")>()),
execFileSync,
}));
describe("runEnvironmentChecks", () => {
const originalFfmpegPath = process.env.HYPERFRAMES_FFMPEG_PATH;
const originalFfprobePath = process.env.HYPERFRAMES_FFPROBE_PATH;
@@ -11,6 +18,7 @@ describe("runEnvironmentChecks", () => {
beforeEach(() => {
process.env.HYPERFRAMES_FFMPEG_PATH = process.execPath;
process.env.HYPERFRAMES_FFPROBE_PATH = process.execPath;
execFileSync.mockReturnValue("ffmpeg version 7.1.1\n");
});
afterEach(() => {
@@ -57,6 +65,29 @@ describe("runEnvironmentChecks", () => {
});
});
it("blocks rendering when the selected FFmpeg binary cannot launch", async () => {
execFileSync.mockImplementation((binaryPath: string) => {
if (binaryPath !== process.env.HYPERFRAMES_FFMPEG_PATH) return "ffprobe version 7.1.1\n";
throw Object.assign(new Error("Command failed with exit code 3221225781"), {
status: 3221225781,
});
});
const result = await runEnvironmentChecks();
const ffmpeg = result.outcomes.find((outcome) => outcome.name === "FFmpeg");
expect(ffmpeg).toMatchObject({
ok: false,
level: "error",
title: "FFmpeg cannot start",
path: process.execPath,
});
expect(ffmpeg?.detail).toContain(process.execPath);
expect(ffmpeg?.detail).toContain("3221225781");
expect(ffmpeg?.hint).toContain("working 64-bit FFmpeg build");
expect(result.ffmpegPath).toBeUndefined();
});
it("validates an explicit browser path without needing browser discovery", async () => {
const result = await runEnvironmentChecks({
includeBrowser: true,
+28 -7
View File
@@ -55,15 +55,23 @@ function configuredMissingDetail(envName: string): string | undefined {
return `Configured path does not exist: ${envName}="${configured}"`;
}
function readToolVersion(binaryPath: string): string {
type ToolVersionResult = { ok: true; detail: string } | { ok: false; detail: string };
function readToolVersion(binaryPath: string): ToolVersionResult {
try {
const raw =
execFileSync(binaryPath, ["-version"], { encoding: "utf-8", timeout: 5000 }).split("\n")[0] ??
"";
const version = parseToolVersion(raw);
return version ? `${version} at ${binaryPath}` : binaryPath;
} catch {
return binaryPath;
return { ok: true, detail: version ? `${version} at ${binaryPath}` : binaryPath };
} catch (error) {
const status =
typeof error === "object" && error !== null && "status" in error ? error.status : undefined;
const exitDetail = typeof status === "number" ? ` (exit code ${status})` : "";
return {
ok: false,
detail: `Failed to run "${binaryPath}" -version${exitDetail}.`,
};
}
}
@@ -82,7 +90,19 @@ function checkFFmpeg(): EnvironmentCheckOutcome {
const path = findFFmpeg();
if (path) {
return { name: "FFmpeg", ok: true, level: "ok", detail: readToolVersion(path), path };
const version = readToolVersion(path);
if (!version.ok) {
return {
name: "FFmpeg",
ok: false,
level: "error",
title: "FFmpeg cannot start",
detail: version.detail,
hint: "Install a working 64-bit FFmpeg build with all required runtime DLLs.",
path,
};
}
return { name: "FFmpeg", ok: true, level: "ok", detail: version.detail, path };
}
return {
@@ -110,7 +130,8 @@ function checkFFprobe(): EnvironmentCheckOutcome {
const path = findFFprobe();
if (path) {
return { name: "FFprobe", ok: true, level: "ok", detail: readToolVersion(path), path };
const version = readToolVersion(path);
return { name: "FFprobe", ok: true, level: "ok", detail: version.detail, path };
}
return {
@@ -270,7 +291,7 @@ export async function runEnvironmentChecks(
return {
outcomes,
...(ffmpeg.path ? { ffmpegPath: ffmpeg.path } : {}),
...(ffmpeg.ok && ffmpeg.path ? { ffmpegPath: ffmpeg.path } : {}),
...(ffprobe.path ? { ffprobePath: ffprobe.path } : {}),
...(browser ? { browser } : {}),
};