Files
whisper-local/whisper_local/tray/_settings.py
T

135 lines
4.5 KiB
Python
Raw Normal View History

"""Einstellungs-Dialog für whisper-local (cross-platform)."""
from __future__ import annotations
import sys
import threading
from typing import Callable
import sounddevice as sd
from whisper_local.config import Config, save_config
from whisper_local.tray._theme import apply_system_theme
def list_microphones() -> list[tuple[str, int]]:
"""Gibt Liste aller Eingabegeräte als (name, index) zurück."""
devices = sd.query_devices()
return [
(dev["name"], idx)
for idx, dev in enumerate(devices)
if dev["max_input_channels"] > 0
]
def _get_record_hotkey():
"""Wählt das plattformspezifische record_hotkey-Callable."""
if sys.platform == "win32":
from whisper_local.tray._hotkey_record_pynput import record_hotkey
return record_hotkey
from whisper_local.tray._hotkey_record_evdev import record_hotkey
return record_hotkey
class SettingsDialog:
"""Einstellungs-Dialog (läuft in eigenem Thread)."""
def __init__(self, config: Config, on_save: Callable[[Config], None]):
self._config = config
self._on_save = on_save
self._cancel_event = threading.Event()
def open(self) -> None:
"""Öffnet den Dialog in einem Daemon-Thread."""
thread = threading.Thread(target=self._run, daemon=True)
thread.start()
def _run(self) -> None:
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.title("whisper-local Einstellungen")
root.resizable(False, False)
apply_system_theme(root)
frame = ttk.Frame(root, padding=16)
frame.pack(fill=tk.BOTH, expand=True)
# --- Hotkey ---
ttk.Label(frame, text="Hotkey").grid(row=0, column=0, sticky=tk.W, pady=4)
hotkey_var = tk.StringVar(value=self._config.hotkey)
ttk.Label(frame, textvariable=hotkey_var, width=14, relief="sunken").grid(
row=0, column=1, padx=8
)
conflict_var = tk.StringVar()
ttk.Label(frame, textvariable=conflict_var, foreground="orange").grid(
row=1, column=0, columnspan=3, sticky=tk.W
)
record_hotkey = _get_record_hotkey()
def do_record():
hotkey_var.set("...")
conflict_var.set("")
def on_result(evdev_key: str, has_conflict: bool):
root.after(0, lambda: hotkey_var.set(evdev_key))
if has_conflict:
root.after(
0,
lambda: conflict_var.set(
"⚠ Taste ist von einer anderen App belegt (Win32-Hotkeys)"
),
)
def worker():
record_hotkey(on_result, self._cancel_event)
threading.Thread(target=worker, daemon=True).start()
ttk.Button(frame, text="Aufzeichnen", command=do_record).grid(
row=0, column=2, padx=4
)
# --- Mikrofon ---
ttk.Label(frame, text="Mikrofon").grid(row=2, column=0, sticky=tk.W, pady=4)
mics = list_microphones()
mic_names = ["Standard"] + [name for name, _ in mics]
current_mic = self._config.microphone or "Standard"
mic_var = tk.StringVar(value=current_mic)
ttk.Combobox(
frame, textvariable=mic_var, values=mic_names, state="readonly", width=32
).grid(row=2, column=1, columnspan=2, sticky=tk.W, padx=4)
# --- Buttons ---
btn_frame = ttk.Frame(frame)
btn_frame.grid(row=3, column=0, columnspan=3, pady=12, sticky=tk.E)
def save():
new_config = Config(
hotkey=hotkey_var.get(),
whisper_model=self._config.whisper_model,
language=self._config.language,
compute_type=self._config.compute_type,
sample_rate=self._config.sample_rate,
channels=self._config.channels,
min_duration=self._config.min_duration,
microphone="" if mic_var.get() == "Standard" else mic_var.get(),
)
save_config(new_config)
self._on_save(new_config)
self._cancel_event.set()
root.destroy()
def cancel():
self._cancel_event.set()
root.destroy()
ttk.Button(btn_frame, text="Speichern", command=save).pack(side=tk.RIGHT, padx=4)
ttk.Button(btn_frame, text="Abbrechen", command=cancel).pack(side=tk.RIGHT)
root.protocol("WM_DELETE_WINDOW", cancel)
root.mainloop()