main.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. """
  2. Speech-to-Text (STT) Plugin for Trixy Application
  3. This plugin demonstrates STT integration with the Trixy plugin system:
  4. - Audio processing and speech recognition
  5. - Integration with various STT engines (OpenAI Whisper, Google Speech, Azure, etc.)
  6. - Real-time audio stream processing
  7. - Confidence scoring and result filtering
  8. - Language detection and multi-language support
  9. - Audio preprocessing (noise reduction, normalization)
  10. - Batch processing and streaming modes
  11. - Result caching and optimization
  12. - Error handling and fallback mechanisms
  13. This serves as a reference for implementing STT functionality in Trixy.
  14. Features:
  15. - Multiple STT engine support
  16. - Real-time audio processing
  17. - Language detection
  18. - Confidence filtering
  19. - Audio preprocessing
  20. - Result caching
  21. - Performance monitoring
  22. """
  23. import os
  24. import time
  25. import threading
  26. import queue
  27. import tempfile
  28. import json
  29. from typing import Any, Dict, Optional, List, Tuple, Union
  30. from pathlib import Path
  31. import numpy as np
  32. # Import plugin base class and event decorator
  33. from trixy_core.plugins import TrixyPlugin
  34. from trixy_core.events import TrixyEvent
  35. def pprint(message: str) -> None:
  36. """STT plugin logging function."""
  37. print(f"[STT_PLUGIN] {message}")
  38. class AudioFormat:
  39. """Audio format specification."""
  40. def __init__(self, sample_rate: int = 16000, channels: int = 1, bit_depth: int = 16):
  41. self.sample_rate = sample_rate
  42. self.channels = channels
  43. self.bit_depth = bit_depth
  44. class STTResult:
  45. """Speech-to-text recognition result."""
  46. def __init__(
  47. self,
  48. text: str,
  49. confidence: float,
  50. language: Optional[str] = None,
  51. processing_time: float = 0.0,
  52. engine: str = "unknown",
  53. alternatives: List[Dict] = None
  54. ):
  55. self.text = text
  56. self.confidence = confidence
  57. self.language = language
  58. self.processing_time = processing_time
  59. self.engine = engine
  60. self.alternatives = alternatives or []
  61. self.timestamp = time.time()
  62. class STTPlugin(TrixyPlugin):
  63. """
  64. Speech-to-Text plugin for Trixy application.
  65. This plugin provides STT functionality with support for:
  66. - Multiple STT engines (OpenAI Whisper, Google Speech, etc.)
  67. - Real-time and batch audio processing
  68. - Language detection and multi-language support
  69. - Audio preprocessing and optimization
  70. - Result caching and confidence filtering
  71. - Performance monitoring and statistics
  72. """
  73. def initialize(self) -> None:
  74. """Initialize the STT plugin."""
  75. pprint("Initializing STT Plugin...")
  76. # Plugin state
  77. self._processing_queue = queue.Queue()
  78. self._processing_thread: Optional[threading.Thread] = None
  79. self._processing_shutdown = threading.Event()
  80. self._active_sessions: Dict[str, Dict] = {}
  81. # Statistics
  82. self._stats = {
  83. 'total_requests': 0,
  84. 'successful_recognitions': 0,
  85. 'failed_recognitions': 0,
  86. 'total_processing_time': 0.0,
  87. 'average_processing_time': 0.0,
  88. 'cache_hits': 0,
  89. 'languages_detected': set(),
  90. 'confidence_distribution': []
  91. }
  92. # Setup configuration
  93. self._setup_configuration()
  94. # Initialize STT engines
  95. self._initialize_engines()
  96. # Setup audio format
  97. self._audio_format = AudioFormat(
  98. sample_rate=self.sample_rate,
  99. channels=self.channels,
  100. bit_depth=self.bit_depth
  101. )
  102. # Start processing thread
  103. self._start_processing_thread()
  104. pprint(f"STT Plugin initialized with engine: {self.primary_engine}")
  105. def _setup_configuration(self) -> None:
  106. """Setup plugin configuration."""
  107. # Engine configuration
  108. self.primary_engine = self.get_config_value("primary_engine", "whisper", str)
  109. self.fallback_engine = self.get_config_value("fallback_engine", "google", str)
  110. self.engines_enabled = self.get_config_value("engines_enabled", ["whisper", "google"], list)
  111. # Audio configuration
  112. self.sample_rate = self.get_config_value("sample_rate", 16000, int)
  113. self.channels = self.get_config_value("channels", 1, int)
  114. self.bit_depth = self.get_config_value("bit_depth", 16, int)
  115. # Processing configuration
  116. self.min_confidence = self.get_config_value("min_confidence", 0.7, float)
  117. self.max_audio_length = self.get_config_value("max_audio_length", 60.0, float)
  118. self.enable_preprocessing = self.get_config_value("enable_preprocessing", True, bool)
  119. self.enable_caching = self.get_config_value("enable_caching", True, bool)
  120. # Language configuration
  121. self.default_language = self.get_config_value("default_language", "en", str)
  122. self.auto_detect_language = self.get_config_value("auto_detect_language", True, bool)
  123. self.supported_languages = self.get_config_value(
  124. "supported_languages",
  125. ["en", "es", "fr", "de", "it", "pt", "ru", "ja", "ko", "zh"],
  126. list
  127. )
  128. # Performance configuration
  129. self.processing_timeout = self.get_config_value("processing_timeout", 30.0, float)
  130. self.max_concurrent_requests = self.get_config_value("max_concurrent_requests", 3, int)
  131. self.enable_streaming = self.get_config_value("enable_streaming", False, bool)
  132. # Validation
  133. if self.min_confidence < 0 or self.min_confidence > 1:
  134. pprint("Warning: min_confidence should be between 0 and 1")
  135. self.min_confidence = max(0, min(1, self.min_confidence))
  136. if self.sample_rate not in [8000, 16000, 22050, 44100, 48000]:
  137. pprint(f"Warning: Unusual sample rate {self.sample_rate}, may cause issues")
  138. def _initialize_engines(self) -> None:
  139. """Initialize available STT engines."""
  140. self._engines = {}
  141. # Initialize Whisper engine
  142. if "whisper" in self.engines_enabled:
  143. self._engines["whisper"] = self._init_whisper_engine()
  144. # Initialize Google Speech engine
  145. if "google" in self.engines_enabled:
  146. self._engines["google"] = self._init_google_engine()
  147. # Initialize Azure Speech engine
  148. if "azure" in self.engines_enabled:
  149. self._engines["azure"] = self._init_azure_engine()
  150. # Initialize custom/local engines
  151. if "custom" in self.engines_enabled:
  152. self._engines["custom"] = self._init_custom_engine()
  153. pprint(f"Initialized {len(self._engines)} STT engines: {list(self._engines.keys())}")
  154. def _init_whisper_engine(self) -> Dict[str, Any]:
  155. """Initialize OpenAI Whisper engine."""
  156. try:
  157. # Try to import whisper (placeholder - actual implementation would import real whisper)
  158. # import whisper
  159. # Whisper configuration
  160. whisper_config = {
  161. 'model_size': self.get_config_value("whisper_model_size", "base", str),
  162. 'device': self.get_config_value("whisper_device", "cpu", str),
  163. 'compute_type': self.get_config_value("whisper_compute_type", "float32", str),
  164. 'beam_size': self.get_config_value("whisper_beam_size", 5, int),
  165. 'temperature': self.get_config_value("whisper_temperature", 0.0, float),
  166. }
  167. pprint(f"Whisper engine configured with model: {whisper_config['model_size']}")
  168. return {
  169. 'name': 'whisper',
  170. 'available': True,
  171. 'config': whisper_config,
  172. 'model': None, # Placeholder - would load actual model
  173. 'supported_formats': ['wav', 'mp3', 'flac', 'm4a'],
  174. 'max_duration': 600 # 10 minutes
  175. }
  176. except ImportError:
  177. pprint("Whisper not available - install openai-whisper package")
  178. return {'name': 'whisper', 'available': False}
  179. def _init_google_engine(self) -> Dict[str, Any]:
  180. """Initialize Google Speech-to-Text engine."""
  181. try:
  182. # Try to import google cloud speech (placeholder)
  183. # from google.cloud import speech
  184. google_config = {
  185. 'credentials_path': self.get_config_value("google_credentials_path", "", str),
  186. 'language_code': self.get_config_value("google_language_code", "en-US", str),
  187. 'alternative_language_codes': self.get_config_value(
  188. "google_alternative_languages",
  189. ["es-ES", "fr-FR", "de-DE"],
  190. list
  191. ),
  192. 'enable_automatic_punctuation': self.get_config_value(
  193. "google_auto_punctuation",
  194. True,
  195. bool
  196. ),
  197. 'enable_speaker_diarization': self.get_config_value(
  198. "google_speaker_diarization",
  199. False,
  200. bool
  201. )
  202. }
  203. pprint("Google Speech engine configured")
  204. return {
  205. 'name': 'google',
  206. 'available': True,
  207. 'config': google_config,
  208. 'client': None, # Placeholder - would initialize actual client
  209. 'supported_formats': ['wav', 'flac'],
  210. 'max_duration': 480 # 8 minutes
  211. }
  212. except ImportError:
  213. pprint("Google Speech not available - install google-cloud-speech package")
  214. return {'name': 'google', 'available': False}
  215. def _init_azure_engine(self) -> Dict[str, Any]:
  216. """Initialize Azure Speech Services engine."""
  217. try:
  218. # Try to import azure speech (placeholder)
  219. # import azure.cognitiveservices.speech as speechsdk
  220. azure_config = {
  221. 'subscription_key': self.get_config_value("azure_subscription_key", "", str),
  222. 'region': self.get_config_value("azure_region", "eastus", str),
  223. 'language': self.get_config_value("azure_language", "en-US", str),
  224. 'endpoint_id': self.get_config_value("azure_endpoint_id", "", str)
  225. }
  226. pprint("Azure Speech engine configured")
  227. return {
  228. 'name': 'azure',
  229. 'available': True,
  230. 'config': azure_config,
  231. 'recognizer': None, # Placeholder
  232. 'supported_formats': ['wav', 'flac'],
  233. 'max_duration': 600
  234. }
  235. except ImportError:
  236. pprint("Azure Speech not available - install azure-cognitiveservices-speech package")
  237. return {'name': 'azure', 'available': False}
  238. def _init_custom_engine(self) -> Dict[str, Any]:
  239. """Initialize custom/local STT engine."""
  240. custom_config = {
  241. 'model_path': self.get_config_value("custom_model_path", "", str),
  242. 'vocab_path': self.get_config_value("custom_vocab_path", "", str),
  243. 'use_gpu': self.get_config_value("custom_use_gpu", False, bool)
  244. }
  245. return {
  246. 'name': 'custom',
  247. 'available': bool(custom_config['model_path']),
  248. 'config': custom_config,
  249. 'model': None,
  250. 'supported_formats': ['wav'],
  251. 'max_duration': 300
  252. }
  253. def _start_processing_thread(self) -> None:
  254. """Start the audio processing thread."""
  255. if self._processing_thread is None or not self._processing_thread.is_alive():
  256. self._processing_thread = threading.Thread(
  257. target=self._processing_loop,
  258. name="STTProcessingThread",
  259. daemon=True
  260. )
  261. self._processing_thread.start()
  262. pprint("STT processing thread started")
  263. def _processing_loop(self) -> None:
  264. """Main processing loop for STT requests."""
  265. while not self._processing_shutdown.wait(0.1):
  266. try:
  267. # Get request from queue with timeout
  268. try:
  269. request = self._processing_queue.get(timeout=1.0)
  270. except queue.Empty:
  271. continue
  272. # Process the audio
  273. self._process_audio_request(request)
  274. self._processing_queue.task_done()
  275. except Exception as e:
  276. pprint(f"Error in processing loop: {e}")
  277. def _process_audio_request(self, request: Dict[str, Any]) -> None:
  278. """Process a single audio STT request."""
  279. try:
  280. audio_data = request['audio_data']
  281. session_id = request.get('session_id', 'unknown')
  282. source_info = request.get('source_info', {})
  283. pprint(f"Processing STT request for session {session_id}")
  284. # Preprocess audio if enabled
  285. if self.enable_preprocessing:
  286. audio_data = self._preprocess_audio(audio_data)
  287. # Check cache if enabled
  288. if self.enable_caching:
  289. cached_result = self._check_cache(audio_data)
  290. if cached_result:
  291. self._stats['cache_hits'] += 1
  292. self._send_stt_result(cached_result, session_id, source_info)
  293. return
  294. # Perform speech recognition
  295. result = self._recognize_speech(audio_data)
  296. # Cache result if successful and caching enabled
  297. if result and result.confidence >= self.min_confidence and self.enable_caching:
  298. self._cache_result(audio_data, result)
  299. # Send result
  300. if result:
  301. self._send_stt_result(result, session_id, source_info)
  302. # Update statistics
  303. self._update_stats(result)
  304. except Exception as e:
  305. pprint(f"Error processing audio request: {e}")
  306. self._stats['failed_recognitions'] += 1
  307. def _recognize_speech(self, audio_data: bytes) -> Optional[STTResult]:
  308. """Recognize speech from audio data."""
  309. start_time = time.time()
  310. try:
  311. # Try primary engine first
  312. if self.primary_engine in self._engines and self._engines[self.primary_engine]['available']:
  313. result = self._recognize_with_engine(audio_data, self.primary_engine)
  314. if result and result.confidence >= self.min_confidence:
  315. return result
  316. # Fallback to secondary engine
  317. if (self.fallback_engine in self._engines and
  318. self._engines[self.fallback_engine]['available'] and
  319. self.fallback_engine != self.primary_engine):
  320. pprint(f"Primary engine failed, trying fallback: {self.fallback_engine}")
  321. result = self._recognize_with_engine(audio_data, self.fallback_engine)
  322. if result and result.confidence >= self.min_confidence:
  323. return result
  324. # Try other available engines
  325. for engine_name, engine in self._engines.items():
  326. if (engine['available'] and
  327. engine_name not in [self.primary_engine, self.fallback_engine]):
  328. pprint(f"Trying additional engine: {engine_name}")
  329. result = self._recognize_with_engine(audio_data, engine_name)
  330. if result and result.confidence >= self.min_confidence:
  331. return result
  332. return None
  333. except Exception as e:
  334. pprint(f"Speech recognition failed: {e}")
  335. return None
  336. finally:
  337. processing_time = time.time() - start_time
  338. pprint(f"Speech recognition completed in {processing_time:.2f}s")
  339. def _recognize_with_engine(self, audio_data: bytes, engine_name: str) -> Optional[STTResult]:
  340. """Recognize speech using a specific engine."""
  341. if engine_name == "whisper":
  342. return self._recognize_whisper(audio_data)
  343. elif engine_name == "google":
  344. return self._recognize_google(audio_data)
  345. elif engine_name == "azure":
  346. return self._recognize_azure(audio_data)
  347. elif engine_name == "custom":
  348. return self._recognize_custom(audio_data)
  349. else:
  350. pprint(f"Unknown engine: {engine_name}")
  351. return None
  352. def _recognize_whisper(self, audio_data: bytes) -> Optional[STTResult]:
  353. """Recognize speech using Whisper engine."""
  354. try:
  355. # Placeholder implementation - actual Whisper integration
  356. pprint("Processing with Whisper engine...")
  357. # Simulate processing delay
  358. time.sleep(0.5)
  359. # Simulate result (actual implementation would call Whisper API)
  360. text = "This is a simulated Whisper transcription"
  361. confidence = 0.85
  362. language = "en"
  363. return STTResult(
  364. text=text,
  365. confidence=confidence,
  366. language=language,
  367. engine="whisper",
  368. processing_time=0.5
  369. )
  370. except Exception as e:
  371. pprint(f"Whisper recognition failed: {e}")
  372. return None
  373. def _recognize_google(self, audio_data: bytes) -> Optional[STTResult]:
  374. """Recognize speech using Google Speech engine."""
  375. try:
  376. pprint("Processing with Google Speech engine...")
  377. # Simulate processing
  378. time.sleep(0.3)
  379. # Simulate result
  380. text = "This is a simulated Google Speech transcription"
  381. confidence = 0.92
  382. return STTResult(
  383. text=text,
  384. confidence=confidence,
  385. language="en-US",
  386. engine="google",
  387. processing_time=0.3
  388. )
  389. except Exception as e:
  390. pprint(f"Google Speech recognition failed: {e}")
  391. return None
  392. def _recognize_azure(self, audio_data: bytes) -> Optional[STTResult]:
  393. """Recognize speech using Azure Speech engine."""
  394. try:
  395. pprint("Processing with Azure Speech engine...")
  396. # Simulate processing
  397. time.sleep(0.4)
  398. # Simulate result
  399. text = "This is a simulated Azure Speech transcription"
  400. confidence = 0.88
  401. return STTResult(
  402. text=text,
  403. confidence=confidence,
  404. language="en-US",
  405. engine="azure",
  406. processing_time=0.4
  407. )
  408. except Exception as e:
  409. pprint(f"Azure Speech recognition failed: {e}")
  410. return None
  411. def _recognize_custom(self, audio_data: bytes) -> Optional[STTResult]:
  412. """Recognize speech using custom engine."""
  413. try:
  414. pprint("Processing with custom engine...")
  415. # Simulate processing
  416. time.sleep(0.6)
  417. # Simulate result
  418. text = "This is a simulated custom engine transcription"
  419. confidence = 0.78
  420. return STTResult(
  421. text=text,
  422. confidence=confidence,
  423. language=self.default_language,
  424. engine="custom",
  425. processing_time=0.6
  426. )
  427. except Exception as e:
  428. pprint(f"Custom engine recognition failed: {e}")
  429. return None
  430. def _preprocess_audio(self, audio_data: bytes) -> bytes:
  431. """Preprocess audio for better recognition."""
  432. try:
  433. pprint("Preprocessing audio...")
  434. # Placeholder - actual implementation would:
  435. # - Normalize volume
  436. # - Apply noise reduction
  437. # - Convert sample rate if needed
  438. # - Apply audio filters
  439. # For now, return original data
  440. return audio_data
  441. except Exception as e:
  442. pprint(f"Audio preprocessing failed: {e}")
  443. return audio_data
  444. def _check_cache(self, audio_data: bytes) -> Optional[STTResult]:
  445. """Check if audio result is cached."""
  446. # Placeholder - actual implementation would use audio hash
  447. return None
  448. def _cache_result(self, audio_data: bytes, result: STTResult) -> None:
  449. """Cache STT result."""
  450. # Placeholder - actual implementation would cache to disk/memory
  451. pass
  452. def _send_stt_result(self, result: STTResult, session_id: str, source_info: Dict) -> None:
  453. """Send STT result via event system."""
  454. try:
  455. event_handler = self.application.get_event_handler()
  456. if event_handler:
  457. event_data = {
  458. 'conversation_id': session_id,
  459. 'text': result.text,
  460. 'confidence': result.confidence,
  461. 'language': result.language,
  462. 'engine': result.engine,
  463. 'processing_time': result.processing_time,
  464. 'timestamp': result.timestamp,
  465. 'speaker_info': source_info,
  466. 'alternatives': result.alternatives
  467. }
  468. event_handler.trigger_event("text_received", event_data)
  469. pprint(f"STT result sent: '{result.text}' (confidence: {result.confidence:.2f})")
  470. except Exception as e:
  471. pprint(f"Error sending STT result: {e}")
  472. def _update_stats(self, result: Optional[STTResult]) -> None:
  473. """Update processing statistics."""
  474. self._stats['total_requests'] += 1
  475. if result:
  476. self._stats['successful_recognitions'] += 1
  477. self._stats['total_processing_time'] += result.processing_time
  478. self._stats['average_processing_time'] = (
  479. self._stats['total_processing_time'] / self._stats['successful_recognitions']
  480. )
  481. self._stats['confidence_distribution'].append(result.confidence)
  482. if result.language:
  483. self._stats['languages_detected'].add(result.language)
  484. else:
  485. self._stats['failed_recognitions'] += 1
  486. # Event Handlers
  487. @TrixyEvent("raw_audio_input_received")
  488. def on_audio_input(self, event_name: str, event_data: Any) -> None:
  489. """Handle raw audio input for STT processing."""
  490. if not self.is_enabled():
  491. return
  492. try:
  493. conversation_id = event_data.get('conversation_id', 'unknown')
  494. audio_data = event_data.get('audio_data')
  495. speaker_info = event_data.get('speaker_info', {})
  496. if not audio_data:
  497. pprint("No audio data received, skipping STT processing")
  498. return
  499. pprint(f"Received audio input for conversation {conversation_id}")
  500. # Create processing request
  501. request = {
  502. 'audio_data': audio_data,
  503. 'session_id': conversation_id,
  504. 'source_info': speaker_info,
  505. 'timestamp': time.time()
  506. }
  507. # Queue for processing
  508. self._processing_queue.put(request)
  509. pprint(f"Audio queued for STT processing (queue size: {self._processing_queue.qsize()})")
  510. except Exception as e:
  511. pprint(f"Error handling audio input: {e}")
  512. @TrixyEvent("system_startup")
  513. def on_system_startup(self, event_name: str, event_data: Any) -> None:
  514. """Handle system startup."""
  515. if not self.is_enabled():
  516. return
  517. pprint("STT Plugin system startup")
  518. # Verify engines
  519. available_engines = [name for name, engine in self._engines.items() if engine['available']]
  520. if not available_engines:
  521. pprint("WARNING: No STT engines available!")
  522. else:
  523. pprint(f"Available STT engines: {available_engines}")
  524. # Lifecycle Hooks
  525. def on_enable(self) -> None:
  526. """Called when plugin is enabled."""
  527. pprint("STT Plugin enabled")
  528. self._start_processing_thread()
  529. def on_disable(self) -> None:
  530. """Called when plugin is disabled."""
  531. pprint("STT Plugin disabled")
  532. # Clear processing queue
  533. while not self._processing_queue.empty():
  534. try:
  535. self._processing_queue.get_nowait()
  536. except queue.Empty:
  537. break
  538. def cleanup(self) -> None:
  539. """Clean up STT plugin resources."""
  540. pprint("STT Plugin cleanup")
  541. # Stop processing thread
  542. self._processing_shutdown.set()
  543. if self._processing_thread and self._processing_thread.is_alive():
  544. self._processing_thread.join(timeout=5.0)
  545. # Clear queue
  546. while not self._processing_queue.empty():
  547. try:
  548. self._processing_queue.get_nowait()
  549. except queue.Empty:
  550. break
  551. # Save final statistics
  552. self.set_config_value("final_stats", self._stats.copy())
  553. # Plugin-specific Methods
  554. def get_stt_stats(self) -> Dict[str, Any]:
  555. """Get STT processing statistics."""
  556. stats = self._stats.copy()
  557. # Convert set to list for JSON serialization
  558. stats['languages_detected'] = list(stats['languages_detected'])
  559. # Add current status
  560. stats.update({
  561. 'queue_size': self._processing_queue.qsize(),
  562. 'active_sessions': len(self._active_sessions),
  563. 'available_engines': [name for name, engine in self._engines.items() if engine['available']],
  564. 'primary_engine': self.primary_engine,
  565. 'fallback_engine': self.fallback_engine
  566. })
  567. return stats
  568. def test_engine(self, engine_name: str) -> Dict[str, Any]:
  569. """Test a specific STT engine."""
  570. if engine_name not in self._engines:
  571. return {'success': False, 'error': f'Engine {engine_name} not found'}
  572. if not self._engines[engine_name]['available']:
  573. return {'success': False, 'error': f'Engine {engine_name} not available'}
  574. try:
  575. # Generate test audio (silence)
  576. test_audio = b'\x00' * (self.sample_rate * 2) # 1 second of silence
  577. result = self._recognize_with_engine(test_audio, engine_name)
  578. return {
  579. 'success': True,
  580. 'engine': engine_name,
  581. 'result': {
  582. 'text': result.text if result else None,
  583. 'confidence': result.confidence if result else 0,
  584. 'processing_time': result.processing_time if result else 0
  585. }
  586. }
  587. except Exception as e:
  588. return {'success': False, 'error': str(e)}