Entfernt den Windows-only-Guard in App.__init__, damit der Dialog mit indeterminatem ttk.Progressbar auch unter Linux erscheint, wenn das Laden länger als 500 ms dauert. Ersetzt das literale \u2026 im Label durch das Zeichen … und passt Spec/Plan an den tatsächlichen Umsetzungsstand an (Timeout-basierter Wartebalken statt tqdm-Monkey-Patch, da die Xet-Engine Python-tqdm bypasst). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7.0 KiB
Modell-Lade-Wartebalken — Implementierungsplan
Status: Umgesetzt (Stand 2026-04-16) Zuletzt geändert: 2026-04-16
Goal: Beim ersten Start zeigt die App einen tkinter-Dialog mit indeterminatem Wartebalken, der erscheint, wenn das Laden des Whisper-Modells länger als 500 ms dauert. Bei schnell bereitem Modell erscheint kein Dialog.
Architecture: Ein Daemon-Thread lädt WhisperModel. Der Hauptthread wartet 500 ms via threading.Event; erst bei Timeout öffnet er einen tkinter-Dialog mit indeterminatem ttk.Progressbar, der via root.after(100, poll) auf done_event.is_set() pollt. Ein früher geplanter tqdm-Monkey-Patch (TkProgressTqdm) wurde verworfen, weil huggingface_hub für model.bin die Xet-Rust-Engine nutzt, die Python-tqdm bypasst.
Tech Stack: Python 3.13+, tkinter, faster_whisper, threading, queue (historisch), tqdm (historisch)
Dateien
| Datei | Status | Verantwortlichkeit |
|---|---|---|
whisper_local/tray/_download_progress.py |
Umgesetzt | TkProgressTqdm (Fallback, ungenutzt), load_model_with_progress() |
whisper_local/transcriber.py |
Umgesetzt | Optionaler model-Parameter in Transcriber.__init__ |
whisper_local/__main__.py |
Umgesetzt | Aufruf von load_model_with_progress (plattformübergreifend) |
tests/test_download_progress.py |
Umgesetzt | Unit-Tests für TkProgressTqdm |
tests/test_transcriber.py |
Umgesetzt | Test für neuen model-Parameter + Signatur-Korrektur |
Historie der Umsetzung
Die Implementierung erfolgte in zwei Phasen:
- Initialer Plan (April 2026): tqdm-Monkey-Patch mit determinater Progressbar, Dateiname- und Prozentanzeige (Commits
e92f5f5,3067499,44c8d8e,c26dfa3,3d9f95b,e31230f). - Korrektur (Commit
753dbc5): Monkey-Patch verworfen, da Xet-Engine tqdm bypasst. Ersetzt durch Timeout-basierten indeterminaten Wartebalken.
Die Tasks unten beschreiben den umgesetzten Endzustand.
Task 1: Transcriber um optionalen model-Parameter erweitern (erledigt)
Files:
whisper_local/transcriber.pytests/test_transcriber.py
Transcriber.__init__ akzeptiert model: WhisperModel | None = None. Wenn gesetzt, wird WhisperModel nicht selbst instanziiert. Test test_init_with_preloaded_model prüft diesen Pfad; test_init_loads_model wurde an die neue Signatur mit download_root=None angepasst.
Task 2: TkProgressTqdm implementieren und testen (erledigt, aber ungenutzt)
Files:
whisper_local/tray/_download_progress.pytests/test_download_progress.py
Klasse TkProgressTqdm erbt von tqdm.tqdm, akkumuliert Fortschritt in _accumulated_n und schreibt {"file", "n", "total"}-Dicts in TkProgressTqdm._queue, falls gesetzt. Unit-Tests decken Queue-Weiterleitung, Null-Queue-Fall und Akkumulation ab.
Hinweis: load_model_with_progress nutzt die Klasse nicht mehr (Xet-Bypass, siehe Historie). Die Klasse + Tests bleiben als dokumentierter Fallback erhalten — wird faster_whisper irgendwann wieder durch Python-tqdm laufen, kann sie reaktiviert werden.
Task 3: load_model_with_progress implementieren (erledigt, Ansatz geändert)
Files:
whisper_local/tray/_download_progress.py
Die finale Version nutzt kein tqdm-Patching. Stattdessen:
def load_model_with_progress(
model_name: str,
compute_type: str,
download_root: str | None,
) -> Any:
import tkinter as tk
from tkinter import messagebox, ttk
from faster_whisper import WhisperModel
from whisper_local.tray._theme import apply_system_theme
result: list[Any] = [None]
error: list[BaseException | None] = [None]
done_event = threading.Event()
def worker() -> None:
try:
result[0] = WhisperModel(
model_name, compute_type=compute_type, download_root=download_root
)
except Exception as exc:
error[0] = exc
finally:
done_event.set()
thread = threading.Thread(target=worker, daemon=True)
thread.start()
# Kurz warten – schneller Cache-Hit überspringt 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]
# Dialog mit indeterminatem Wartebalken
root = tk.Tk()
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)
ttk.Label(frame, text="Bitte warten\u2026", foreground="gray").pack(
anchor=tk.W, pady=(4, 8)
)
pb = ttk.Progressbar(frame, length=320, mode="indeterminate")
pb.pack(fill=tk.X)
pb.start(10)
def poll() -> None:
if done_event.is_set():
root.quit()
return
root.after(100, poll)
root.after(100, poll)
root.mainloop()
if error[0] is not None:
root.withdraw()
messagebox.showerror("Fehler beim Modell-Laden", str(error[0]))
root.destroy()
sys.exit(1)
root.destroy()
return result[0]
Task 4: App.__init__ in __main__.py anpassen (erledigt)
Files:
whisper_local/__main__.py
Plattformübergreifend — der anfängliche sys.platform == "win32"-Guard wurde entfernt, weil der Dialog auch auf Linux gewünscht ist und tkinter zur Standardbibliothek gehört.
from whisper_local.tray._download_progress import load_model_with_progress
from whisper_local.transcriber import _model_cache_dir
_preloaded_model = load_model_with_progress(
model_name=config.whisper_model,
compute_type=config.compute_type,
download_root=_model_cache_dir(),
)
self.transcriber = Transcriber(
model_name=config.whisper_model,
compute_type=config.compute_type,
language=config.language,
model=_preloaded_model,
)
Task 5: Manueller Test (empfohlen nach Regressionen)
- Schritt 1: Modell-Cache temporär umbenennen (Download erzwingen)
# Zeigt den Cache-Pfad
uv run python -c "from whisper_local.transcriber import _model_cache_dir; print(_model_cache_dir())"
Modell-Ordner umbenennen (models → models_bak).
- Schritt 2: App starten und Dialog beobachten
uv run whisper-local
Erwartet:
-
Fenster mit Label
"Lade Whisper-Modell 'small'..."+"Bitte warten…"erscheint nach ca. 500 ms -
Indeterminater Wartebalken animiert durchlaufend
-
Dialog schließt automatisch nach Abschluss des Downloads, Tray-Icon erscheint
-
Schritt 3: Neustart mit gecachtem Modell
uv run whisper-local
Erwartet: kein Dialog erscheint — App startet direkt mit Tray-Icon (Modell < 500 ms bereit).
- Schritt 4: Cache wiederherstellen
models_bak zurück zu models benennen.