109 lines
4.4 KiB
Python
109 lines
4.4 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
"""Findet Woerter, die im deutschen TTS vermutlich falsch ausgesprochen werden.
|
||
|
|
|
||
|
|
Idee: Was ein deutsches Rechtschreibwoerterbuch (hunspell) nicht kennt, ist meist
|
||
|
|
ein englischer Fachbegriff oder ein fremder Eigenname — also genau das, was
|
||
|
|
Chatterbox auf Deutsch falsch liest. Bereits im Aussprache-Woerterbuch stehende
|
||
|
|
Begriffe werden ausgeblendet.
|
||
|
|
|
||
|
|
Das Ergebnis ist eine VORSCHLAGSLISTE, kein fertiges Woerterbuch: die Umschrift
|
||
|
|
muss ein Mensch schreiben (oder pruefen). Genau dafuer gibt --json ein Geruest aus,
|
||
|
|
das man in pronunciation/de.json einfuegen und ausfuellen kann.
|
||
|
|
|
||
|
|
python pronunciation_candidates.py transkript.txt
|
||
|
|
python pronunciation_candidates.py *.txt --min-count 2
|
||
|
|
cat text.md | python pronunciation_candidates.py - --json
|
||
|
|
|
||
|
|
Braucht: hunspell + deutsches Woerterbuch (Debian/Ubuntu: hunspell hunspell-de-de).
|
||
|
|
"""
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import argparse
|
||
|
|
import json
|
||
|
|
import re
|
||
|
|
import shutil
|
||
|
|
import subprocess
|
||
|
|
import sys
|
||
|
|
from collections import Counter
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||
|
|
from pronunciation import load_pronunciation_dict # noqa: E402 (leicht, kein torch)
|
||
|
|
|
||
|
|
# Woerter mit Buchstaben, optional Bindestrich/Apostroph im Inneren.
|
||
|
|
WORD_RE = re.compile(r"[A-Za-zÄÖÜäöüß][A-Za-zÄÖÜäöüß'-]*")
|
||
|
|
|
||
|
|
# Kurze Grossbuchstabenfolgen (API, GPU) kennt hunspell auch nicht — die sind aber
|
||
|
|
# oft schon als Akronym korrekt buchstabiert. Trotzdem anzeigen, nur markieren.
|
||
|
|
ACRONYM_RE = re.compile(r"^[A-ZÄÖÜ]{2,6}s?$")
|
||
|
|
|
||
|
|
DICT_CANDIDATES = ["de_DE", "de_DE_frami", "de_AT", "de_CH"]
|
||
|
|
|
||
|
|
|
||
|
|
def pick_dictionary() -> str:
|
||
|
|
for d in DICT_CANDIDATES:
|
||
|
|
probe = subprocess.run(["hunspell", "-d", d, "-l"], input="Test\n",
|
||
|
|
capture_output=True, text=True)
|
||
|
|
if probe.returncode == 0:
|
||
|
|
return d
|
||
|
|
sys.exit("FEHLER: kein deutsches hunspell-Woerterbuch gefunden "
|
||
|
|
"(Debian/Ubuntu: sudo apt install hunspell hunspell-de-de).")
|
||
|
|
|
||
|
|
|
||
|
|
def unknown_words(words: list[str], dictionary: str) -> set[str]:
|
||
|
|
"""hunspell -l gibt genau die Woerter zurueck, die es NICHT kennt."""
|
||
|
|
out = subprocess.run(["hunspell", "-d", dictionary, "-l"],
|
||
|
|
input="\n".join(words), capture_output=True, text=True)
|
||
|
|
return set(out.stdout.split())
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> int:
|
||
|
|
ap = argparse.ArgumentParser(description=__doc__,
|
||
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||
|
|
ap.add_argument("files", nargs="+", help="Textdateien ('-' = stdin)")
|
||
|
|
ap.add_argument("--min-count", type=int, default=1, metavar="N",
|
||
|
|
help="nur Woerter, die mindestens N-mal vorkommen (Default: 1)")
|
||
|
|
ap.add_argument("--lang", default="de", help="Sprache des Woerterbuchs (Default: de)")
|
||
|
|
ap.add_argument("--json", action="store_true",
|
||
|
|
help="Geruest fuer pronunciation/<lang>.json ausgeben (Umschrift leer)")
|
||
|
|
args = ap.parse_args()
|
||
|
|
|
||
|
|
if not shutil.which("hunspell"):
|
||
|
|
return int(bool(sys.stderr.write(
|
||
|
|
"FEHLER: hunspell nicht installiert (sudo apt install hunspell hunspell-de-de).\n")))
|
||
|
|
|
||
|
|
text = "\n".join(sys.stdin.read() if f == "-" else Path(f).read_text(encoding="utf-8")
|
||
|
|
for f in args.files)
|
||
|
|
|
||
|
|
counts = Counter(WORD_RE.findall(text))
|
||
|
|
known = load_pronunciation_dict(args.lang)
|
||
|
|
# Auch Mehrwort-Eintraege beruecksichtigen ("Donald Trump" deckt "Trump" mit ab).
|
||
|
|
covered = {w for phrase in known for w in WORD_RE.findall(phrase)}
|
||
|
|
|
||
|
|
dictionary = pick_dictionary()
|
||
|
|
unknown = unknown_words(sorted(counts), dictionary)
|
||
|
|
|
||
|
|
hits = [(w, n) for w, n in counts.most_common()
|
||
|
|
if w in unknown and w not in covered and n >= args.min_count]
|
||
|
|
|
||
|
|
if not hits:
|
||
|
|
print("Keine neuen Kandidaten gefunden.")
|
||
|
|
return 0
|
||
|
|
|
||
|
|
if args.json:
|
||
|
|
print(json.dumps({w: "" for w, _ in hits}, ensure_ascii=False, indent=2))
|
||
|
|
print("\n// Umschrift eintragen und nach pronunciation/"
|
||
|
|
f"{args.lang}.json uebernehmen.", file=sys.stderr)
|
||
|
|
return 0
|
||
|
|
|
||
|
|
print(f"Woerterbuch: {dictionary} | bereits abgedeckt: {len(known)} Eintraege")
|
||
|
|
print(f"Kandidaten ({len(hits)}), haeufigste zuerst:\n")
|
||
|
|
for w, n in hits:
|
||
|
|
mark = " (Akronym)" if ACRONYM_RE.match(w) else ""
|
||
|
|
print(f" {n:4d}x {w}{mark}")
|
||
|
|
print("\nMit --json ein Geruest zum Ausfuellen erzeugen.")
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
sys.exit(main())
|