UNPKG

voice-to-text-converter

Version:

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

417 lines 14.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.VoiceToText = void 0; const events_1 = require("events"); const types_1 = require("./types"); const engines_1 = require("./engines"); const audio_1 = require("./utils/audio"); /** * Main VoiceToText converter class */ class VoiceToText extends events_1.EventEmitter { constructor(options = {}) { super(); this.currentEngine = null; this.isInitialized = false; this.options = { enableFallback: true, enginePriority: ['web-speech', 'vosk', 'google-cloud'], debug: false, ...options }; if (this.options.debug) { console.log('VoiceToText initialized with options:', this.options); } } /** * Initialize the voice-to-text converter */ async initialize() { if (this.isInitialized) { return; } try { // Select and initialize the best available engine const engine = await this.selectEngine(); if (!engine) { throw new Error('No speech recognition engines are available'); } this.currentEngine = engine; this.setupEngineEvents(); this.isInitialized = true; if (this.options.debug) { console.log('VoiceToText initialized successfully'); } } catch (error) { throw new Error(`Failed to initialize VoiceToText: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Convert speech from microphone to text */ async fromMicrophone(options = {}) { await this.ensureInitialized(); const audioConfig = { source: 'microphone', duration: options.duration, deviceId: options.deviceId }; const recognitionConfig = { sampleRate: options.sampleRate || this.options.defaultRecognitionConfig?.sampleRate, continuous: true, interimResults: true, ...this.options.defaultRecognitionConfig }; await this.currentEngine.start(audioConfig, recognitionConfig); } /** * Convert speech from audio file to text */ async fromFile(filePath, config) { await this.ensureInitialized(); try { (0, audio_1.validateAudioFile)(filePath); const mergedConfig = { ...this.options.defaultRecognitionConfig, ...config }; return await this.currentEngine.processFile(filePath, mergedConfig); } catch (error) { throw new Error(`Failed to process file: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Convert speech from audio stream to text */ async fromStream(stream, config) { await this.ensureInitialized(); try { const mergedConfig = { ...this.options.defaultRecognitionConfig, ...config }; return await this.currentEngine.processStream(stream, mergedConfig); } catch (error) { throw new Error(`Failed to process stream: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Start continuous speech recognition */ async startListening(audioConfig, config) { await this.ensureInitialized(); const mergedConfig = { continuous: true, interimResults: true, ...this.options.defaultRecognitionConfig, ...config }; await this.currentEngine.start(audioConfig, mergedConfig); } /** * Stop speech recognition */ async stopListening() { if (this.currentEngine && this.currentEngine.isActive) { await this.currentEngine.stop(); } } /** * Abort speech recognition */ async abort() { if (this.currentEngine) { await this.currentEngine.abort(); } } /** * Check if currently listening/recording */ get isListening() { return this.currentEngine?.isActive || false; } /** * Get current engine information */ getCurrentEngine() { if (!this.currentEngine) { return null; } // Determine engine type (simplified approach) const engineType = this.currentEngine.constructor.name.toLowerCase().replace('engine', ''); return { type: engineType, available: this.currentEngine.isAvailable(), capabilities: (0, engines_1.getEngineCapabilities)(engineType) }; } /** * Switch to a different engine */ async switchEngine(engineConfig) { if (this.currentEngine?.isActive) { await this.currentEngine.stop(); } try { const newEngine = (0, engines_1.createEngine)(engineConfig); if (!newEngine.isAvailable()) { throw new Error(`Engine ${engineConfig.engine} is not available`); } this.currentEngine = newEngine; this.setupEngineEvents(); if (this.options.debug) { console.log(`Switched to engine: ${engineConfig.engine}`); } } catch (error) { throw new Error(`Failed to switch engine: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Get available engines in current environment */ static getAvailableEngines() { return (0, engines_1.getAvailableEngines)(); } /** * Check if a specific engine is available */ static isEngineAvailable(engineType) { return (0, engines_1.isEngineAvailable)(engineType); } /** * Get engine capabilities */ static getEngineCapabilities(engineType) { return (0, engines_1.getEngineCapabilities)(engineType); } /** * Get browser support information */ static getBrowserSupport() { if (typeof window === 'undefined') { return { webSpeechAPI: false, mediaRecorder: false, getUserMedia: false, browser: { name: 'Node.js', version: process.version } }; } const webSpeechAPI = !!(window.SpeechRecognition || window.webkitSpeechRecognition); const mediaRecorder = !!window.MediaRecorder; const getUserMedia = !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia); // Simple browser detection const userAgent = navigator.userAgent; let browserName = 'Unknown'; let browserVersion = 'Unknown'; if (userAgent.includes('Chrome')) { browserName = 'Chrome'; const match = userAgent.match(/Chrome\/(\d+)/); browserVersion = match ? match[1] : 'Unknown'; } else if (userAgent.includes('Firefox')) { browserName = 'Firefox'; const match = userAgent.match(/Firefox\/(\d+)/); browserVersion = match ? match[1] : 'Unknown'; } else if (userAgent.includes('Safari')) { browserName = 'Safari'; const match = userAgent.match(/Version\/(\d+)/); browserVersion = match ? match[1] : 'Unknown'; } else if (userAgent.includes('Edge')) { browserName = 'Edge'; const match = userAgent.match(/Edge\/(\d+)/); browserVersion = match ? match[1] : 'Unknown'; } return { webSpeechAPI, mediaRecorder, getUserMedia, browser: { name: browserName, version: browserVersion } }; } /** * Create a quick instance for one-time use */ static async quickTranscribe(source, options = {}) { const converter = new VoiceToText({ defaultEngine: options.engine ? { engine: options.engine } : undefined, defaultRecognitionConfig: { language: options.language, ...options.config } }); await converter.initialize(); try { if (source === 'microphone') { return new Promise((resolve, reject) => { const results = []; converter.on('result', (result) => { if (result.isFinal) { results.push(result); } }); converter.on('end', () => resolve(results)); converter.on('error', reject); converter.fromMicrophone({ duration: options.duration || 5000 }).catch(reject); }); } else if (typeof source === 'string') { return await converter.fromFile(source, options.config); } else { return await converter.fromStream(source, options.config); } } finally { await converter.cleanup(); } } /** * Clean up resources */ async cleanup() { if (this.currentEngine) { if (this.currentEngine.isActive) { await this.currentEngine.abort(); } this.currentEngine.removeAllListeners(); } this.removeAllListeners(); this.isInitialized = false; this.currentEngine = null; } /** * Ensure the converter is initialized */ async ensureInitialized() { if (!this.isInitialized) { await this.initialize(); } } /** * Select the best available engine */ async selectEngine() { // If a default engine is specified, try it first if (this.options.defaultEngine) { try { const engine = (0, engines_1.createEngine)(this.options.defaultEngine); if (engine.isAvailable()) { return engine; } } catch (error) { if (this.options.debug) { console.warn(`Default engine ${this.options.defaultEngine.engine} failed:`, error); } } } // Try engines in priority order const availableEngines = (0, engines_1.getAvailableEngines)(); const enginePriority = this.options.enginePriority || ['web-speech', 'vosk', 'google-cloud']; for (const engineType of enginePriority) { if (availableEngines.includes(engineType)) { try { const engineConfig = { engine: engineType }; // Add default configuration for specific engines if (engineType === 'google-cloud' && this.options.defaultEngine?.apiKey) { engineConfig.apiKey = this.options.defaultEngine.apiKey; engineConfig.projectId = this.options.defaultEngine.projectId; } else if (engineType === 'vosk' && this.options.defaultEngine?.modelPath) { engineConfig.modelPath = this.options.defaultEngine.modelPath; } const engine = (0, engines_1.createEngine)(engineConfig); if (engine.isAvailable()) { if (this.options.debug) { console.log(`Selected engine: ${engineType}`); } return engine; } } catch (error) { if (this.options.debug) { console.warn(`Engine ${engineType} failed:`, error); } } } } return null; } /** * Set up event forwarding from engine to main class */ setupEngineEvents() { if (!this.currentEngine) { return; } // Forward all events from engine this.currentEngine.on('start', () => this.emit('start')); this.currentEngine.on('end', () => this.emit('end')); this.currentEngine.on('result', (result) => this.emit('result', result)); this.currentEngine.on('error', (error) => { if (this.options.enableFallback && !this.isListening) { this.handleEngineFallback(error).catch(fallbackError => { this.emit('error', fallbackError); }); } else { this.emit('error', error); } }); this.currentEngine.on('audiostart', () => this.emit('audiostart')); this.currentEngine.on('audioend', () => this.emit('audioend')); this.currentEngine.on('soundstart', () => this.emit('soundstart')); this.currentEngine.on('soundend', () => this.emit('soundend')); this.currentEngine.on('speechstart', () => this.emit('speechstart')); this.currentEngine.on('speechend', () => this.emit('speechend')); } /** * Handle engine fallback on error */ async handleEngineFallback(error) { if (this.options.debug) { console.log('Attempting engine fallback due to error:', error.message); } const currentEngineType = this.getCurrentEngine()?.type; const availableEngines = (0, engines_1.getAvailableEngines)(); const remainingEngines = availableEngines.filter(engine => engine !== currentEngineType); for (const engineType of remainingEngines) { try { await this.switchEngine({ engine: engineType }); if (this.options.debug) { console.log(`Successfully switched to fallback engine: ${engineType}`); } return; } catch (fallbackError) { if (this.options.debug) { console.warn(`Fallback engine ${engineType} also failed:`, fallbackError); } } } throw new types_1.SpeechRecognitionError(types_1.SpeechRecognitionErrorType.ENGINE_ERROR, `All engines failed. Original error: ${error.message}`, error); } /** * Type-safe event emitter methods */ emit(event, ...args) { return super.emit(event, ...args); } on(event, listener) { return super.on(event, listener); } once(event, listener) { return super.once(event, listener); } off(event, listener) { return super.off(event, listener); } } exports.VoiceToText = VoiceToText; //# sourceMappingURL=voice-to-text.js.map