Spaces:
Running on Zero
Running on Zero
| """ | |
| Speech-to-text via faster-whisper. | |
| Model loads lazily on first use and is cached for the process lifetime | |
| (loading is slow, ~seconds; keep it out of the request hot path after | |
| the first call). Runs on CPU by default -- fine for short voice memos, | |
| not tuned for long-form transcription. | |
| """ | |
| import logging | |
| from functools import lru_cache | |
| logger = logging.getLogger("memora.speech") | |
| # "tiny" / "base" / "small" - tradeoff between speed and accuracy. | |
| # "base" is a reasonable default for short voice commands. | |
| WHISPER_MODEL_SIZE = "base" | |
| class TranscriptionError(RuntimeError): | |
| """Raised when audio transcription fails.""" | |
| def _get_model(): | |
| from faster_whisper import WhisperModel | |
| logger.info("Loading faster-whisper model (%s) - first call only", WHISPER_MODEL_SIZE) | |
| return WhisperModel(WHISPER_MODEL_SIZE, device="cpu", compute_type="int8") | |
| def transcribe(audio_path: str) -> str: | |
| """ | |
| audio_path: path to a local audio file (wav/mp3/m4a/etc). | |
| Returns the transcribed text. Raises TranscriptionError on failure. | |
| """ | |
| try: | |
| model = _get_model() | |
| segments, _info = model.transcribe(audio_path) | |
| text = " ".join(segment.text.strip() for segment in segments) | |
| except Exception as exc: # faster-whisper doesn't expose a narrow exception type | |
| logger.exception("Transcription failed") | |
| raise TranscriptionError(str(exc)) from exc | |
| if not text.strip(): | |
| raise TranscriptionError("No speech detected in audio") | |
| return text.strip() | |