File size: 5,321 Bytes
b97b788
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
"""
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