Files
2026-04-15 19:02:04 +02:00

146 lines
4.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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)
# --- Medien-Pause ---
pause_media_var = tk.BooleanVar(
value=self._config.pause_media_during_recording
)
ttk.Checkbutton(
frame,
text="Medienwiedergabe während Aufnahme pausieren",
variable=pause_media_var,
).grid(row=3, column=0, columnspan=3, sticky=tk.W, pady=4)
# --- Buttons ---
btn_frame = ttk.Frame(frame)
btn_frame.grid(row=4, 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(),
pause_media_during_recording=pause_media_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()