152 lines
5.4 KiB
Python
152 lines
5.4 KiB
Python
import json
|
|
import os
|
|
import sys
|
|
|
|
|
|
def _app_dir() -> str:
|
|
"""Root dir for config.json and vocabulary.json."""
|
|
if getattr(sys, "frozen", False):
|
|
exe_dir = os.path.dirname(sys.executable)
|
|
# Built exe lives at dist/<platform>/; project root is two levels up.
|
|
project_root = os.path.normpath(os.path.join(exe_dir, "..", ".."))
|
|
if os.path.isfile(os.path.join(project_root, "config.json")):
|
|
return project_root
|
|
return exe_dir
|
|
# config.py lives at whisper_app/config.py → parent of parent = repo root
|
|
return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
|
|
DATA_DIR = os.environ.get("WHISPER_DATA_DIR") or _app_dir()
|
|
|
|
_env_local = os.environ.get("WHISPER_LOCAL_DIR")
|
|
if _env_local:
|
|
_local_dir = _env_local
|
|
elif os.name == "nt":
|
|
_local_dir = os.path.join(os.environ.get("LOCALAPPDATA", DATA_DIR), "WhisperDictation")
|
|
else:
|
|
_local_dir = os.path.join(os.path.expanduser("~"), ".local", "share", "WhisperDictation")
|
|
|
|
CONFIG_FILE = os.path.join(DATA_DIR, "config.json")
|
|
CONFIG_LOCAL_FILE = os.path.join(_local_dir, "config_local.json")
|
|
VOCAB_FILE = os.path.join(DATA_DIR, "vocabulary.json")
|
|
|
|
DEFAULT_CONFIG = {
|
|
"hotkey": "ctrl+shift+space",
|
|
"model": "medium",
|
|
"device": "cuda",
|
|
"compute_type": "float16",
|
|
"language": "de",
|
|
"audio_device": None,
|
|
"sample_rate": 16000,
|
|
"vocab_path": "",
|
|
"model_dir": "",
|
|
"grammar_check": True,
|
|
"paste_delay_ms": 300,
|
|
"media_duck": True,
|
|
"duck_percent": 20,
|
|
}
|
|
|
|
MODELS = ["tiny", "base", "small", "medium", "large-v2", "large-v3"]
|
|
LANGUAGES = {"Deutsch": "de", "English": "en", "Français": "fr", "Español": "es",
|
|
"Italiano": "it", "Auto": None}
|
|
DEVICES = ["cuda", "cpu"]
|
|
COMPUTE_TYPES = {"float16 (GPU)": "float16", "int8 (CPU/GPU)": "int8", "float32": "float32"}
|
|
LOCAL_KEYS = {"audio_device", "device", "compute_type", "model"}
|
|
|
|
config: dict = {}
|
|
vocab: dict = {"words": [], "replacements": []}
|
|
|
|
|
|
def _resolve_path(value: str, fallback_name: str = "") -> str:
|
|
"""Resolve a config path: absolute stays absolute, relative is joined with DATA_DIR."""
|
|
if value:
|
|
return value if os.path.isabs(value) else os.path.join(DATA_DIR, value)
|
|
if fallback_name:
|
|
return os.path.join(DATA_DIR, fallback_name)
|
|
return ""
|
|
|
|
|
|
def _resolve_vocab_file() -> None:
|
|
"""Set VOCAB_FILE from config['vocab_path'], falling back to DATA_DIR."""
|
|
global VOCAB_FILE
|
|
VOCAB_FILE = _resolve_path(config.get("vocab_path", ""), "vocabulary.json")
|
|
|
|
|
|
def load_config() -> None:
|
|
global config
|
|
os.makedirs(_local_dir, exist_ok=True)
|
|
config = dict(DEFAULT_CONFIG)
|
|
if os.path.exists(CONFIG_FILE):
|
|
with open(CONFIG_FILE, encoding="utf-8") as f:
|
|
try:
|
|
config.update(json.load(f))
|
|
except json.JSONDecodeError:
|
|
print(f"Warning: could not parse {CONFIG_FILE}; using defaults")
|
|
config = dict(DEFAULT_CONFIG)
|
|
if os.path.exists(CONFIG_LOCAL_FILE):
|
|
with open(CONFIG_LOCAL_FILE, encoding="utf-8") as f:
|
|
try:
|
|
config.update(json.load(f))
|
|
except json.JSONDecodeError:
|
|
print(f"Warning: could not parse {CONFIG_LOCAL_FILE}; ignoring")
|
|
_resolve_vocab_file()
|
|
|
|
|
|
def save_config() -> None:
|
|
os.makedirs(os.path.dirname(CONFIG_FILE), exist_ok=True)
|
|
os.makedirs(os.path.dirname(CONFIG_LOCAL_FILE), exist_ok=True)
|
|
shared = {k: v for k, v in config.items() if k not in LOCAL_KEYS}
|
|
local = {k: v for k, v in config.items() if k in LOCAL_KEYS}
|
|
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
|
json.dump(shared, f, indent=2)
|
|
with open(CONFIG_LOCAL_FILE, "w", encoding="utf-8") as f:
|
|
json.dump(local, f, indent=2)
|
|
_resolve_vocab_file()
|
|
|
|
|
|
def load_vocab() -> None:
|
|
global vocab
|
|
if os.path.exists(VOCAB_FILE):
|
|
with open(VOCAB_FILE, encoding="utf-8") as f:
|
|
try:
|
|
vocab = json.load(f)
|
|
except json.JSONDecodeError:
|
|
print(f"Warning: could not parse {VOCAB_FILE}; using empty vocab")
|
|
vocab = {"words": [], "replacements": []}
|
|
else:
|
|
vocab = {"words": [], "replacements": []}
|
|
|
|
|
|
def save_vocab() -> None:
|
|
os.makedirs(os.path.dirname(VOCAB_FILE), exist_ok=True)
|
|
with open(VOCAB_FILE, "w", encoding="utf-8") as f:
|
|
json.dump(vocab, f, indent=2, ensure_ascii=False)
|
|
|
|
|
|
def apply_vocab(text: str) -> str:
|
|
for r in vocab.get("replacements", []):
|
|
text = text.replace(r["from"], r["to"])
|
|
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:
|
|
parts = []
|
|
lang = config.get("language")
|
|
hint = _STYLE_HINTS.get(lang)
|
|
if hint:
|
|
parts.append(hint)
|
|
words = vocab.get("words", [])
|
|
if words:
|
|
parts.append(", ".join(words))
|
|
return " ".join(parts) if parts else ""
|