Compare commits
2 Commits
20e623dc37
...
05eb7ed144
| Author | SHA1 | Date | |
|---|---|---|---|
| 05eb7ed144 | |||
| ddfc4261e5 |
Binary file not shown.
+76
-1
@@ -109,7 +109,27 @@ class WhisperRunner:
|
||||
segments, info = self.model.transcribe(
|
||||
audio, language=language, beam_size=beam_size, vad_filter=vad_filter,
|
||||
)
|
||||
text = " ".join(seg.text.strip() for seg in segments)
|
||||
# Per-segment no_speech_prob auswerten: faster-whisper liefert das
|
||||
# mit. Bei Stille/Rauschen halluziniert Whisper bekannte YouTube-
|
||||
# Untertitel-Patterns ("Untertitelung des ZDF", "Vielen Dank fuer's
|
||||
# Zuschauen", ...). Segmente mit hohem no_speech_prob filtern wir
|
||||
# raus. Plus: bekannte Hallucination-Patterns explizit blacklisten.
|
||||
kept = []
|
||||
for seg in segments:
|
||||
# no_speech_prob: 1.0 = sicher Stille; 0.0 = sicher Sprache.
|
||||
# Threshold 0.6 ist nicht zu strikt (echte leise Sprache geht
|
||||
# noch durch) und nicht zu locker (Halluzinationen werden
|
||||
# zuverlaessig erwischt).
|
||||
nsp = getattr(seg, "no_speech_prob", 0.0)
|
||||
if nsp is not None and nsp >= 0.6:
|
||||
continue
|
||||
stext = (seg.text or "").strip()
|
||||
if not stext:
|
||||
continue
|
||||
if _is_known_hallucination(stext):
|
||||
continue
|
||||
kept.append(stext)
|
||||
text = " ".join(kept)
|
||||
return text, info.duration
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
@@ -117,6 +137,61 @@ class WhisperRunner:
|
||||
return await loop.run_in_executor(None, _run)
|
||||
|
||||
|
||||
# Bekannte Whisper-Halluzinations-Patterns. Tritt typisch bei Stille oder
|
||||
# Rauschen auf — Whispers Trainings-Corpus enthaelt Stunden von YouTube-
|
||||
# Videos mit diesen Untertitel-Outros. Substring-Match (case-insensitive)
|
||||
# ueber gestrippten Text. Wenn ein Segment EXAKT (nach Normalisierung) so
|
||||
# aussieht, ist's mit ~99% Sicherheit eine Halluzination.
|
||||
_HALLUCINATION_PHRASES = (
|
||||
"untertitelung des zdf",
|
||||
"untertitel im auftrag des zdf",
|
||||
"untertitelung im auftrag des zdf",
|
||||
"untertitel der amara.org community",
|
||||
"untertitel von stephanie geiges",
|
||||
"amara.org",
|
||||
"untertitel: kerstin grass",
|
||||
"vielen dank fuers zuschauen",
|
||||
"vielen dank fürs zuschauen",
|
||||
"vielen dank für's zuschauen",
|
||||
"vielen dank fuer's zuschauen",
|
||||
"vielen dank für das zuschauen",
|
||||
"vielen dank fuer das zuschauen",
|
||||
"danke für's zuschauen",
|
||||
"danke fürs zuschauen",
|
||||
"danke fuers zuschauen",
|
||||
"subs by",
|
||||
"subtitle by",
|
||||
"subtitles by",
|
||||
"thanks for watching",
|
||||
)
|
||||
|
||||
|
||||
def _normalize_for_hallu(text: str) -> str:
|
||||
"""Lowercase + trailing-Satzzeichen/Whitespace strippen. Jahreszahlen
|
||||
(4 Ziffern am Ende) auch entfernen — 'Untertitelung des ZDF, 2020'
|
||||
matcht damit auf 'untertitelung des zdf'."""
|
||||
t = text.lower().strip()
|
||||
# Entferne trailing punctuation incl. comma+digits
|
||||
while t and t[-1] in ".,!? \t\n":
|
||||
t = t[:-1]
|
||||
# 4-stellige Jahreszahl am Ende
|
||||
import re
|
||||
t = re.sub(r"[,\s]+\d{4}$", "", t).strip()
|
||||
while t and t[-1] in ".,!? \t\n":
|
||||
t = t[:-1]
|
||||
return t
|
||||
|
||||
|
||||
def _is_known_hallucination(text: str) -> bool:
|
||||
norm = _normalize_for_hallu(text)
|
||||
if not norm:
|
||||
return True
|
||||
for pat in _HALLUCINATION_PHRASES:
|
||||
if pat in norm:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def ffmpeg_to_float32(audio_b64: str, mime_type: str) -> np.ndarray:
|
||||
"""Dekodiert beliebiges Audio-Format → 16kHz mono float32 PCM."""
|
||||
if "mp4" in mime_type or "m4a" in mime_type or "aac" in mime_type:
|
||||
|
||||
Reference in New Issue
Block a user