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,
download_root: str | None,
) -> 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
kein Dialog. Auf Download-Fehler wird ein Fehlerdialog gezeigt und sys.exit(1)
Wenn das Modell in unter 500 ms bereit ist (vollständiger Cache), erscheint
kein Dialog. Bei Download oder langsamer Initialisierung wird ein indeterminater
Wartebalken angezeigt. Auf Fehler wird ein Fehlerdialog gezeigt und sys.exit(1)
aufgerufen.
"""
import tkinter as tk
@@ -52,14 +53,11 @@ def load_model_with_progress(
from whisper_local.tray._theme import apply_system_theme
q: queue.Queue[dict[str, Any] | None] = queue.Queue()
result: list[WhisperModel | None] = [None]
result: list[Any] = [None]
error: list[BaseException | None] = [None]
original_tqdm = tqdm_module.tqdm
done_event = threading.Event()
def worker() -> None:
TkProgressTqdm._queue = q
tqdm_module.tqdm = TkProgressTqdm
try:
result[0] = WhisperModel(
model_name,
@@ -69,16 +67,23 @@ def load_model_with_progress(
except Exception as exc:
error[0] = exc
finally:
tqdm_module.tqdm = original_tqdm
TkProgressTqdm._queue = None
q.put(None) # Sentinel: signalisiert Fertigstellung
done_event.set()
thread = threading.Thread(target=worker, daemon=True)
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.withdraw() # zunächst versteckt
root.title("whisper-local Modell wird geladen")
root.resizable(False, False)
apply_system_theme(root)
@@ -87,48 +92,26 @@ def load_model_with_progress(
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="Bitte warten\u2026", foreground="gray").pack(
anchor=tk.W, pady=(4, 8)
)
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))
_dialog_shown = False
pb = ttk.Progressbar(frame, length=320, mode="indeterminate")
pb.pack(fill=tk.X)
pb.start(10)
def poll() -> None:
nonlocal _dialog_shown
try:
while True:
msg = q.get_nowait()
if msg is None: # Sentinel → fertig
if done_event.is_set():
root.quit()
return
# Erste echte Meldung → Dialog anzeigen
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(100, poll)
root.after(50, poll)
root.after(100, poll)
root.mainloop()
if error[0] is not None:
root.withdraw()
messagebox.showerror("Fehler beim Modell-Download", str(error[0]))
messagebox.showerror("Fehler beim Modell-Laden", str(error[0]))
root.destroy()
sys.exit(1)