testingfaces commited on
Commit
6757f33
·
verified ·
1 Parent(s): d0a41ba

Update transcriber.py

Browse files
Files changed (1) hide show
  1. transcriber.py +16 -216
transcriber.py CHANGED
@@ -1,223 +1,23 @@
1
- """
2
- Department 2 — Transcriber
3
- UPGRADE APPLIED:
4
- 1. Military-Grade VAD: Silero VAD filters audio prior to Whisper processing.
5
- This drops the hallucination rate to zero by physically isolating human speech.
6
- """
7
-
8
  import os
9
- import time
10
  import logging
11
- import subprocess
12
- import tempfile
13
- import shutil
14
 
15
  logger = logging.getLogger(__name__)
16
 
17
- LANG_TO_WHISPER = {"auto": None, "en": "en", "te": "te", "hi": "hi", "ta": "ta", "kn": "kn"}
18
- GROQ_SUPPORTED_LANGS = {"en", "hi", "ta", "es", "fr", "de", "ja", "zh", "ar", "pt", "ru", "it"}
19
- FORCE_LANGUAGE_HINT = {"te", "kn", "hi", "ta"}
20
- CHUNK_SEC = 60
21
- MAX_RETRIES = 3
22
-
23
  class Transcriber:
24
  def __init__(self):
25
- self.groq_key = os.environ.get("GROQ_API_KEY", "")
26
- self._groq_client = None
27
- self._local_model = None
28
- self._last_segments = []
29
-
30
- if self.groq_key:
31
- print("[Transcriber] Groq API key found — primary = Groq Whisper large-v3")
32
- self._init_groq()
33
- else:
34
- print("[Transcriber] No GROQ_API_KEY — local Whisper loads on first use")
35
-
36
- def transcribe(self, audio_path: str, language: str = "auto"):
37
- lang_hint = LANG_TO_WHISPER.get(language, None)
38
- duration = self._get_duration(audio_path)
39
- self._last_segments = []
40
-
41
- if duration <= CHUNK_SEC:
42
- return self._transcribe_single(audio_path, lang_hint)
43
- return self._transcribe_chunked(audio_path, lang_hint, duration)
44
-
45
- def _transcribe_chunked(self, audio_path, language, duration):
46
- tmp_dir = tempfile.mkdtemp()
47
- chunks, start, idx = [], 0, 0
48
-
49
- while start < duration:
50
- cp = os.path.join(tmp_dir, f"chunk_{idx:03d}.wav")
51
- subprocess.run([
52
- "ffmpeg", "-y", "-i", audio_path, "-ss", str(start), "-t", str(CHUNK_SEC),
53
- "-acodec", "pcm_s16le", "-ar", "16000", "-ac", "1", cp
54
- ], capture_output=True)
55
- if os.path.exists(cp): chunks.append((cp, start))
56
- start += CHUNK_SEC
57
- idx += 1
58
-
59
- all_texts, all_segments = [], []
60
- detected, method = language or "en", "unknown"
61
-
62
- for i, (chunk_path, offset) in enumerate(chunks):
63
- try:
64
- text, lang, m = self._transcribe_single(chunk_path, language, skip_preprocess=True)
65
- all_texts.append(text.strip())
66
- detected, method = lang, m
67
- for seg in self._last_segments:
68
- all_segments.append({
69
- 'word': seg['word'],
70
- 'start': round(seg['start'] + offset, 3),
71
- 'end': round(seg['end'] + offset, 3),
72
- })
73
- except Exception as e:
74
- logger.warning(f"Chunk {i+1} failed: {e}")
75
-
76
- shutil.rmtree(tmp_dir, ignore_errors=True)
77
- self._last_segments = all_segments
78
- return " ".join(t for t in all_texts if t), detected, f"{method} (chunked)"
79
-
80
- def _transcribe_single(self, audio_path, language, skip_preprocess=False):
81
- preprocessed, temp_created = audio_path, False
82
- vad_path, vad_created = audio_path, False
83
-
84
- try:
85
- # 1. Standardize format
86
- if not skip_preprocess:
87
- preprocessed, temp_created = self._preprocess_for_whisper(audio_path)
88
-
89
- # 2. UPGRADE: Apply Silero VAD to kill hallucinations
90
- vad_path, vad_created = self._apply_silero_vad(preprocessed)
91
-
92
- # 3. Transcribe clean speech
93
- if self._groq_client is not None:
94
- try: return self._transcribe_groq(vad_path, language)
95
- except Exception as e:
96
- logger.warning(f"Groq failed ({e}), falling back to local")
97
- if self._local_model is None: self._init_local()
98
- return self._transcribe_local(vad_path, language)
99
-
100
- finally:
101
- for path, created in [(preprocessed, temp_created), (vad_path, vad_created)]:
102
- if created and path != audio_path:
103
- try: os.unlink(path)
104
- except: pass
105
-
106
- # ══════════════════════════════════════════════════════════════════
107
- # UPGRADE 2: MILITARY-GRADE VAD (SILERO)
108
- # ══════════════════════════════════════════════════════════════════
109
- def _apply_silero_vad(self, audio_path):
110
- """Isolates pure speech blocks so Whisper never hears static/silence."""
111
- try:
112
- import torch
113
- import torchaudio
114
-
115
- # Load model directly from Torch Hub cache
116
- model, utils = torch.hub.load(repo_or_dir='snakers4/silero-vad',
117
- model='silero_vad', force_reload=False, trust_repo=True)
118
-
119
- get_speech_timestamps, save_audio, read_audio, _, _ = utils
120
- wav = read_audio(audio_path, sampling_rate=16000)
121
-
122
- # Extract timestamps of undeniable human speech
123
- speech_timestamps = get_speech_timestamps(wav, model, sampling_rate=16000, min_silence_duration_ms=250)
124
-
125
- if not speech_timestamps:
126
- return audio_path, False
127
-
128
- # Stitch chunks together with a tiny 100ms pad to prevent words from blending
129
- chunks = []
130
- pad = torch.zeros(int(16000 * 0.1))
131
- for ts in speech_timestamps:
132
- chunks.append(wav[ts['start']:ts['end']])
133
- chunks.append(pad)
134
-
135
- final_wav = torch.cat(chunks).unsqueeze(0)
136
-
137
- fd, out_path = tempfile.mkstemp(suffix="_silero_cleaned.wav")
138
- os.close(fd)
139
- save_audio(out_path, final_wav, sampling_rate=16000)
140
-
141
- print("[Transcriber] ✅ Silero VAD applied. Hallucination risk neutralized.")
142
- return out_path, True
143
-
144
- except Exception as e:
145
- logger.warning(f"[Transcriber] Silero VAD failed: {e}. Passing raw audio to Whisper.")
146
- return audio_path, False
147
-
148
- def _preprocess_for_whisper(self, audio_path: str):
149
- try:
150
- fd, out_path = tempfile.mkstemp(suffix="_16k.wav")
151
- os.close(fd)
152
- if subprocess.run([
153
- "ffmpeg", "-y", "-i", audio_path, "-ar", "16000", "-ac", "1", "-acodec", "pcm_s16le", out_path
154
- ], capture_output=True).returncode == 0:
155
- return out_path, True
156
- return audio_path, False
157
- except: return audio_path, False
158
-
159
- def _init_groq(self):
160
- try:
161
- from groq import Groq
162
- self._groq_client = Groq(api_key=self.groq_key)
163
- except: self._groq_client = None
164
-
165
- def _transcribe_groq(self, audio_path, language=None):
166
- if language and language not in GROQ_SUPPORTED_LANGS:
167
- if language not in FORCE_LANGUAGE_HINT: language = None
168
- t0, resp, last_exc = time.time(), None, None
169
-
170
- for attempt in range(1, MAX_RETRIES + 1):
171
- try:
172
- with open(audio_path, "rb") as f:
173
- kwargs = dict(file=f, model="whisper-large-v3", response_format="verbose_json", timestamp_granularities=["word"], temperature=0.0)
174
- if language: kwargs["language"] = language
175
- resp = self._groq_client.audio.transcriptions.create(**kwargs)
176
- break
177
- except Exception as e:
178
- last_exc = e
179
- if "429" in str(e).lower() or "rate" in str(e).lower(): time.sleep(2 ** attempt)
180
- else: raise
181
-
182
- if resp is None: raise RuntimeError(f"Groq failed: {last_exc}")
183
-
184
- transcript = resp.text.strip()
185
- detected_lang = self._norm(getattr(resp, "language", language or "en") or "en")
186
-
187
- self._last_segments = [{'word': getattr(w, 'word', str(w)).strip(), 'start': float(getattr(w, 'start', 0.0)), 'end': float(getattr(w, 'end', 0.0))} for w in (getattr(resp, "words", []) or [])]
188
- print(f"[Transcriber] ✅ Groq done in {time.time()-t0:.2f}s")
189
- return transcript, detected_lang, "Groq Whisper large-v3"
190
-
191
- def _init_local(self):
192
- try:
193
- from faster_whisper import WhisperModel
194
- self._local_model = WhisperModel("large-v3", device="cpu", compute_type="int8")
195
- except: self._local_model = None
196
-
197
- def _transcribe_local(self, audio_path, language=None):
198
- t0 = time.time()
199
- if self._local_model is None: self._init_local()
200
-
201
- segments, info = self._local_model.transcribe(
202
- audio_path, language=language, beam_size=5, word_timestamps=True,
203
- condition_on_previous_text=False, temperature=[0.0, 0.2, 0.4, 0.6]
204
- )
205
-
206
- all_words, text_parts = [], []
207
- for seg in segments:
208
- text_parts.append(seg.text.strip())
209
- if seg.words:
210
- all_words.extend([{'word': w.word.strip(), 'start': round(w.start, 3), 'end': round(w.end, 3)} for w in seg.words])
211
-
212
- self._last_segments = all_words
213
- print(f"[Transcriber] ✅ Local Whisper done in {time.time()-t0:.2f}s")
214
- return " ".join(text_parts).strip(), info.language or language or "en", "faster-whisper large-v3 int8"
215
-
216
- def _get_duration(self, audio_path):
217
- try: return float(subprocess.run(["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", audio_path], capture_output=True, text=True).stdout.strip())
218
- except: return 0.0
219
-
220
- @staticmethod
221
- def _norm(raw):
222
- m = {"english":"en","telugu":"te","hindi":"hi","tamil":"ta","kannada":"kn"}
223
- return m.get(raw.lower(), raw[:2].lower() if len(raw) >= 2 else raw)
 
 
 
 
 
 
 
 
1
  import os
 
2
  import logging
3
+ from groq import Groq
 
 
4
 
5
  logger = logging.getLogger(__name__)
6
 
 
 
 
 
 
 
7
  class Transcriber:
8
  def __init__(self):
9
+ self.api_key = os.environ.get("GROQ_API_KEY", "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")
10
+ self.client = Groq(api_key=self.api_key)
11
+ print("[Transcriber] Groq API Loaded — Whisper-v3")
12
+
13
+ def transcribe(self, audio_path: str) -> dict:
14
+ with open(audio_path, "rb") as file:
15
+ transcription = self.client.audio.transcriptions.create(
16
+ file=(audio_path, file.read()),
17
+ model="whisper-large-v3",
18
+ response_format="verbose_json",
19
+ )
20
+ return {
21
+ "text": transcription.text,
22
+ "segments": transcription.segments
23
+ }