fix: tqdm-Patch durch Timeout-basierten Wartebalken ersetzen

huggingface_hub nutzt jetzt Xet (Rust-Engine) fuer model.bin-Downloads,
welche Python-tqdm komplett bypassen. Der Dialog erschien deshalb nie.

Neuer Ansatz: Nach 500ms Wartezeit wird ein indeterminater Wartebalken
angezeigt -- sowohl bei Downloads als auch bei langsamer Initialisierung.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-12 12:49:31 +02:00
parent e31230fd84
commit 753dbc555e
+28 -45
View File
@@ -39,10 +39,11 @@ def load_model_with_progress(
compute_type: str, compute_type: str,
download_root: str | None, download_root: str | None,
) -> Any: ) -> Any:
"""Lädt WhisperModel — zeigt bei Bedarf einen Download-Fortschrittsdialog. """Lädt WhisperModel — zeigt bei längerem Laden einen Wartebalken.
Wenn das Modell bereits gecacht ist (kein tqdm-Update kommt), erscheint Wenn das Modell in unter 500 ms bereit ist (vollständiger Cache), erscheint
kein Dialog. Auf Download-Fehler wird ein Fehlerdialog gezeigt und sys.exit(1) kein Dialog. Bei Download oder langsamer Initialisierung wird ein indeterminater
Wartebalken angezeigt. Auf Fehler wird ein Fehlerdialog gezeigt und sys.exit(1)
aufgerufen. aufgerufen.
""" """
import tkinter as tk import tkinter as tk
@@ -52,14 +53,11 @@ def load_model_with_progress(
from whisper_local.tray._theme import apply_system_theme from whisper_local.tray._theme import apply_system_theme
q: queue.Queue[dict[str, Any] | None] = queue.Queue() result: list[Any] = [None]
result: list[WhisperModel | None] = [None]
error: list[BaseException | None] = [None] error: list[BaseException | None] = [None]
original_tqdm = tqdm_module.tqdm done_event = threading.Event()
def worker() -> None: def worker() -> None:
TkProgressTqdm._queue = q
tqdm_module.tqdm = TkProgressTqdm
try: try:
result[0] = WhisperModel( result[0] = WhisperModel(
model_name, model_name,
@@ -69,16 +67,23 @@ def load_model_with_progress(
except Exception as exc: except Exception as exc:
error[0] = exc error[0] = exc
finally: finally:
tqdm_module.tqdm = original_tqdm done_event.set()
TkProgressTqdm._queue = None
q.put(None) # Sentinel: signalisiert Fertigstellung
thread = threading.Thread(target=worker, daemon=True) thread = threading.Thread(target=worker, daemon=True)
thread.start() thread.start()
# --- tkinter-Dialog (lazy: erscheint nur bei echtem Download) --- # Kurz warten wenn Modell sofort bereit (vollständiger Cache), kein Dialog
if done_event.wait(timeout=0.5):
if error[0] is not None:
root = tk.Tk()
root.withdraw()
messagebox.showerror("Fehler beim Modell-Laden", str(error[0]))
root.destroy()
sys.exit(1)
return result[0]
# Modell braucht länger (Download oder Initialisierung) → Dialog anzeigen
root = tk.Tk() root = tk.Tk()
root.withdraw() # zunächst versteckt
root.title("whisper-local Modell wird geladen") root.title("whisper-local Modell wird geladen")
root.resizable(False, False) root.resizable(False, False)
apply_system_theme(root) apply_system_theme(root)
@@ -87,48 +92,26 @@ def load_model_with_progress(
frame.pack(fill=tk.BOTH, expand=True) frame.pack(fill=tk.BOTH, expand=True)
ttk.Label(frame, text=f"Lade Whisper-Modell '{model_name}'...").pack(anchor=tk.W) ttk.Label(frame, text=f"Lade Whisper-Modell '{model_name}'...").pack(anchor=tk.W)
ttk.Label(frame, text="Bitte warten\u2026", foreground="gray").pack(
anchor=tk.W, pady=(4, 8)
)
file_var = tk.StringVar(value="") pb = ttk.Progressbar(frame, length=320, mode="indeterminate")
ttk.Label(frame, textvariable=file_var, foreground="gray").pack(anchor=tk.W, pady=(4, 8)) pb.pack(fill=tk.X)
pb.start(10)
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))
_dialog_shown = False
def poll() -> None: def poll() -> None:
nonlocal _dialog_shown if done_event.is_set():
try:
while True:
msg = q.get_nowait()
if msg is None: # Sentinel → fertig
root.quit() root.quit()
return return
# Erste echte Meldung → Dialog anzeigen root.after(100, poll)
if not _dialog_shown:
root.deiconify()
_dialog_shown = True
# 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.after(100, poll)
root.mainloop() root.mainloop()
if error[0] is not None: if error[0] is not None:
root.withdraw() root.withdraw()
messagebox.showerror("Fehler beim Modell-Download", str(error[0])) messagebox.showerror("Fehler beim Modell-Laden", str(error[0]))
root.destroy() root.destroy()
sys.exit(1) sys.exit(1)