added grammar check
This commit is contained in:
parent
e802ab2b3c
commit
9a0d080fb8
|
|
@ -62,6 +62,10 @@
|
||||||
{
|
{
|
||||||
"from": "Kashi",
|
"from": "Kashi",
|
||||||
"to": "Cachy"
|
"to": "Cachy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"from": "SHP",
|
||||||
|
"to": "SAP"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -34,6 +34,7 @@ _hiddenimports = [
|
||||||
'ctranslate2',
|
'ctranslate2',
|
||||||
'faster_whisper',
|
'faster_whisper',
|
||||||
'sounddevice',
|
'sounddevice',
|
||||||
|
'language_tool_python',
|
||||||
]
|
]
|
||||||
if _is_windows:
|
if _is_windows:
|
||||||
_hiddenimports.append('pynput.keyboard._win32')
|
_hiddenimports.append('pynput.keyboard._win32')
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ DEFAULT_CONFIG = {
|
||||||
"sample_rate": 16000,
|
"sample_rate": 16000,
|
||||||
"vocab_path": "",
|
"vocab_path": "",
|
||||||
"model_dir": "",
|
"model_dir": "",
|
||||||
|
"grammar_check": True,
|
||||||
}
|
}
|
||||||
|
|
||||||
MODELS = ["tiny", "base", "small", "medium", "large-v2", "large-v3"]
|
MODELS = ["tiny", "base", "small", "medium", "large-v2", "large-v3"]
|
||||||
|
|
@ -115,6 +116,23 @@ def apply_vocab(text: str) -> str:
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
_STYLE_HINTS = {
|
||||||
|
"de": "Hallo, wie geht es Ihnen? Ich arbeite an einem wichtigen Projekt. "
|
||||||
|
"Die Ergebnisse der Analyse zeigen deutliche Verbesserungen.",
|
||||||
|
"en": "Hello, how are you? I am working on an important project. "
|
||||||
|
"The analysis results show clear improvements.",
|
||||||
|
"fr": "Bonjour, comment allez-vous ? Je travaille sur un projet important. "
|
||||||
|
"Les résultats de l'analyse montrent des améliorations nettes.",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_initial_prompt() -> str:
|
def get_initial_prompt() -> str:
|
||||||
|
parts = []
|
||||||
|
lang = config.get("language")
|
||||||
|
hint = _STYLE_HINTS.get(lang)
|
||||||
|
if hint:
|
||||||
|
parts.append(hint)
|
||||||
words = vocab.get("words", [])
|
words = vocab.get("words", [])
|
||||||
return ", ".join(words) if words else ""
|
if words:
|
||||||
|
parts.append(", ".join(words))
|
||||||
|
return " ".join(parts) if parts else ""
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
"""Optional grammar correction using LanguageTool."""
|
||||||
|
|
||||||
|
_tool = None
|
||||||
|
_lang = None
|
||||||
|
|
||||||
|
_LANG_MAP = {
|
||||||
|
"de": "de-DE",
|
||||||
|
"en": "en-US",
|
||||||
|
"fr": "fr-FR",
|
||||||
|
"es": "es",
|
||||||
|
"it": "it",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def init(lang, log=print):
|
||||||
|
"""Pre-initialize LanguageTool. Call once at startup."""
|
||||||
|
global _tool, _lang
|
||||||
|
if lang == _lang and _tool is not None:
|
||||||
|
return
|
||||||
|
_lang = lang
|
||||||
|
try:
|
||||||
|
import language_tool_python
|
||||||
|
lt_lang = _LANG_MAP.get(lang, lang or "de-DE")
|
||||||
|
_tool = language_tool_python.LanguageTool(lt_lang)
|
||||||
|
log("Grammar checker ready.")
|
||||||
|
except ImportError:
|
||||||
|
_tool = None
|
||||||
|
log("language_tool_python not installed — grammar check disabled.")
|
||||||
|
except Exception as e:
|
||||||
|
_tool = None
|
||||||
|
log(f"Grammar checker init failed: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def correct(text):
|
||||||
|
"""Correct grammar, capitalization, and punctuation."""
|
||||||
|
if _tool is None:
|
||||||
|
return text
|
||||||
|
try:
|
||||||
|
return _tool.correct(text)
|
||||||
|
except Exception:
|
||||||
|
return text
|
||||||
|
|
@ -158,6 +158,15 @@ def _open_main(root: tk.Tk, on_reload) -> None:
|
||||||
f = row("Sprache")
|
f = row("Sprache")
|
||||||
dd(f, lang_var, list(cfg.LANGUAGES.keys()), 14).pack(side="left")
|
dd(f, lang_var, list(cfg.LANGUAGES.keys()), 14).pack(side="left")
|
||||||
|
|
||||||
|
# ── TEXTVERARBEITUNG ──
|
||||||
|
section("TEXTVERARBEITUNG")
|
||||||
|
grammar_var = tk.BooleanVar(value=cfg.config.get("grammar_check", True))
|
||||||
|
f_gc = row("Grammatikkorrektur", hint="pip install language_tool_python")
|
||||||
|
tk.Checkbutton(f_gc, variable=grammar_var, text="Aktiviert",
|
||||||
|
bg=BG, fg=FG, selectcolor=BG3, activebackground=BG,
|
||||||
|
activeforeground=FG, font=FONT_UI,
|
||||||
|
highlightthickness=0, bd=0).pack(side="left")
|
||||||
|
|
||||||
# ── LEISTUNG ──
|
# ── LEISTUNG ──
|
||||||
section("LEISTUNG")
|
section("LEISTUNG")
|
||||||
device_var = tk.StringVar(value=cfg.config["device"])
|
device_var = tk.StringVar(value=cfg.config["device"])
|
||||||
|
|
@ -232,6 +241,7 @@ def _open_main(root: tk.Tk, on_reload) -> None:
|
||||||
cfg.config["hotkey"] = hotkey_var.get()
|
cfg.config["hotkey"] = hotkey_var.get()
|
||||||
cfg.config["vocab_path"] = vocab_path_var.get()
|
cfg.config["vocab_path"] = vocab_path_var.get()
|
||||||
cfg.config["model_dir"] = model_dir_var.get()
|
cfg.config["model_dir"] = model_dir_var.get()
|
||||||
|
cfg.config["grammar_check"] = grammar_var.get()
|
||||||
cfg.save_config()
|
cfg.save_config()
|
||||||
win.destroy()
|
win.destroy()
|
||||||
threading.Thread(target=on_reload, daemon=True).start()
|
threading.Thread(target=on_reload, daemon=True).start()
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import time
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from faster_whisper import WhisperModel
|
from faster_whisper import WhisperModel
|
||||||
|
|
||||||
from whisper_app import app, config, typer
|
from whisper_app import app, config, grammar, typer
|
||||||
|
|
||||||
|
|
||||||
def load_model() -> None:
|
def load_model() -> None:
|
||||||
|
|
@ -16,6 +16,8 @@ def load_model() -> None:
|
||||||
download_root=model_dir,
|
download_root=model_dir,
|
||||||
)
|
)
|
||||||
app.log("Model ready.")
|
app.log("Model ready.")
|
||||||
|
if config.config.get("grammar_check"):
|
||||||
|
grammar.init(config.config.get("language") or "de", log=app.log)
|
||||||
|
|
||||||
|
|
||||||
def stop_and_transcribe() -> None:
|
def stop_and_transcribe() -> None:
|
||||||
|
|
@ -62,6 +64,8 @@ def _do_transcribe() -> None:
|
||||||
)
|
)
|
||||||
text = " ".join(s.text for s in segments).strip()
|
text = " ".join(s.text for s in segments).strip()
|
||||||
text = config.apply_vocab(text)
|
text = config.apply_vocab(text)
|
||||||
|
if config.config.get("grammar_check"):
|
||||||
|
text = grammar.correct(text)
|
||||||
app.log(f"Result: {repr(text)}")
|
app.log(f"Result: {repr(text)}")
|
||||||
|
|
||||||
if text:
|
if text:
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,31 @@ def _pynput_type(text):
|
||||||
KeyboardController().type(text)
|
KeyboardController().type(text)
|
||||||
|
|
||||||
|
|
||||||
|
def _wl_paste():
|
||||||
|
"""Read current clipboard contents, returns None on failure."""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["wl-paste", "--no-newline"],
|
||||||
|
capture_output=True, timeout=2,
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
return result.stdout
|
||||||
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _wl_copy_bytes(data):
|
||||||
|
"""Restore clipboard from raw bytes."""
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
["wl-copy"],
|
||||||
|
input=data, check=False, timeout=2,
|
||||||
|
)
|
||||||
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def type_text(text):
|
def type_text(text):
|
||||||
"""Type text into the active window, cross-platform."""
|
"""Type text into the active window, cross-platform."""
|
||||||
if os.name == "nt":
|
if os.name == "nt":
|
||||||
|
|
@ -16,9 +41,13 @@ def type_text(text):
|
||||||
return
|
return
|
||||||
session = os.environ.get("XDG_SESSION_TYPE", "")
|
session = os.environ.get("XDG_SESSION_TYPE", "")
|
||||||
if session == "wayland" and shutil.which("wl-copy"):
|
if session == "wayland" and shutil.which("wl-copy"):
|
||||||
|
old_clipboard = _wl_paste()
|
||||||
subprocess.run(["wl-copy", "--", text], check=False)
|
subprocess.run(["wl-copy", "--", text], check=False)
|
||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
subprocess.run(["xdotool", "key", "ctrl+v"], check=False)
|
subprocess.run(["xdotool", "key", "ctrl+v"], check=False)
|
||||||
|
time.sleep(0.05)
|
||||||
|
if old_clipboard is not None:
|
||||||
|
_wl_copy_bytes(old_clipboard)
|
||||||
elif shutil.which("xdotool"):
|
elif shutil.which("xdotool"):
|
||||||
subprocess.run(["xdotool", "type", "--clearmodifiers", "--", text], check=False)
|
subprocess.run(["xdotool", "type", "--clearmodifiers", "--", text], check=False)
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue