From 3067499c8863bd6a53848899b732204f9a9d9fcc Mon Sep 17 00:00:00 2001 From: Vitali Graf Date: Sun, 12 Apr 2026 12:30:48 +0200 Subject: [PATCH] feat: load_model_with_progress mit tkinter-Fortschrittsdialog --- whisper_local/tray/_download_progress.py | 96 ++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/whisper_local/tray/_download_progress.py b/whisper_local/tray/_download_progress.py index 3b6d055..d129ccb 100644 --- a/whisper_local/tray/_download_progress.py +++ b/whisper_local/tray/_download_progress.py @@ -32,3 +32,99 @@ class TkProgressTqdm(tqdm_module.tqdm): "total": self.total or 0, }) return result + + +def load_model_with_progress( + model_name: str, + compute_type: str, + download_root: str | None, +) -> Any: + """Lädt WhisperModel — zeigt bei Bedarf einen Download-Fortschrittsdialog. + + Wenn das Modell bereits gecacht ist (kein tqdm-Update kommt), erscheint + kein Dialog. Auf Download-Fehler wird ein Fehlerdialog gezeigt und sys.exit(1) + aufgerufen. + """ + import tkinter as tk + from tkinter import messagebox, ttk + + from faster_whisper import WhisperModel + + from whisper_local.tray._theme import apply_system_theme + + q: queue.Queue[dict[str, Any] | None] = queue.Queue() + result: list[WhisperModel | None] = [None] + error: list[BaseException | None] = [None] + original_tqdm = tqdm_module.tqdm + + def worker() -> None: + TkProgressTqdm._queue = q + tqdm_module.tqdm = TkProgressTqdm + try: + result[0] = WhisperModel( + model_name, + compute_type=compute_type, + download_root=download_root, + ) + except Exception as exc: + error[0] = exc + finally: + tqdm_module.tqdm = original_tqdm + TkProgressTqdm._queue = None + q.put(None) # Sentinel: signalisiert Fertigstellung + + thread = threading.Thread(target=worker, daemon=True) + thread.start() + + # --- tkinter-Dialog (lazy: erscheint nur bei echtem Download) --- + root = tk.Tk() + root.withdraw() # zunächst versteckt + root.title("whisper-local – Modell wird geladen") + root.resizable(False, False) + apply_system_theme(root) + + frame = ttk.Frame(root, padding=16) + frame.pack(fill=tk.BOTH, expand=True) + + ttk.Label(frame, text=f"Lade Whisper-Modell '{model_name}'...").pack(anchor=tk.W) + + file_var = tk.StringVar(value="") + ttk.Label(frame, textvariable=file_var, foreground="gray").pack(anchor=tk.W, pady=(4, 8)) + + progress_var = tk.DoubleVar(value=0.0) + ttk.Progressbar( + frame, variable=progress_var, maximum=100, length=320, mode="determinate" + ).pack(fill=tk.X) + + pct_var = tk.StringVar(value="0 %") + ttk.Label(frame, textvariable=pct_var).pack(anchor=tk.E, pady=(2, 0)) + + def poll() -> None: + try: + while True: + msg = q.get_nowait() + if msg is None: # Sentinel → fertig + root.quit() + return + # Erste echte Meldung → Dialog anzeigen + if not root.winfo_viewable(): + root.deiconify() + # UI aktualisieren + file_var.set(msg["file"]) + if msg["total"] > 0: + pct = 100.0 * msg["n"] / msg["total"] + progress_var.set(pct) + pct_var.set(f"{pct:.0f} %") + except queue.Empty: + pass + root.after(50, poll) + + root.after(50, poll) + root.mainloop() + root.destroy() + + if error[0] is not None: + messagebox.showerror("Fehler beim Modell-Download", str(error[0])) + sys.exit(1) + + return result[0]