amical/packages/whisper-wrapper/src/loader.ts

106 lines
2.7 KiB
TypeScript

import path from "node:path";
import fs from "node:fs";
const GPU_FIRST_CANDIDATES = ["metal", "openblas", "cuda"] as const;
function candidateDirs(platform: string, arch: string): string[] {
return [
...GPU_FIRST_CANDIDATES.map((tag) => `${platform}-${arch}-${tag}`),
`${platform}-${arch}`,
"cpu-fallback",
];
}
function bindingPathFor(dir: string): string {
return path.join(__dirname, "..", "native", dir, "whisper.node");
}
function isLoadableError(error: unknown): boolean {
return (
!!error &&
typeof error === "object" &&
"code" in error &&
(error as NodeJS.ErrnoException).code === "ERR_DLOPEN_FAILED"
);
}
export function resolveBinding(): string {
const { platform, arch } = process;
for (const dir of candidateDirs(platform, arch)) {
const candidate = bindingPathFor(dir);
if (fs.existsSync(candidate)) {
return candidate;
}
}
throw new Error(
`No suitable whisper.node binary found for ${platform}-${arch}`,
);
}
let loadedBindingInfo: { path: string; type: string } | null = null;
export function getLoadedBindingInfo(): { path: string; type: string } | null {
return loadedBindingInfo;
}
export function loadBinding(): any {
const { platform, arch } = process;
const attempted: string[] = [];
let lastLoadError: unknown = null;
for (const dir of candidateDirs(platform, arch)) {
const candidate = bindingPathFor(dir);
if (!fs.existsSync(candidate)) {
continue;
}
attempted.push(candidate);
try {
const mod = require(candidate);
if (attempted.length > 1) {
console.warn(
`[whisper-wrapper] loaded fallback binary: ${candidate} (attempted ${attempted.length} candidates)`,
);
}
// Store the loaded binding info
const bindingType = dir.includes("-cuda")
? "cuda"
: dir.includes("-metal")
? "metal"
: dir.includes("-openblas")
? "openblas"
: dir === "cpu-fallback"
? "cpu-fallback"
: "cpu";
loadedBindingInfo = {
path: candidate,
type: bindingType,
};
return mod;
} catch (error) {
if (isLoadableError(error)) {
console.warn(
`[whisper-wrapper] failed to load ${candidate}: ${(error as Error).message}. Trying next candidate...`,
);
lastLoadError = error;
continue;
}
throw error;
}
}
if (lastLoadError) {
const error = new Error(
`Unable to load whisper.node for ${platform}-${arch}. Attempted: ${attempted.join(", ")}`,
{ cause: lastLoadError },
);
throw error;
}
throw new Error(
`No suitable whisper.node binary found for ${platform}-${arch}`,
);
}