hunspell_layer.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. # -*- coding: utf-8 -*-
  2. """
  3. Hunspell-Korrekturschicht.
  4. Woerterbuch-basierte Korrektur (~6ms/Wort).
  5. """
  6. from pathlib import Path
  7. from trixy_core.stt.layers.base import CorrectionLayer
  8. from trixy_core.utils.debug import pdebug, pwarn
  9. try:
  10. import hunspell
  11. _HAS_HUNSPELL = True
  12. except ImportError:
  13. _HAS_HUNSPELL = False
  14. class HunspellLayer(CorrectionLayer):
  15. """Hunspell woerterbuch-basierte Korrektur."""
  16. NAME = "hunspell"
  17. def __init__(self) -> None:
  18. self._hobj: "hunspell.HunSpell | None" = None
  19. self._protected_words: set[str] = set()
  20. def is_available(self) -> bool:
  21. """Prueft ob pyhunspell installiert ist."""
  22. return _HAS_HUNSPELL
  23. def initialize(self, config: dict, language: str, protected_words: list[str]) -> bool:
  24. """Laedt Hunspell-Woerterbuch."""
  25. if not _HAS_HUNSPELL:
  26. return False
  27. dict_path = config.get("hunspell_dict_path", f"/usr/share/hunspell/{language}_DE")
  28. # Hunspell braucht .dic und .aff Dateien
  29. dic_file = Path(f"{dict_path}.dic")
  30. aff_file = Path(f"{dict_path}.aff")
  31. if not dic_file.exists() or not aff_file.exists():
  32. pdebug(f"[Hunspell] Woerterbuch nicht gefunden: {dict_path}")
  33. return False
  34. try:
  35. self._hobj = hunspell.HunSpell(str(aff_file), str(dic_file))
  36. self._protected_words = {w.lower() for w in protected_words}
  37. # Geschuetzte Woerter zum Woerterbuch hinzufuegen
  38. for word in protected_words:
  39. self._hobj.add(word)
  40. pdebug(f"[Hunspell] Woerterbuch geladen: {dict_path}")
  41. return True
  42. except Exception as e:
  43. pwarn(f"[Hunspell] Initialisierung fehlgeschlagen: {e}")
  44. self._hobj = None
  45. return False
  46. def correct(self, text: str) -> str:
  47. """Prueft jedes Wort einzeln und schlaegt Korrekturen vor."""
  48. if not self._hobj or not text.strip():
  49. return text
  50. try:
  51. words = text.split()
  52. corrected_words = []
  53. changed = False
  54. for word in words:
  55. # Geschuetzte Woerter ueberspringen
  56. if word.lower() in self._protected_words:
  57. corrected_words.append(word)
  58. continue
  59. # Wort pruefen (Satzzeichen entfernen fuer Pruefung)
  60. clean_word = word.strip(".,!?;:\"'()")
  61. if not clean_word:
  62. corrected_words.append(word)
  63. continue
  64. if self._hobj.spell(clean_word):
  65. # Wort ist korrekt
  66. corrected_words.append(word)
  67. else:
  68. # Wort ist falsch — Vorschlaege holen
  69. suggestions = self._hobj.suggest(clean_word)
  70. if suggestions:
  71. # Ersten Vorschlag verwenden (bester Match)
  72. suggestion = suggestions[0]
  73. # Satzzeichen beibehalten
  74. prefix = word[:len(word) - len(word.lstrip(".,!?;:\"'()"))] if word != word.lstrip(".,!?;:\"'()") else ""
  75. suffix = word[len(word.rstrip(".,!?;:\"'()")):] if word != word.rstrip(".,!?;:\"'()") else ""
  76. corrected_words.append(f"{prefix}{suggestion}{suffix}")
  77. changed = True
  78. else:
  79. corrected_words.append(word)
  80. if changed:
  81. result = " ".join(corrected_words)
  82. pdebug(f"[Hunspell] '{text}' → '{result}'")
  83. return result
  84. return text
  85. except Exception as e:
  86. pdebug(f"[Hunspell] Fehler bei Korrektur: {e}")
  87. return text
  88. def shutdown(self) -> None:
  89. """Gibt Ressourcen frei."""
  90. self._hobj = None