Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.2 KiB
Design: Model-Download-Fortschrittsdialog
Datum: 2026-04-12
Status: Genehmigt
Problemstellung
Beim ersten Start lädt faster_whisper das Whisper-Modell von HuggingFace herunter (z.B. small ≈ 460 MB). Dieser Vorgang blockiert App.__init__() ohne jede Rückmeldung. Der Nutzer sieht einen eingefrorenen Start ohne Fortschrittsanzeige.
Ziel
Ein kleiner tkinter-Dialog erscheint automatisch während des Downloads und zeigt den echten Byte-Fortschritt an. Ist das Modell bereits gecacht, erscheint kein Dialog.
Ansatz: tqdm-Monkey-Patch
faster_whisper nutzt intern tqdm für Download-Fortschritt. Wir ersetzen tqdm.tqdm kurzzeitig mit einer eigenen Klasse (TkProgressTqdm), die Fortschrittsdaten thread-safe in eine Queue schreibt. Der Hauptthread liest die Queue und aktualisiert den tkinter-Dialog.
Architektur & Ablauf
main()
└─ App.__init__()
├─ [Windows] load_model_with_progress(model_name, compute_type, download_root)
│ ├─ [Daemon-Thread] WhisperModel(...) mit gepatchtem tqdm
│ └─ [Hauptthread] tkinter-Dialog pollt Queue via root.after(50, poll)
│ └─ Sentinel in Queue → Dialog schließt sich, WhisperModel zurückgegeben
└─ Transcriber(model=fertig_geladenes_modell)
Auf Linux lädt Transcriber das Modell direkt wie bisher (kein Dialog).
Komponenten
Neue Datei: whisper_local/tray/_download_progress.py
TkProgressTqdm — ersetzt tqdm.tqdm während des Downloads:
- Erbt von
tqdm.tqdm(damitfaster_whisperkeine TypeError bekommt) - Überschreibt
update(n)und schreibt{"file": desc, "n": n, "total": total}in einequeue.Queue - Thread-safe, kein direkter tkinter-Zugriff aus dem Thread
DownloadProgressDialog — tkinter-Fenster:
- Titel:
"whisper-local – Modell wird geladen" - Label oben:
"Lade Whisper-Modell '<model_name>'..." - Label Mitte: aktueller Dateiname (z.B.
model.bin) ttk.Progressbarimdeterminate-Modus (0–100 %)- Prozentzahl als Text daneben
- Kein Abbrechen-Button
apply_system_theme(root)für konsistentes Aussehenroot.resizable(False, False)
load_model_with_progress(model_name, compute_type, download_root) -> WhisperModel — öffentliche Funktion:
- Erstellt
queue.Queueund startet Daemon-Thread mitWhisperModel(...) - Öffnet tkinter-Fenster erst wenn erste tqdm-Meldung ankommt (kein leerer Dialog bei gecachtem Modell)
- Pollt Queue via
root.after(50, ...)und aktualisiert Dialog - Bei Sentinel (
Nonein Queue): Dialog schließen,WhisperModelzurückgeben - Bei Exception in Queue:
messagebox.showerror+sys.exit(1)
Geänderte Datei: whisper_local/__main__.py
App.__init__() ruft auf Windows load_model_with_progress() auf und übergibt das fertige WhisperModel-Objekt an Transcriber.
Geänderte Datei: whisper_local/transcriber.py
Transcriber.__init__() akzeptiert optional ein bereits geladenes WhisperModel-Objekt via Parameter model: WhisperModel | None = None. Falls übergeben, wird es direkt verwendet statt eines neuen Downloads.
Threading-Modell
| Thread | Aufgabe |
|---|---|
| Hauptthread | tkinter mainloop(), Queue-Polling via root.after |
| Daemon-Thread | WhisperModel(...) mit gepatchtem tqdm |
Der tqdm-Patch ist lokal: vor dem Thread-Start wird tqdm.tqdm ersetzt, nach Thread-Ende wiederhergestellt (auch bei Fehler, via try/finally).
Fehlerbehandlung
- Kein Internet / Download-Fehler: Exception im Thread → via Queue in Hauptthread →
messagebox.showerror("Fehler beim Modell-Download", str(exc))→sys.exit(1) - Modell bereits gecacht: tqdm wird nie aufgerufen → kein Dialog erscheint →
load_model_with_progresskehrt sofort zurück
Plattform-Abgrenzung
# whisper_local/__main__.py
if sys.platform == "win32":
from whisper_local.tray._download_progress import load_model_with_progress
model = load_model_with_progress(...)
else:
model = None # Transcriber lädt selbst
Dateien
| Datei | Änderungstyp |
|---|---|
whisper_local/tray/_download_progress.py |
Neu |
whisper_local/__main__.py |
Geändert (App.init) |
whisper_local/transcriber.py |
Geändert (optionaler model-Parameter) |