UNPKG

voice-to-text-converter

Version:

A modern, lightweight Node.js package for speech-to-text conversion with support for multiple engines

459 lines 17.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.VoskEngine = void 0; const base_engine_1 = require("./base-engine"); const types_1 = require("../types"); const audio_1 = require("../utils/audio"); const fs = __importStar(require("fs")); const path = __importStar(require("path")); /** * Vosk engine for offline speech recognition */ class VoskEngine extends base_engine_1.BaseSpeechRecognitionEngine { constructor(modelPath) { super(); this.vosk = null; this.model = null; this.recognizer = null; this.modelPath = null; this.modelPath = modelPath || null; } /** * Check if Vosk is available */ isAvailable() { try { // Try to require vosk module require.resolve('vosk'); return true; } catch { return false; } } /** * Get supported languages based on available models */ getSupportedLanguages() { // This would typically scan available model directories // For now, return common languages that have Vosk models return [ 'en-US', 'en-GB', 'en-IN', 'es-ES', 'es-MX', 'fr-FR', 'de-DE', 'it-IT', 'pt-BR', 'ru-RU', 'ja-JP', 'ko-KR', 'zh-CN', 'ar-SA', 'hi-IN', 'tr-TR', 'pl-PL', 'nl-NL', 'sv-SE', 'da-DK', 'no-NO', 'fi-FI' ]; } /** * Initialize Vosk module and model */ async initializeVosk(modelPath) { if (!this.isAvailable()) { throw new Error('Vosk module is not installed. Install it with: npm install vosk'); } try { // Dynamic import of vosk module this.vosk = require('vosk'); this.vosk.setLogLevel(-1); // Disable logging // Use provided model path or default const resolvedModelPath = modelPath || this.modelPath; if (!resolvedModelPath) { throw new Error('Model path must be provided for Vosk engine'); } if (!fs.existsSync(resolvedModelPath)) { throw new Error(`Vosk model not found at: ${resolvedModelPath}`); } // Load model this.model = new this.vosk.Model(resolvedModelPath); } catch (error) { throw new Error(`Failed to initialize Vosk: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Start speech recognition */ async start(audioConfig, recognitionConfig) { this.validateAudioConfig(audioConfig); this.config = this.getConfig(recognitionConfig); this.validateConfig(this.config); if (this.isRecording) { await this.stop(); } try { await this.initializeVosk(); if (!this.model || !this.vosk) { throw new Error('Vosk not properly initialized'); } // Create recognizer this.recognizer = new this.vosk.KaldiRecognizer(this.model, this.config.sampleRate || 16000); this.setRecordingState(true); this.emit('start'); if (audioConfig.source === 'microphone') { await this.startMicrophoneRecognition(audioConfig); } else if (audioConfig.source === 'file') { await this.processAudioFile(audioConfig.filePath); } else if (audioConfig.source === 'stream') { await this.processAudioStream(audioConfig.audioStream); } } catch (error) { this.emitError(types_1.SpeechRecognitionErrorType.ENGINE_ERROR, `Failed to start Vosk recognition: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined); } } /** * Start microphone recognition */ async startMicrophoneRecognition(audioConfig) { try { // Use node-record-lpcm16 for microphone recording const recorder = require('node-record-lpcm16'); const recordingOptions = { sampleRate: this.config.sampleRate || 16000, channels: 1, audioType: 'raw', device: audioConfig.deviceId || null }; const recording = recorder.record(recordingOptions); this.emit('audiostart'); recording.stream().on('data', (data) => { if (this.recognizer && this.isRecording) { const hasResult = this.recognizer.acceptWaveform(data); if (hasResult) { const result = JSON.parse(this.recognizer.result()); if (result.text) { this.emitResult({ transcript: result.text, confidence: result.confidence || 1.0, isFinal: true }); } } else if (this.config.interimResults) { const partialResult = JSON.parse(this.recognizer.partialResult()); if (partialResult.partial) { this.emitResult({ transcript: partialResult.partial, confidence: 0.5, isFinal: false }); } } } }); recording.stream().on('error', (error) => { this.emitError(types_1.SpeechRecognitionErrorType.AUDIO_CAPTURE, `Microphone recording error: ${error.message}`, error); }); // Stop recording after specified duration if (audioConfig.duration) { setTimeout(() => { recording.stop(); this.stop().catch(console.error); }, audioConfig.duration); } // Handle recording end recording.stream().on('end', () => { if (this.recognizer) { const finalResult = JSON.parse(this.recognizer.finalResult()); if (finalResult.text) { this.emitResult({ transcript: finalResult.text, confidence: finalResult.confidence || 1.0, isFinal: true }); } } this.emit('audioend'); this.emit('end'); this.setRecordingState(false); }); } catch (error) { this.emitError(types_1.SpeechRecognitionErrorType.AUDIO_CAPTURE, `Failed to start microphone recording: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined); } } /** * Process audio file */ async processAudioFile(filePath) { try { (0, audio_1.validateAudioFile)(filePath); const stream = (0, audio_1.createAudioFileStream)(filePath); await this.processAudioStream(stream); } catch (error) { this.emitError(types_1.SpeechRecognitionErrorType.ENGINE_ERROR, `Failed to process audio file: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined); } } /** * Process audio stream */ async processAudioStream(stream) { return new Promise((resolve, reject) => { if (!this.recognizer) { reject(new Error('Recognizer not initialized')); return; } this.emit('audiostart'); stream.on('data', (chunk) => { if (this.recognizer && this.isRecording) { const hasResult = this.recognizer.acceptWaveform(chunk); if (hasResult) { const result = JSON.parse(this.recognizer.result()); if (result.text) { this.emitResult({ transcript: result.text, confidence: result.confidence || 1.0, isFinal: true }); } } else if (this.config.interimResults) { const partialResult = JSON.parse(this.recognizer.partialResult()); if (partialResult.partial) { this.emitResult({ transcript: partialResult.partial, confidence: 0.5, isFinal: false }); } } } }); stream.on('end', () => { if (this.recognizer) { const finalResult = JSON.parse(this.recognizer.finalResult()); if (finalResult.text) { this.emitResult({ transcript: finalResult.text, confidence: finalResult.confidence || 1.0, isFinal: true }); } } this.emit('audioend'); this.emit('end'); this.setRecordingState(false); resolve(); }); stream.on('error', (error) => { this.emitError(types_1.SpeechRecognitionErrorType.ENGINE_ERROR, `Stream processing error: ${error.message}`, error); reject(error); }); }); } /** * Stop speech recognition */ async stop() { this.setRecordingState(false); if (this.recognizer) { try { // Get final result before stopping const finalResult = JSON.parse(this.recognizer.finalResult()); if (finalResult.text) { this.emitResult({ transcript: finalResult.text, confidence: finalResult.confidence || 1.0, isFinal: true }); } } catch (error) { console.error('Error getting final result:', error); } } } /** * Abort speech recognition */ async abort() { this.setRecordingState(false); this.cleanup(); } /** * Process audio file directly */ async processFile(filePath, config) { const results = []; await this.initializeVosk(); if (!this.model || !this.vosk) { throw new Error('Vosk not properly initialized'); } const mergedConfig = this.getConfig(config); const recognizer = new this.vosk.KaldiRecognizer(this.model, mergedConfig.sampleRate || 16000); try { (0, audio_1.validateAudioFile)(filePath); const stream = (0, audio_1.createAudioFileStream)(filePath); return new Promise((resolve, reject) => { stream.on('data', (chunk) => { const hasResult = recognizer.acceptWaveform(chunk); if (hasResult) { const result = JSON.parse(recognizer.result()); if (result.text) { results.push({ transcript: result.text, confidence: result.confidence || 1.0, isFinal: true }); } } }); stream.on('end', () => { const finalResult = JSON.parse(recognizer.finalResult()); if (finalResult.text) { results.push({ transcript: finalResult.text, confidence: finalResult.confidence || 1.0, isFinal: true }); } recognizer.free(); resolve(results); }); stream.on('error', reject); }); } catch (error) { recognizer.free(); throw error; } } /** * Process audio stream directly */ async processStream(stream, config) { const results = []; await this.initializeVosk(); if (!this.model || !this.vosk) { throw new Error('Vosk not properly initialized'); } const mergedConfig = this.getConfig(config); const recognizer = new this.vosk.KaldiRecognizer(this.model, mergedConfig.sampleRate || 16000); return new Promise((resolve, reject) => { stream.on('data', (chunk) => { const hasResult = recognizer.acceptWaveform(chunk); if (hasResult) { const result = JSON.parse(recognizer.result()); if (result.text) { results.push({ transcript: result.text, confidence: result.confidence || 1.0, isFinal: true }); } } }); stream.on('end', () => { const finalResult = JSON.parse(recognizer.finalResult()); if (finalResult.text) { results.push({ transcript: finalResult.text, confidence: finalResult.confidence || 1.0, isFinal: true }); } recognizer.free(); resolve(results); }); stream.on('error', (error) => { recognizer.free(); reject(error); }); }); } /** * Clean up resources */ cleanup() { if (this.recognizer) { try { this.recognizer.free(); } catch (error) { console.error('Error freeing recognizer:', error); } this.recognizer = null; } this.model = null; this.vosk = null; super.cleanup(); } /** * Set model path */ setModelPath(modelPath) { this.modelPath = modelPath; } /** * Get current model path */ getModelPath() { return this.modelPath; } /** * Download and setup Vosk model (helper method) */ static async downloadModel(language, modelSize = 'small') { // This would implement model downloading logic // For now, just return expected path const modelName = `vosk-model-${language}-${modelSize}`; const modelPath = path.join(process.cwd(), 'models', modelName); if (!fs.existsSync(modelPath)) { throw new Error(`Model not found at ${modelPath}. Please download it manually from https://alphacephei.com/vosk/models`); } return modelPath; } } exports.VoskEngine = VoskEngine; //# sourceMappingURL=vosk-engine.js.map