UNPKG

aivox

Version:

A lightweight CLI tool for translating voice to text using Whisper, seamlessly piping the transcribed text to any Unix-like command for versatile integration.

174 lines (170 loc) 6.4 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; 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 __async = (__this, __arguments, generator) => { return new Promise((resolve, 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 ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/worker.ts var import_node_wav = require("node-wav"); var import_node_record_lpcm16 = __toESM(require("node-record-lpcm16")); var import_wav_headers = __toESM(require("wav-headers")); var import_smart_whisper = require("smart-whisper"); // src/download_model.ts var import_node_fs = require("fs"); var import_node_path = require("path"); var src = "https://huggingface.co/ggerganov/whisper.cpp"; var pfx = "resolve/main/ggml"; var model = "base.en"; var downloadModel = (modelsPath = "./models") => __async(void 0, null, function* () { const modelFileName = `ggml-${model}.bin`; const modelFilePath = (0, import_node_path.join)(modelsPath, modelFileName); if (!(0, import_node_fs.existsSync)(modelsPath)) { (0, import_node_fs.mkdirSync)(modelsPath, { recursive: true }); } if ((0, import_node_fs.existsSync)(modelFilePath)) { return; } const url = `${src}/${pfx}-${model}.bin`; try { const response = yield fetch(url); if (!response.ok) { throw new Error(`Failed to download ggml model ${model}`); } const arrayBuffer = yield response.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); const fileStream = (0, import_node_fs.createWriteStream)(modelFilePath); fileStream.write(buffer); fileStream.end(); } catch (error) { console.error(error.message); console.error( "Please try again later or download the original Whisper model files and convert them yourself." ); process.exit(1); } }); var download_model_default = downloadModel; // src/worker.ts var import_node_os = require("os"); var import_node_path2 = require("path"); var import_node_process = require("process"); var import_node_readline = __toESM(require("readline")); function main() { return __async(this, null, function* () { const homeDir = (0, import_node_os.homedir)(); const directoryName = ".aivox/models"; const fullPath = (0, import_node_path2.join)(homeDir, directoryName); const dl = download_model_default(fullPath); const model2 = (0, import_node_path2.join)(fullPath, "ggml-base.en.bin"); const recording = import_node_record_lpcm16.default.record({ sampleRate: 16e3, channels: 1, encoding: "pcm16" }); const chunks = []; recording.stream().on("data", (chunk) => chunks.push(chunk)); import_node_readline.default.emitKeypressEvents(process.stdin); if (process.stdin.isTTY) { process.stdin.setRawMode(true); } let recordingFinished = false; const stopRecordingAndProcess = () => __async(this, null, function* () { if (recordingFinished) return; recordingFinished = true; recording.stop(); const rawData = Buffer.concat(chunks); if (rawData.length < 16e3) { console.log("Not enough audio data recorded. Please try again and speak for a longer duration."); (0, import_node_process.exit)(0); } const header = (0, import_wav_headers.default)({ sampleRate: 16e3, channels: 1, bitDepth: 16, dataLength: rawData.length }); const wavFile = Buffer.concat([header, rawData]); try { yield dl; const whisper = new import_smart_whisper.Whisper(model2, { gpu: true }); const pcm = readWavFromBuffer(wavFile); const task = yield whisper.transcribe(pcm, { language: "en", print_special: false, print_progress: false, print_realtime: false, print_timestamps: false }); const result = yield task.result; if (result && result.length > 0 && result[0].text) { console.log(result[0].text); } else { console.log("No transcription result. The audio might be too short or silent."); } yield whisper.free(); (0, import_node_process.exit)(0); } catch (error) { console.error("Error during transcription:", error); (0, import_node_process.exit)(1); } }); const timeoutId = setTimeout(stopRecordingAndProcess, 5e3); process.stdin.on("keypress", (str, key) => { if (key.name === "space" || key.ctrl && key.name === "c") { clearTimeout(timeoutId); stopRecordingAndProcess(); } }); }); } function readWavFromBuffer(buffer) { const { sampleRate, channelData } = (0, import_node_wav.decode)(buffer); if (sampleRate !== 16e3) { throw new Error(`Invalid sample rate: ${sampleRate}`); } if (channelData.length !== 1) { throw new Error(`Invalid channel count: ${channelData.length}`); } return channelData[0]; } main().catch((error) => { console.error("Error:", error); process.exit(1); });