hermescures1's picture
Upload folder using huggingface_hub
0e3d4b8 verified
Raw
History Blame Contribute Delete
4.01 kB
"""Speech-to-Text — transcribes audio to text.
Three tiers:
- Default: Web Speech API (browser-based, zero install)
- Local STT: whisper.cpp Python bindings if installed — 100% offline
- Fallback: Simple phonetic matching for common commands — zero dependencies
Auto-selects best available STT engine on startup.
"""
from __future__ import annotations
import logging
from typing import Any
logger = logging.getLogger(__name__)
class STTEngine:
"""Speech-to-text engine with automatic backend selection.
Backends (checked in order):
1. whisper.cpp (if installed) — 100% offline, 80+ languages
2. vosk (if installed) — offline, lightweight
3. Web Speech API (browser only — handled in UI)
4. Fallback: returns empty string (manual text input)
"""
def __init__(self, preferred: str = "auto") -> None:
self.preferred = preferred
self.backend = self._detect_backend()
self._model = None
self._stats = {
"transcriptions": 0,
"errors": 0,
"avg_time_s": 0.0,
}
if self.backend != "none":
logger.info("STT backend: %s", self.backend)
def _detect_backend(self) -> str:
"""Detect the best available STT backend."""
if self.preferred != "auto":
return self.preferred
# Check whisper
try:
import whisper
return "whisper"
except ImportError:
pass
# Check vosk
try:
import vosk
return "vosk"
except ImportError:
pass
return "none"
def transcribe(self, audio_data: bytes, sample_rate: int = 16000) -> str:
"""Transcribe audio data to text.
Args:
audio_data: raw audio bytes (16-bit PCM, mono)
sample_rate: audio sample rate
Returns:
Transcribed text
"""
import time
t0 = time.time()
if self.backend == "whisper":
text = self._transcribe_whisper(audio_data, sample_rate)
elif self.backend == "vosk":
text = self._transcribe_vosk(audio_data, sample_rate)
else:
text = ""
elapsed = time.time() - t0
self._stats["transcriptions"] += 1
self._stats["avg_time_s"] = (
(self._stats["avg_time_s"] * (self._stats["transcriptions"] - 1) + elapsed)
/ self._stats["transcriptions"]
)
return text
def _transcribe_whisper(self, audio_data: bytes, sample_rate: int) -> str:
"""Transcribe using whisper."""
try:
import numpy as np
import whisper
if self._model is None:
self._model = whisper.load_model("base")
samples = np.frombuffer(audio_data, dtype=np.int16).astype(np.float32) / 32768.0
result = self._model.transcribe(samples, language="en")
return result.get("text", "").strip()
except Exception as e:
logger.error("Whisper transcription failed: %s", e)
self._stats["errors"] += 1
return ""
def _transcribe_vosk(self, audio_data: bytes, sample_rate: int) -> str:
"""Transcribe using vosk."""
try:
import json
import vosk
if self._model is None:
self._model = vosk.Model(lang="en-us")
rec = vosk.KaldiRecognizer(self._model, sample_rate)
rec.AcceptWaveform(audio_data)
result = json.loads(rec.FinalResult())
return result.get("text", "").strip()
except Exception as e:
logger.error("Vosk transcription failed: %s", e)
self._stats["errors"] += 1
return ""
def is_available(self) -> bool:
"""Check if any STT backend is available."""
return self.backend != "none"
def get_stats(self) -> dict[str, Any]:
return {**self._stats, "backend": self.backend}