UNPKG

smart-whisper-electron

Version:

Whisper.cpp Node.js binding with auto model offloading strategy.

373 lines (365 loc) 12.1 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropNames = Object.getOwnPropertyNames; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __async = (__this, __arguments, generator) => { return new Promise((resolve2, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve2(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/index.ts var src_exports = {}; __export(src_exports, { Binding: () => Binding, TranscribeTask: () => TranscribeTask, Whisper: () => Whisper, WhisperAligmentHeadsPreset: () => WhisperAligmentHeadsPreset, WhisperModel: () => WhisperModel, WhisperSamplingStrategy: () => WhisperSamplingStrategy, binding: () => binding, manager: () => model_manager_exports }); module.exports = __toCommonJS(src_exports); // src/binding.ts var import_node_path = __toESM(require("path")); process.env.GGML_METAL_PATH_RESOURCES = process.env.GGML_METAL_PATH_RESOURCES || import_node_path.default.join(__dirname, "../whisper.cpp/ggml/src"); var module2 = require(import_node_path.default.join(__dirname, "../build/Release/smart-whisper")); var WhisperAligmentHeadsPreset = /* @__PURE__ */ ((WhisperAligmentHeadsPreset2) => { WhisperAligmentHeadsPreset2[WhisperAligmentHeadsPreset2["NONE"] = 0] = "NONE"; return WhisperAligmentHeadsPreset2; })(WhisperAligmentHeadsPreset || {}); var Binding; ((Binding2) => { })(Binding || (Binding = {})); var binding = module2; // src/model.ts var WhisperModel = class extends binding.WhisperModel { }; // src/transcribe.ts var import_node_events = __toESM(require("events")); var TranscribeTask = class _TranscribeTask extends import_node_events.default { /** * You should not construct this class directly, use {@link TranscribeTask.run} instead. */ constructor(model) { super(); this._result = null; this._model = model; } get model() { return this._model; } /** * A promise that resolves to the result of the transcription task. */ get result() { if (this._result === null) { throw new Error("Task has not been started"); } return this._result; } _run(pcm, params) { return __async(this, null, function* () { return new Promise((resolve2) => { const handle = this.model.handle; if (!handle) { throw new Error("Model has been freed"); } binding.transcribe( handle, pcm, params, (results) => { this.emit("finish"); resolve2(results); }, (result) => { this.emit("transcribed", result); } ); }); }); } static run(model, pcm, params) { return __async(this, null, function* () { if (model.freed) { throw new Error("Model has been freed"); } const task = new _TranscribeTask(model); task._result = task._run(pcm, params); return task; }); } on(event, listener) { return super.on(event, listener); } once(event, listener) { return super.once(event, listener); } off(event, listener) { return super.off(event, listener); } }; // src/types.ts var WhisperSamplingStrategy = /* @__PURE__ */ ((WhisperSamplingStrategy2) => { WhisperSamplingStrategy2[WhisperSamplingStrategy2["WHISPER_SAMPLING_GREEDY"] = 0] = "WHISPER_SAMPLING_GREEDY"; WhisperSamplingStrategy2[WhisperSamplingStrategy2["WHISPER_SAMPLING_BEAM_SEARCH"] = 1] = "WHISPER_SAMPLING_BEAM_SEARCH"; return WhisperSamplingStrategy2; })(WhisperSamplingStrategy || {}); // src/whisper.ts var Whisper = class { /** * Constructs a new Whisper instance with a specified model file and configuration. * @param file - The path to the Whisper model file. * @param config - Optional configuration for the Whisper instance. */ constructor(file, config = {}) { this._available = null; this._loading = null; this._tasks = []; this._offload_timer = null; this._file = file; this._config = __spreadValues({ offload: 300, gpu: true }, config); } get file() { return this._file; } set file(file) { this._file = file; } get config() { return this._config; } get tasks() { return this._tasks; } reset_offload_timer() { this.clear_offload_timer(); this._offload_timer = setTimeout(() => { this.free(); }, this.config.offload * 1e3); } clear_offload_timer() { if (this._offload_timer !== null) { clearTimeout(this._offload_timer); this._offload_timer = null; } } model() { return __async(this, null, function* () { if (this._available === null) { return this.load(); } this.reset_offload_timer(); return Promise.resolve(this._available); }); } /** * Loads the whisper model asynchronously. * If the model is already being loaded, returns the existing one. * * You don't need to call this method directly, it's called automatically if necessary when you call {@link Whisper.transcribe}. * * @returns A Promise that resolves to the loaded model. */ load() { return __async(this, null, function* () { if (this._loading !== null) { return this._loading; } const params = __spreadValues({ use_gpu: this._config.gpu }, this._config.params); const model = WhisperModel.load(this.file, __spreadProps(__spreadValues({}, this._config), { params })); this._loading = model; this._available = yield model; this._loading = null; this.reset_offload_timer(); return this._available; }); } /** * Transcribes the given PCM audio data using the Whisper model. * @param pcm - The mono 16k PCM audio data to transcribe. * @param params - Optional parameters for transcription. * @returns A promise that resolves to the result of the transcription task. */ transcribe(_0) { return __async(this, arguments, function* (pcm, params = {}) { const model = yield this.model(); const task = yield TranscribeTask.run(model, pcm, params); this._tasks.push(task.result); return task; }); } free() { return __async(this, null, function* () { if (this._available === null) { return; } const model = this._available; this._available = null; this.clear_offload_timer(); yield Promise.all(this.tasks); yield model.free(); }); } }; // src/model-manager/index.ts var model_manager_exports = {}; __export(model_manager_exports, { MODELS: () => MODELS, check: () => check, dir: () => dir, download: () => download, list: () => list, remove: () => remove, resolve: () => resolve }); var import_node_path2 = __toESM(require("path")); var import_node_fs = __toESM(require("fs")); var import_node_os = __toESM(require("os")); var import_node_stream = require("stream"); var root = import_node_path2.default.join(import_node_os.default.homedir(), ".smart-whisper"); var models = import_node_path2.default.join(root, "models"); var ext = ".bin"; import_node_fs.default.mkdirSync(models, { recursive: true }); var BASE_MODELS_URL = "https://huggingface.co/ggerganov/whisper.cpp/resolve/main"; var MODELS = { tiny: `${BASE_MODELS_URL}/ggml-tiny.bin`, "tiny.en": `${BASE_MODELS_URL}/ggml-tiny.en.bin`, small: `${BASE_MODELS_URL}/ggml-small.bin`, "small.en": `${BASE_MODELS_URL}/ggml-small.en.bin`, base: `${BASE_MODELS_URL}/ggml-base.bin`, "base.en": `${BASE_MODELS_URL}/ggml-base.en.bin`, medium: `${BASE_MODELS_URL}/ggml-medium.bin`, "medium.en": `${BASE_MODELS_URL}/ggml-medium.en.bin`, "large-v1": `${BASE_MODELS_URL}/ggml-large-v1.bin`, "large-v2": `${BASE_MODELS_URL}/ggml-large-v2.bin`, "large-v3": `${BASE_MODELS_URL}/ggml-large-v3.bin`, "large-v3-turbo": `${BASE_MODELS_URL}/ggml-large-v3-turbo.bin` }; function download(model) { return __async(this, null, function* () { var _a; let url = "", name = ""; if (model in MODELS) { url = MODELS[model]; name = model; } else { try { url = new URL(model).href; name = (_a = new URL(url).pathname.split("/").pop()) != null ? _a : ""; } catch (e) { } } if (!url) { throw new Error(`Invalid model URL or shorthand: ${model}`); } if (!name) { throw new Error(`Failed to parse model name: ${url}`); } if (check(name)) { return name; } const res = yield fetch(url); if (!res.ok || !res.body) { throw new Error(`Failed to download model: ${res.statusText}`); } const stream = import_node_fs.default.createWriteStream(import_node_path2.default.join(models, name.endsWith(ext) ? name : name + ext)); import_node_stream.Readable.fromWeb(res.body).pipe(stream); return new Promise((resolve2) => stream.on("finish", () => resolve2(name))); }); } function remove(model) { if (check(model)) { import_node_fs.default.unlinkSync(import_node_path2.default.join(models, model + ext)); } } function list() { const files = import_node_fs.default.readdirSync(models).filter((file) => file.endsWith(ext)); return files.map((file) => file.slice(0, -ext.length)); } function check(model) { return import_node_fs.default.existsSync(import_node_path2.default.join(models, model + ext)); } function resolve(model) { if (check(model)) { return import_node_path2.default.join(models, model + ext); } else { throw new Error(`Model not found: ${model}`); } } var dir = { root, models }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Binding, TranscribeTask, Whisper, WhisperAligmentHeadsPreset, WhisperModel, WhisperSamplingStrategy, binding, manager });