smart-whisper-electron
Version:
Whisper.cpp Node.js binding with auto model offloading strategy.
373 lines (365 loc) • 12.1 kB
JavaScript
;
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
});