MIC-Off / speech.py
VethaNarayananG's picture
Refactor translation system to modular v3.0 architecture with offline NLLB/Whisper, gTTS fallback, corrections, and automated tests
b97b788
Raw
History Blame Contribute Delete
5.32 kB
"""
speech.py — Whisper-based offline Speech-to-Text module
Uses OpenAI Whisper (tiny model) for offline speech recognition.
"""
import os
import tempfile
import traceback
from typing import Optional, Dict, Any
import whisper
from config import WHISPER_MODEL
class SpeechRecognizer:
"""Offline speech-to-text using OpenAI Whisper."""
def __init__(self):
self.model = None
self.model_name = WHISPER_MODEL
self.ready = False
def load(self):
"""Load the Whisper model."""
try:
print(f"[Speech] Loading Whisper '{self.model_name}' model...")
self.model = whisper.load_model(self.model_name)
self.ready = True
print(f"[Speech] Whisper '{self.model_name}' loaded successfully.")
except Exception as e:
print(f"[Speech] ERROR loading Whisper: {e}")
traceback.print_exc()
self.ready = False
def transcribe(self, audio_path: str, language: Optional[str] = None) -> Dict[str, Any]:
"""
Transcribe an audio file to text.
Args:
audio_path: Path to the audio file (WAV, MP3, etc.)
language: Optional ISO 639-1 language code to force.
If None, Whisper auto-detects the language.
Returns:
dict with keys:
- text: transcribed text
- language: detected/forced language code
- confidence: detection confidence (0-1)
- segments: list of timed segments
"""
if not self.ready or self.model is None:
return {
"text": "",
"language": None,
"confidence": 0.0,
"segments": [],
"error": "Whisper model not loaded"
}
try:
options = {}
if language:
options["language"] = language
result = self.model.transcribe(audio_path, **options)
# Extract language detection info
detected_lang = result.get("language", language or "en")
# Get detection confidence from the model
confidence = self._get_language_confidence(audio_path, detected_lang)
segments = []
for seg in result.get("segments", []):
segments.append({
"start": round(seg["start"], 2),
"end": round(seg["end"], 2),
"text": seg["text"].strip()
})
return {
"text": result["text"].strip(),
"language": detected_lang,
"confidence": confidence,
"segments": segments,
"error": None
}
except Exception as e:
print(f"[Speech] Transcription error: {e}")
traceback.print_exc()
return {
"text": "",
"language": None,
"confidence": 0.0,
"segments": [],
"error": str(e)
}
def transcribe_bytes(self, audio_bytes: bytes, language: Optional[str] = None) -> Dict[str, Any]:
"""Transcribe raw audio bytes by saving to a temp file first."""
tmp_path = None
try:
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp:
tmp.write(audio_bytes)
tmp_path = tmp.name
return self.transcribe(tmp_path, language)
finally:
if tmp_path and os.path.exists(tmp_path):
try:
os.unlink(tmp_path)
except OSError:
pass
def detect_language(self, audio_path: str) -> Dict[str, Any]:
"""
Detect the spoken language in an audio file without full transcription.
Returns:
dict with language code and confidence scores
"""
if not self.ready or self.model is None:
return {"language": None, "confidence": 0.0, "error": "Model not loaded"}
try:
audio = whisper.load_audio(audio_path)
audio = whisper.pad_or_trim(audio)
mel = whisper.log_mel_spectrogram(audio).to(self.model.device)
_, probs = self.model.detect_language(mel)
# Get top 5 languages
sorted_langs = sorted(probs.items(), key=lambda x: x[1], reverse=True)
top_lang = sorted_langs[0]
return {
"language": top_lang[0],
"confidence": round(top_lang[1], 4),
"top_5": {k: round(v, 4) for k, v in sorted_langs[:5]},
"error": None
}
except Exception as e:
return {"language": None, "confidence": 0.0, "error": str(e)}
def _get_language_confidence(self, audio_path: str, detected_lang: str) -> float:
"""Get confidence score for a detected language."""
try:
audio = whisper.load_audio(audio_path)
audio = whisper.pad_or_trim(audio)
mel = whisper.log_mel_spectrogram(audio).to(self.model.device)
_, probs = self.model.detect_language(mel)
return round(probs.get(detected_lang, 0.0), 4)
except Exception:
return 0.0