mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 09:16:55 +00:00
fix(cli): Apple Silicon Macs without ffmpeg get working arm64 ffprobe and ffmpeg
This commit is contained in:
@@ -69,7 +69,6 @@
|
||||
"hyperframes-localize-fonts": "./bin/hyperframes-localize-fonts.mjs",
|
||||
},
|
||||
"dependencies": {
|
||||
"undici": "7.25.0",
|
||||
"@hono/node-server": "^2.0.5",
|
||||
"@puppeteer/browsers": "^3.2.2",
|
||||
"acorn": "^8.17.0",
|
||||
@@ -86,6 +85,7 @@
|
||||
"open": "^10.0.0",
|
||||
"postcss": "^8.5.8",
|
||||
"puppeteer-core": "^25.10.0",
|
||||
"undici": "7.25.0",
|
||||
"yauzl": "^3.4.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -114,6 +114,8 @@
|
||||
"vitest": "^4.1.11",
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||
"@ffprobe-installer/ffprobe": "^2.1.2",
|
||||
"sharp": "^0.35.0",
|
||||
},
|
||||
},
|
||||
@@ -166,7 +168,6 @@
|
||||
"name": "@hyperframes/engine",
|
||||
"version": "0.8.210",
|
||||
"dependencies": {
|
||||
"undici": "7.25.0",
|
||||
"@hono/node-server": "^2.0.5",
|
||||
"@hyperframes/core": "workspace:^",
|
||||
"@hyperframes/parsers": "workspace:^",
|
||||
@@ -174,6 +175,7 @@
|
||||
"linkedom": "^0.18.12",
|
||||
"puppeteer": "~25.11.0",
|
||||
"puppeteer-core": "^25.11.0",
|
||||
"undici": "7.25.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^25.0.10",
|
||||
@@ -699,6 +701,42 @@
|
||||
|
||||
"@fallow-cli/win32-x64-msvc": ["@fallow-cli/win32-x64-msvc@3.34.1", "", { "os": "win32", "cpu": "x64" }, "sha512-rdjchbPiFK5ExxyZnj50FN6+n+KYTtbLrtcEegVg+iL97LvLml9sVvF1YcDP69fWDJVAhAwlE2mQZhW/P2HPyw=="],
|
||||
|
||||
"@ffmpeg-installer/darwin-arm64": ["@ffmpeg-installer/darwin-arm64@4.1.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hYqTiP63mXz7wSQfuqfFwfLOfwwFChUedeCVKkBtl/cliaTM7/ePI9bVzfZ2c+dWu3TqCwLDRWNSJ5pqZl8otA=="],
|
||||
|
||||
"@ffmpeg-installer/darwin-x64": ["@ffmpeg-installer/darwin-x64@4.1.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z4EyG3cIFjdhlY8wI9aLUXuH8nVt7E9SlMVZtWvSPnm2sm37/yC2CwjUzyCQbJbySnef1tQwGG2Sx+uWhd9IAw=="],
|
||||
|
||||
"@ffmpeg-installer/ffmpeg": ["@ffmpeg-installer/ffmpeg@1.1.0", "", { "optionalDependencies": { "@ffmpeg-installer/darwin-arm64": "4.1.5", "@ffmpeg-installer/darwin-x64": "4.1.0", "@ffmpeg-installer/linux-arm": "4.1.3", "@ffmpeg-installer/linux-arm64": "4.1.4", "@ffmpeg-installer/linux-ia32": "4.1.0", "@ffmpeg-installer/linux-x64": "4.1.0", "@ffmpeg-installer/win32-ia32": "4.1.0", "@ffmpeg-installer/win32-x64": "4.1.0" } }, "sha512-Uq4rmwkdGxIa9A6Bd/VqqYbT7zqh1GrT5/rFwCwKM70b42W5gIjWeVETq6SdcL0zXqDtY081Ws/iJWhr1+xvQg=="],
|
||||
|
||||
"@ffmpeg-installer/linux-arm": ["@ffmpeg-installer/linux-arm@4.1.3", "", { "os": "linux", "cpu": "arm" }, "sha512-NDf5V6l8AfzZ8WzUGZ5mV8O/xMzRag2ETR6+TlGIsMHp81agx51cqpPItXPib/nAZYmo55Bl2L6/WOMI3A5YRg=="],
|
||||
|
||||
"@ffmpeg-installer/linux-arm64": ["@ffmpeg-installer/linux-arm64@4.1.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-dljEqAOD0oIM6O6DxBW9US/FkvqvQwgJ2lGHOwHDDwu/pX8+V0YsDL1xqHbj1DMX/+nP9rxw7G7gcUvGspSoKg=="],
|
||||
|
||||
"@ffmpeg-installer/linux-ia32": ["@ffmpeg-installer/linux-ia32@4.1.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-0LWyFQnPf+Ij9GQGD034hS6A90URNu9HCtQ5cTqo5MxOEc7Rd8gLXrJvn++UmxhU0J5RyRE9KRYstdCVUjkNOQ=="],
|
||||
|
||||
"@ffmpeg-installer/linux-x64": ["@ffmpeg-installer/linux-x64@4.1.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Y5BWhGLU/WpQjOArNIgXD3z5mxxdV8c41C+U15nsE5yF8tVcdCGet5zPs5Zy3Ta6bU7haGpIzryutqCGQA/W8A=="],
|
||||
|
||||
"@ffmpeg-installer/win32-ia32": ["@ffmpeg-installer/win32-ia32@4.1.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV2D7RlaZv/lrtdhaQ4oETwoFUsUjlUiasiZLDxhEUPdNDWcH1OU9K1xTvqz+OXLdsmYelUDuBS/zkMOTtlUAw=="],
|
||||
|
||||
"@ffmpeg-installer/win32-x64": ["@ffmpeg-installer/win32-x64@4.1.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Drt5u2vzDnIONf4ZEkKtFlbvwj6rI3kxw1Ck9fpudmtgaZIHD4ucsWB2lCZBXRxJgXR+2IMSti+4rtM4C4rXgg=="],
|
||||
|
||||
"@ffprobe-installer/darwin-arm64": ["@ffprobe-installer/darwin-arm64@5.0.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vwNCNjokH8hfkbl6m95zICHwkSzhEvDC3GVBcUp5HX8+4wsX10SP3B+bGur7XUzTIZ4cQpgJmEIAx6TUwRepMg=="],
|
||||
|
||||
"@ffprobe-installer/darwin-x64": ["@ffprobe-installer/darwin-x64@5.1.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-J+YGscZMpQclFg31O4cfVRGmDpkVsQ2fZujoUdMAAYcP0NtqpC49Hs3SWJpBdsGB4VeqOt5TTm1vSZQzs1NkhA=="],
|
||||
|
||||
"@ffprobe-installer/ffprobe": ["@ffprobe-installer/ffprobe@2.1.2", "", { "optionalDependencies": { "@ffprobe-installer/darwin-arm64": "5.0.1", "@ffprobe-installer/darwin-x64": "5.1.0", "@ffprobe-installer/linux-arm": "5.2.0", "@ffprobe-installer/linux-arm64": "5.2.0", "@ffprobe-installer/linux-ia32": "5.2.0", "@ffprobe-installer/linux-x64": "5.2.0", "@ffprobe-installer/win32-ia32": "5.1.0", "@ffprobe-installer/win32-x64": "5.1.0" } }, "sha512-ZNvwk4f2magF42Zji2Ese16SMj9BS7Fui4kRjg6gTYTxY3gWZNpg85n4MIfQyI9nimHg4x/gT6FVkp/bBDuBwg=="],
|
||||
|
||||
"@ffprobe-installer/linux-arm": ["@ffprobe-installer/linux-arm@5.2.0", "", { "os": "linux", "cpu": "arm" }, "sha512-PF5HqEhCY7WTWHtLDYbA/+rLS+rhslWvyBlAG1Fk8VzVlnRdl93o6hy7DE2kJgxWQbFaR3ZktPQGEzfkrmQHvQ=="],
|
||||
|
||||
"@ffprobe-installer/linux-arm64": ["@ffprobe-installer/linux-arm64@5.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-X1VvWtlLs6ScP73biVLuHD5ohKJKsMTa0vafCESOen4mOoNeLAYbxOVxDWAdFz9cpZgRiloFj5QD6nDj8E28yQ=="],
|
||||
|
||||
"@ffprobe-installer/linux-ia32": ["@ffprobe-installer/linux-ia32@5.2.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-TFVK5sasXyXhbIG7LtPRDmtkrkOsInwKcL43iEvEw+D9vCS2rc//mn9/0Q+BR0UoJEiMK4+ApYr/3LLVUBPOCQ=="],
|
||||
|
||||
"@ffprobe-installer/linux-x64": ["@ffprobe-installer/linux-x64@5.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-D3UeqTLYPNs7pBWPLUYGehPdRVqU8eACox4OZy3pZUZatxye2YKlvBwEfaLdL1v2Z4FOAlLUhms0kY8m8kqSRA=="],
|
||||
|
||||
"@ffprobe-installer/win32-ia32": ["@ffprobe-installer/win32-ia32@5.1.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-5O3vOoNRxmut0/Nu9vSazTdSHasrr+zPT2B3Hm7kjmO3QVFcIfVImS6ReQnZeSy8JPJOqXts5kX5x/3KOX54XQ=="],
|
||||
|
||||
"@ffprobe-installer/win32-x64": ["@ffprobe-installer/win32-x64@5.1.0", "", { "os": "win32", "cpu": "x64" }, "sha512-jMGYeAgkrdn4e2vvYt/qakgHRE3CPju4bn5TmdPfoAm1BlX1mY9cyMd8gf5vSzI8gH8Zq5WQAyAkmekX/8TSTg=="],
|
||||
|
||||
"@floating-ui/core": ["@floating-ui/core@1.8.0", "", { "dependencies": { "@floating-ui/utils": "^0.2.12" } }, "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ=="],
|
||||
|
||||
"@floating-ui/dom": ["@floating-ui/dom@1.8.0", "", { "dependencies": { "@floating-ui/core": "^1.8.0", "@floating-ui/utils": "^0.2.12" } }, "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg=="],
|
||||
@@ -1477,8 +1515,6 @@
|
||||
|
||||
"data-urls": ["data-urls@7.0.0", "", { "dependencies": { "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.0" } }, "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA=="],
|
||||
|
||||
"date-fns": ["date-fns@2.30.0", "", { "dependencies": { "@babel/runtime": "^7.21.0" } }, "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="],
|
||||
|
||||
@@ -99,6 +99,8 @@
|
||||
"vitest": "^4.1.11"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||
"@ffprobe-installer/ffprobe": "^2.1.2",
|
||||
"sharp": "^0.35.0"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -184,7 +184,7 @@ describe("findFfBinary", () => {
|
||||
|
||||
function installerProject(
|
||||
name: "ffmpeg" | "ffprobe",
|
||||
{ viaInstaller = false, mode = 0o755 } = {},
|
||||
{ viaInstaller = false, mode = 0o755, content = "" as string | Buffer } = {},
|
||||
) {
|
||||
const project = realpathSync(mkdtempSync(join(tmpdir(), "hf-ff-installer-")));
|
||||
const platformPackage = `@${name}-installer/${process.platform}-${process.arch}`;
|
||||
@@ -205,7 +205,7 @@ describe("findFfBinary", () => {
|
||||
);
|
||||
}
|
||||
const binary = join(packageDir, process.platform === "win32" ? `${name}.exe` : name);
|
||||
writeFileSync(binary, "");
|
||||
writeFileSync(binary, content);
|
||||
chmodSync(binary, mode);
|
||||
return { project, binary };
|
||||
}
|
||||
@@ -279,6 +279,29 @@ describe("findFfBinary", () => {
|
||||
},
|
||||
);
|
||||
|
||||
describe.skipIf(process.platform === "win32")("on Apple Silicon without Rosetta", () => {
|
||||
const machO = (cpu: number) => Buffer.from([0xcf, 0xfa, 0xed, 0xfe, cpu, 0x00, 0x00, 0x01]);
|
||||
|
||||
function asAppleSilicon() {
|
||||
Object.defineProperty(process, "platform", { value: "darwin", configurable: true });
|
||||
vi.spyOn(process, "arch", "get").mockReturnValue("arm64");
|
||||
}
|
||||
|
||||
it("skips an x86_64 installer binary", async () => {
|
||||
asAppleSilicon();
|
||||
const { project } = installerProject("ffprobe", { content: machO(0x07) });
|
||||
|
||||
expect(await findInProjectDir(project, "ffprobe")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps an arm64 installer binary", async () => {
|
||||
asAppleSilicon();
|
||||
const { project, binary } = installerProject("ffprobe", { content: machO(0x0c) });
|
||||
|
||||
expect(await findInProjectDir(project, "ffprobe")).toBe(binary);
|
||||
});
|
||||
});
|
||||
|
||||
it("returns undefined when the binary is nowhere, and caches the miss until cleared", async () => {
|
||||
delete process.env.HYPERFRAMES_FFMPEG_PATH;
|
||||
Object.defineProperty(process, "platform", { value: "linux", configurable: true });
|
||||
@@ -345,3 +368,25 @@ describe("findFfBinary", () => {
|
||||
expect(execFileSync).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isX86OnlyMachO", () => {
|
||||
function fixture(bytes: number[]): string {
|
||||
const file = join(realpathSync(mkdtempSync(join(tmpdir(), "hf-macho-"))), "ffprobe");
|
||||
writeFileSync(file, Buffer.from(bytes));
|
||||
onTestFinished(() => rmSync(file, { force: true }));
|
||||
return file;
|
||||
}
|
||||
|
||||
it("flags a 64-bit x86_64 Mach-O (the ffprobe-static darwin/arm64 payload)", async () => {
|
||||
const { isX86OnlyMachO } = await importFresh();
|
||||
expect(isX86OnlyMachO(fixture([0xcf, 0xfa, 0xed, 0xfe, 0x07, 0x00, 0x00, 0x01]))).toBe(true);
|
||||
});
|
||||
|
||||
it("passes an arm64 Mach-O, a universal binary, a script and a missing file", async () => {
|
||||
const { isX86OnlyMachO } = await importFresh();
|
||||
expect(isX86OnlyMachO(fixture([0xcf, 0xfa, 0xed, 0xfe, 0x0c, 0x00, 0x00, 0x01]))).toBe(false);
|
||||
expect(isX86OnlyMachO(fixture([0xca, 0xfe, 0xba, 0xbe, 0x00, 0x00, 0x00, 0x02]))).toBe(false);
|
||||
expect(isX86OnlyMachO(fixture([0x23, 0x21, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x73]))).toBe(false);
|
||||
expect(isX86OnlyMachO("/nonexistent/ffprobe")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { accessSync, constants, existsSync } from "node:fs";
|
||||
import { accessSync, closeSync, constants, existsSync, openSync, readSync } from "node:fs";
|
||||
import { createRequire } from "node:module";
|
||||
import { delimiter, dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
/**
|
||||
* Shared FFmpeg/FFprobe binary resolution for every package that shells out
|
||||
@@ -41,11 +42,39 @@ function chooseBestPathCandidate(
|
||||
);
|
||||
}
|
||||
|
||||
const MACHO_CPU_X86_64 = 0x01000007;
|
||||
const ROSETTA_RUNTIME = "/Library/Apple/usr/libexec/oah/libRosettaRuntime";
|
||||
|
||||
// A Mach-O built only for x86_64 (some npm packages ship one under darwin/arm64). A universal
|
||||
// binary or an unreadable file is not one.
|
||||
export function isX86OnlyMachO(file: string): boolean {
|
||||
const header = Buffer.alloc(8);
|
||||
try {
|
||||
const fd = openSync(file, "r");
|
||||
try {
|
||||
readSync(fd, header, 0, 8, 0);
|
||||
} finally {
|
||||
closeSync(fd);
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return header.readUInt32LE(0) === 0xfeedfacf && header.readUInt32LE(4) === MACHO_CPU_X86_64;
|
||||
}
|
||||
|
||||
// Without Rosetta an Apple Silicon Mac cannot run an x86_64 binary, so it must not be chosen.
|
||||
function runsOnThisMac(candidate: string): boolean {
|
||||
if (process.platform !== "darwin" || process.arch !== "arm64" || existsSync(ROSETTA_RUNTIME)) {
|
||||
return true;
|
||||
}
|
||||
return !isX86OnlyMachO(candidate);
|
||||
}
|
||||
|
||||
function isExecutablePathCandidate(candidate: string): boolean {
|
||||
if (process.platform === "win32") return existsSync(candidate);
|
||||
try {
|
||||
accessSync(candidate, constants.X_OK);
|
||||
return true;
|
||||
return runsOnThisMac(candidate);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
@@ -95,7 +124,7 @@ const COMMON_BIN_DIRS =
|
||||
function findInCommonDirsOrInstallerPackage(name: FfBinaryName): string | undefined {
|
||||
for (const dir of COMMON_BIN_DIRS) {
|
||||
const candidate = `${dir}/${name}`;
|
||||
if (existsSync(candidate)) return candidate;
|
||||
if (existsSync(candidate) && runsOnThisMac(candidate)) return candidate;
|
||||
}
|
||||
return findInInstallerPackage(name);
|
||||
}
|
||||
@@ -117,10 +146,17 @@ function tryResolve(fromFile: string, request: string): string | undefined {
|
||||
}
|
||||
}
|
||||
|
||||
// An @ffmpeg-installer / @ffprobe-installer package reachable from the current folder, located
|
||||
// but never loaded. The platform package is resolved via the installer package first (pnpm layout).
|
||||
// An @ffmpeg-installer / @ffprobe-installer package reachable from the current folder or from
|
||||
// this package's own install (the CLI's optional dependencies), located but never loaded. The
|
||||
// platform package is resolved via the installer package first (pnpm layout).
|
||||
function findInInstallerPackage(name: FfBinaryName): string | undefined {
|
||||
const project = resolve("package.json");
|
||||
return (
|
||||
findInInstallerPackageFrom(name, resolve("package.json")) ??
|
||||
findInInstallerPackageFrom(name, fileURLToPath(import.meta.url))
|
||||
);
|
||||
}
|
||||
|
||||
function findInInstallerPackageFrom(name: FfBinaryName, project: string): string | undefined {
|
||||
const platformManifest = `@${name}-installer/${process.platform}-${process.arch}/package.json`;
|
||||
const installer = tryResolve(project, `@${name}-installer/${name}/package.json`);
|
||||
const manifest =
|
||||
|
||||
Reference in New Issue
Block a user