"""Transcription service: Audio -> canonical Transcript. This is the only place that runs Whisper. It always uses Whisper's "transcribe" task, never "translate" -- translation is handled as a separate, text-based step (see services/translation.py) operating on the Transcript this produces. That split is the core v1.0 architecture decision: there is exactly one path from audio to text, and everything else branches off the resulting Transcript. """ from __future__ import annotations from typing import Optional from faster_whisper import WhisperModel from models.transcript import Segment, Transcript # ISO 639-1 code -> display name. Used both for the "Source Language" # dropdown and for labelling detected languages in the results dashboard. SUPPORTED_LANGUAGES: dict[str, str] = { "fr": "French", "en": "English", "de": "German", "fa": "Persian", "es": "Spanish", } class TranscriptionService: """Thin wrapper around a faster-whisper model. Deliberately has no knowledge of translation, subtitles, or the UI -- it only knows how to turn audio into a Transcript. """ def __init__( self, model_size: str = "base", device: str = "cpu", compute_type: str = "int8", download_root: str = "/tmp/whisper_models", ) -> None: self._model = WhisperModel( model_size, device=device, compute_type=compute_type, download_root=download_root, ) def transcribe( self, audio_path: str, source_filename: str, language: Optional[str] = None, window_start: Optional[float] = None, window_end: Optional[float] = None, beam_size: int = 5, ) -> Transcript: """Run speech-to-text and return the canonical Transcript. `language` is an ISO 639-1 code, or None for auto-detect (the "Auto Detect" dropdown option). """ segments_iter, info = self._model.transcribe( audio_path, task="transcribe", language=language, beam_size=beam_size, ) segments = [ Segment(index=i, start=seg.start, end=seg.end, text=seg.text.strip()) for i, seg in enumerate(segments_iter, start=1) ] duration = segments[-1].end if segments else 0.0 return Transcript( source_filename=source_filename, language=info.language, language_probability=info.language_probability, duration=duration, segments=segments, window_start=window_start, window_end=window_end, )