| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- # -*- coding: utf-8 -*-
- """
- Phonetische Korrekturschicht.
- Nutzt Koelner Phonetik um phonetisch aehnliche Woerter zu erkennen
- und Domain-Vokabular (Kuenstlernamen, Befehle) zu schuetzen.
- """
- from trixy_core.stt.layers.base import CorrectionLayer
- from trixy_core.utils.debug import pdebug, pwarn
- try:
- import cologne_phonetics
- _HAS_COLOGNE = True
- except ImportError:
- _HAS_COLOGNE = False
- class PhoneticLayer(CorrectionLayer):
- """Koelner-Phonetik-basierte Korrektur fuer Domain-Vokabular."""
- NAME = "phonetic"
- def __init__(self) -> None:
- # phonetischer Code → bevorzugtes Wort
- self._phonetic_index: dict[str, str] = {}
- self._protected_words: set[str] = set()
- def is_available(self) -> bool:
- """Prueft ob cologne_phonetics installiert ist."""
- return _HAS_COLOGNE
- def initialize(self, config: dict, language: str, protected_words: list[str]) -> bool:
- """Baut phonetischen Index aus geschuetzten Woertern auf."""
- if not _HAS_COLOGNE:
- return False
- try:
- self._protected_words = {w.lower() for w in protected_words}
- # Phonetischen Index aufbauen
- for word in protected_words:
- codes = cologne_phonetics.encode(word)
- if codes:
- # cologne_phonetics.encode gibt Liste von (wort, code) Tupeln
- for _, code in codes:
- if code:
- self._phonetic_index[code] = word.lower()
- pdebug(f"[Phonetic] Index aufgebaut mit {len(self._phonetic_index)} Eintraegen")
- return True
- except Exception as e:
- pwarn(f"[Phonetic] Initialisierung fehlgeschlagen: {e}")
- return False
- def correct(self, text: str) -> str:
- """Ersetzt Woerter die phonetisch einem geschuetzten Wort entsprechen."""
- if not text.strip():
- return text
- try:
- words = text.split()
- corrected_words = []
- changed = False
- for word in words:
- replacement = self._find_phonetic_match(word)
- if replacement and replacement != word.lower():
- corrected_words.append(replacement)
- changed = True
- else:
- corrected_words.append(word)
- if changed:
- result = " ".join(corrected_words)
- pdebug(f"[Phonetic] '{text}' → '{result}'")
- return result
- return text
- except Exception as e:
- pdebug(f"[Phonetic] Fehler bei Korrektur: {e}")
- return text
- def _find_phonetic_match(self, word: str) -> str | None:
- """Sucht phonetische Uebereinstimmung fuer ein Wort."""
- if word.lower() in self._protected_words:
- return word.lower()
- try:
- codes = cologne_phonetics.encode(word)
- if not codes:
- return None
- for _, code in codes:
- if code and code in self._phonetic_index:
- candidate = self._phonetic_index[code]
- # Nur ersetzen wenn Edit-Distanz klein genug
- if self._edit_distance(word.lower(), candidate) <= 3:
- return candidate
- return None
- except Exception:
- return None
- @staticmethod
- def _edit_distance(s1: str, s2: str) -> int:
- """Berechnet Levenshtein-Distanz zwischen zwei Strings."""
- if len(s1) < len(s2):
- return PhoneticLayer._edit_distance(s2, s1)
- if len(s2) == 0:
- return len(s1)
- prev_row = range(len(s2) + 1)
- for i, c1 in enumerate(s1):
- curr_row = [i + 1]
- for j, c2 in enumerate(s2):
- # Einfuegen, Loeschen, Ersetzen
- insertions = prev_row[j + 1] + 1
- deletions = curr_row[j] + 1
- substitutions = prev_row[j] + (c1 != c2)
- curr_row.append(min(insertions, deletions, substitutions))
- prev_row = curr_row
- return prev_row[-1]
|