import json import os import sys def _app_dir() -> str: """Root dir for config.json and vocabulary.json.""" if getattr(sys, "frozen", False): return os.path.dirname(sys.executable) # 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": "", } 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_vocab_file() -> None: """Set VOCAB_FILE from config['vocab_path'], falling back to DATA_DIR.""" global VOCAB_FILE vp = config.get("vocab_path", "") if vp: VOCAB_FILE = vp if os.path.isabs(vp) else os.path.join(DATA_DIR, vp) else: VOCAB_FILE = os.path.join(DATA_DIR, "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 def get_initial_prompt() -> str: words = vocab.get("words", []) return ", ".join(words) if words else ""