File size: 2,683 Bytes
7d761b6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
"""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,
        )