UNPKG

voice-to-text-converter

Version:

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

502 lines 20.7 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.GoogleCloudEngine = 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")); /** * Google Cloud Speech-to-Text engine */ class GoogleCloudEngine extends base_engine_1.BaseSpeechRecognitionEngine { constructor(apiKey, projectId) { super(); this.client = null; this.apiKey = null; this.projectId = null; this.streamingRecognizeStream = null; this.apiKey = apiKey || null; this.projectId = projectId || null; } /** * Check if Google Cloud Speech is available */ isAvailable() { try { require.resolve('@google-cloud/speech'); return true; } catch { return false; } } /** * Get supported languages */ getSupportedLanguages() { return [ 'af-ZA', 'am-ET', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-IL', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-OM', 'ar-PS', 'ar-QA', 'ar-SA', 'ar-SY', 'ar-TN', 'ar-YE', 'az-AZ', 'bg-BG', 'bn-BD', 'bn-IN', 'bs-BA', 'ca-ES', 'cs-CZ', 'da-DK', 'de-AT', 'de-CH', 'de-DE', 'el-GR', 'en-AU', 'en-CA', 'en-GB', 'en-GH', 'en-HK', 'en-IE', 'en-IN', 'en-KE', 'en-NG', 'en-NZ', 'en-PH', 'en-PK', 'en-SG', 'en-TZ', 'en-US', 'en-ZA', 'es-AR', 'es-BO', 'es-CL', 'es-CO', 'es-CR', 'es-DO', 'es-EC', 'es-ES', 'es-GT', 'es-HN', 'es-MX', 'es-NI', 'es-PA', 'es-PE', 'es-PR', 'es-PY', 'es-SV', 'es-UY', 'es-VE', 'et-EE', 'eu-ES', 'fa-IR', 'fi-FI', 'fil-PH', 'fr-BE', 'fr-CA', 'fr-CH', 'fr-FR', 'gl-ES', 'gu-IN', 'he-IL', 'hi-IN', 'hr-HR', 'hu-HU', 'hy-AM', 'id-ID', 'is-IS', 'it-CH', 'it-IT', 'ja-JP', 'jv-ID', 'ka-GE', 'kk-KZ', 'km-KH', 'kn-IN', 'ko-KR', 'lo-LA', 'lt-LT', 'lv-LV', 'mk-MK', 'ml-IN', 'mn-MN', 'mr-IN', 'ms-MY', 'mt-MT', 'my-MM', 'ne-NP', 'nl-BE', 'nl-NL', 'no-NO', 'pa-Guru-IN', 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', 'ru-RU', 'si-LK', 'sk-SK', 'sl-SI', 'sq-AL', 'sr-RS', 'su-ID', 'sv-SE', 'sw-KE', 'sw-TZ', 'ta-IN', 'ta-LK', 'ta-MY', 'ta-SG', 'te-IN', 'th-TH', 'tr-TR', 'uk-UA', 'ur-IN', 'ur-PK', 'uz-UZ', 'vi-VN', 'yue-Hant-HK', 'zh-CN', 'zh-TW', 'zu-ZA' ]; } /** * Initialize Google Cloud Speech client */ async initializeClient() { if (!this.isAvailable()) { throw new Error('Google Cloud Speech module is not installed. Install it with: npm install @google-cloud/speech'); } try { const speech = require('@google-cloud/speech'); const clientOptions = {}; if (this.apiKey) { clientOptions.apiKey = this.apiKey; } if (this.projectId) { clientOptions.projectId = this.projectId; } this.client = new speech.SpeechClient(clientOptions); } catch (error) { throw new Error(`Failed to initialize Google Cloud Speech client: ${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.initializeClient(); if (!this.client) { throw new Error('Google Cloud Speech client not initialized'); } this.setRecordingState(true); this.emit('start'); if (audioConfig.source === 'microphone') { await this.startStreamingRecognition(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 Google Cloud Speech recognition: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined); } } /** * Start streaming recognition for microphone input */ async startStreamingRecognition(audioConfig) { if (!this.client) { throw new Error('Client not initialized'); } try { const request = { config: this.buildSpeechConfig(), interimResults: this.config.interimResults || false, }; this.streamingRecognizeStream = this.client.streamingRecognize() .on('error', (error) => { this.emitError(types_1.SpeechRecognitionErrorType.ENGINE_ERROR, `Streaming recognition error: ${error.message}`, error); }) .on('data', (data) => { if (data.results && data.results.length > 0) { const result = data.results[0]; const alternative = result.alternatives[0]; if (alternative) { const speechResult = { transcript: alternative.transcript, confidence: alternative.confidence || 1.0, isFinal: result.isFinal, alternatives: result.alternatives?.map((alt) => ({ transcript: alt.transcript, confidence: alt.confidence || 1.0 })) }; this.emitResult(speechResult); } } }); // Write initial config this.streamingRecognizeStream.write(request); // Set up 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', (chunk) => { if (this.streamingRecognizeStream && this.isRecording) { this.streamingRecognizeStream.write({ audioContent: chunk }); } }); 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); } recording.stream().on('end', () => { if (this.streamingRecognizeStream) { this.streamingRecognizeStream.end(); } this.emit('audioend'); this.emit('end'); this.setRecordingState(false); }); } catch (error) { this.emitError(types_1.SpeechRecognitionErrorType.AUDIO_CAPTURE, `Failed to start streaming recognition: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined); } } /** * Process audio file */ async processAudioFile(filePath) { if (!this.client) { throw new Error('Client not initialized'); } try { (0, audio_1.validateAudioFile)(filePath); const audioBytes = fs.readFileSync(filePath).toString('base64'); const request = { audio: { content: audioBytes, }, config: this.buildSpeechConfig(), }; this.emit('audiostart'); const [response] = await this.client.recognize(request); if (response.results) { for (const result of response.results) { const alternative = result.alternatives[0]; if (alternative) { const speechResult = { transcript: alternative.transcript, confidence: alternative.confidence || 1.0, isFinal: true, alternatives: result.alternatives?.map((alt) => ({ transcript: alt.transcript, confidence: alt.confidence || 1.0 })) }; this.emitResult(speechResult); } } } this.emit('audioend'); this.emit('end'); this.setRecordingState(false); } 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) { if (!this.client) { throw new Error('Client not initialized'); } return new Promise((resolve, reject) => { const chunks = []; stream.on('data', (chunk) => { chunks.push(chunk); }); stream.on('end', async () => { try { const audioBuffer = Buffer.concat(chunks); const audioBytes = audioBuffer.toString('base64'); const request = { audio: { content: audioBytes, }, config: this.buildSpeechConfig(), }; this.emit('audiostart'); const [response] = await this.client.recognize(request); if (response.results) { for (const result of response.results) { const alternative = result.alternatives[0]; if (alternative) { const speechResult = { transcript: alternative.transcript, confidence: alternative.confidence || 1.0, isFinal: true, alternatives: result.alternatives?.map((alt) => ({ transcript: alt.transcript, confidence: alt.confidence || 1.0 })) }; this.emitResult(speechResult); } } } this.emit('audioend'); this.emit('end'); this.setRecordingState(false); resolve(); } catch (error) { this.emitError(types_1.SpeechRecognitionErrorType.ENGINE_ERROR, `Failed to process audio stream: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error ? error : undefined); reject(error); } }); stream.on('error', (error) => { this.emitError(types_1.SpeechRecognitionErrorType.ENGINE_ERROR, `Stream processing error: ${error.message}`, error); reject(error); }); }); } /** * Build Google Cloud Speech configuration */ buildSpeechConfig() { return { encoding: this.mapEncodingFormat(this.config.encoding || 'LINEAR16'), sampleRateHertz: this.config.sampleRate || 16000, languageCode: this.config.language || 'en-US', maxAlternatives: this.config.maxAlternatives || 1, profanityFilter: false, enableWordTimeOffsets: true, enableAutomaticPunctuation: true, model: 'latest_long', useEnhanced: true }; } /** * Map encoding format to Google Cloud format */ mapEncodingFormat(encoding) { const encodingMap = { 'LINEAR16': 'LINEAR16', 'FLAC': 'FLAC', 'MULAW': 'MULAW', 'AMR': 'AMR', 'AMR_WB': 'AMR_WB', 'OGG_OPUS': 'OGG_OPUS', 'SPEEX_WITH_HEADER_BYTE': 'SPEEX_WITH_HEADER_BYTE' }; return encodingMap[encoding] || 'LINEAR16'; } /** * Stop speech recognition */ async stop() { this.setRecordingState(false); if (this.streamingRecognizeStream) { this.streamingRecognizeStream.end(); this.streamingRecognizeStream = null; } } /** * Abort speech recognition */ async abort() { this.setRecordingState(false); if (this.streamingRecognizeStream) { this.streamingRecognizeStream.destroy(); this.streamingRecognizeStream = null; } this.cleanup(); } /** * Process audio file directly */ async processFile(filePath, config) { await this.initializeClient(); if (!this.client) { throw new Error('Google Cloud Speech client not initialized'); } const results = []; const mergedConfig = this.getConfig(config); try { (0, audio_1.validateAudioFile)(filePath); const audioBytes = fs.readFileSync(filePath).toString('base64'); const request = { audio: { content: audioBytes, }, config: { encoding: this.mapEncodingFormat(mergedConfig.encoding || 'LINEAR16'), sampleRateHertz: mergedConfig.sampleRate || 16000, languageCode: mergedConfig.language || 'en-US', maxAlternatives: mergedConfig.maxAlternatives || 1, profanityFilter: false, enableWordTimeOffsets: true, enableAutomaticPunctuation: true, model: 'latest_long', useEnhanced: true }, }; const [response] = await this.client.recognize(request); if (response.results) { for (const result of response.results) { const alternative = result.alternatives[0]; if (alternative) { results.push({ transcript: alternative.transcript, confidence: alternative.confidence || 1.0, isFinal: true, alternatives: result.alternatives?.map((alt) => ({ transcript: alt.transcript, confidence: alt.confidence || 1.0 })) }); } } } return results; } catch (error) { throw new Error(`Failed to process file: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Process audio stream directly */ async processStream(stream, config) { await this.initializeClient(); if (!this.client) { throw new Error('Google Cloud Speech client not initialized'); } const results = []; const mergedConfig = this.getConfig(config); return new Promise((resolve, reject) => { const chunks = []; stream.on('data', (chunk) => { chunks.push(chunk); }); stream.on('end', async () => { try { const audioBuffer = Buffer.concat(chunks); const audioBytes = audioBuffer.toString('base64'); const request = { audio: { content: audioBytes, }, config: { encoding: this.mapEncodingFormat(mergedConfig.encoding || 'LINEAR16'), sampleRateHertz: mergedConfig.sampleRate || 16000, languageCode: mergedConfig.language || 'en-US', maxAlternatives: mergedConfig.maxAlternatives || 1, profanityFilter: false, enableWordTimeOffsets: true, enableAutomaticPunctuation: true, model: 'latest_long', useEnhanced: true }, }; const [response] = await this.client.recognize(request); if (response.results) { for (const result of response.results) { const alternative = result.alternatives[0]; if (alternative) { results.push({ transcript: alternative.transcript, confidence: alternative.confidence || 1.0, isFinal: true, alternatives: result.alternatives?.map((alt) => ({ transcript: alt.transcript, confidence: alt.confidence || 1.0 })) }); } } } resolve(results); } catch (error) { reject(error); } }); stream.on('error', reject); }); } /** * Set API credentials */ setCredentials(apiKey, projectId) { this.apiKey = apiKey; if (projectId) { this.projectId = projectId; } this.client = null; // Reset client to reinitialize with new credentials } /** * Clean up resources */ cleanup() { if (this.streamingRecognizeStream) { this.streamingRecognizeStream.destroy(); this.streamingRecognizeStream = null; } super.cleanup(); } } exports.GoogleCloudEngine = GoogleCloudEngine; //# sourceMappingURL=google-cloud-engine.js.map