UNPKG

@micdrop/gladia

Version:

Gladia implementation for @micdrop/server

179 lines (177 loc) 6.41 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 __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); // src/index.ts var index_exports = {}; __export(index_exports, { GladiaSTT: () => GladiaSTT }); module.exports = __toCommonJS(index_exports); // src/GladiaSTT.ts var import_server = require("@micdrop/server"); var import_ws = __toESM(require("ws")); var SAMPLE_RATE = 16e3; var BIT_DEPTH = 16; var GladiaSTT = class extends import_server.STT { // Store audio chunks to send them again if reconnecting constructor(options) { super(); this.options = options; this.audioChunksPending = []; // Register real-time transcription to get a WebSocket URL this.getURL = async () => { const response = await fetch("https://api.gladia.io/v2/live", { method: "POST", headers: { "Content-Type": "application/json", "X-Gladia-Key": this.options.apiKey }, body: JSON.stringify({ encoding: "wav/pcm", sample_rate: SAMPLE_RATE, bit_depth: BIT_DEPTH, channels: 1, messages_config: { receive_final_transcripts: true, receive_speech_events: false, receive_pre_processing_events: false, receive_realtime_processing_events: false, receive_post_processing_events: false, receive_acknowledgments: false, receive_errors: true, receive_lifecycle_events: false }, ...this.options.settings }) }); if (!response.ok) { throw new Error( `${response.status}: ${await response.text() || response.statusText}` ); } const { url } = await response.json(); return url; }; // Connect to Gladia this.initWS = async (url) => { return new Promise((resolve, reject) => { const socket = new import_ws.default(url); this.socket = socket; socket.addEventListener("open", () => { this.log("Connection opened"); resolve(); }); socket.addEventListener("error", (error) => { reject(error); }); socket.addEventListener("close", ({ code, reason }) => { this.socket?.removeAllListeners(); this.socket = void 0; if (code !== 1e3) { this.reconnect(); } else { this.log("Connection closed", { code, reason }); } }); socket.addEventListener("message", (event) => { const message = JSON.parse(event.data.toString()); if (message.type === "transcript" && message.data.is_final) { const transcript = message.data.utterance.text; this.log(`Received transcript: "${transcript}"`); this.emit("Transcript", transcript); this.audioChunksPending.length = 0; } }); }); }; this.initPromise = this.getURL().then(this.initWS).catch((error) => { console.error("[GladiaSTT] Connection error:", error); }); } transcribe(audioStream) { const pcmStream = (0, import_server.convertToPCM)(audioStream, SAMPLE_RATE, BIT_DEPTH); pcmStream.on("data", async (chunk) => { await this.initPromise; this.audioChunksPending.push(chunk); this.socket?.send(chunk); this.log(`Sent audio chunk (${chunk.byteLength} bytes)`); }); pcmStream.on("end", async () => { if (this.audioChunksPending.length === 0) return; await this.initPromise; this.sendSilence(1); }); } destroy() { super.destroy(); if (this.reconnectTimeout) { clearTimeout(this.reconnectTimeout); this.reconnectTimeout = void 0; } this.socket?.removeAllListeners(); if (this.socket?.readyState === import_ws.default.OPEN) { this.socket?.close(1e3); } this.socket = void 0; } reconnect() { this.initPromise = new Promise((resolve, reject) => { this.log("Reconnecting..."); this.reconnectTimeout = setTimeout(() => { this.reconnectTimeout = void 0; this.getURL().then(this.initWS).then(() => { if (this.audioChunksPending.length > 0) { this.log("Sending audio chunks again"); this.audioChunksPending.forEach( (chunk) => this.socket?.send(chunk) ); } }).then(resolve).catch((error) => { this.log("Reconnection error:", error); reject(error); }); }, 1e3); }); } sendSilence(durationSeconds) { if (!this.socket) return; const numSamples = Math.round(SAMPLE_RATE * durationSeconds); const bytesPerSample = BIT_DEPTH / 8; const silenceBuffer = Buffer.alloc(numSamples * bytesPerSample, 0); this.socket.send(silenceBuffer); this.audioChunksPending.push(silenceBuffer); this.log( `Sent ${durationSeconds * 1e3}ms of silence (${silenceBuffer.byteLength} bytes) after stream end` ); } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { GladiaSTT }); //# sourceMappingURL=index.js.map