Files
whisper-local/docs/superpowers/specs/2026-04-12-model-download-progress-dialog-design.md
T
2026-04-12 12:19:30 +02:00

4.2 KiB
Raw Blame History

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 (damit faster_whisper keine TypeError bekommt)
  • Überschreibt update(n) und schreibt {"file": desc, "n": n, "total": total} in eine queue.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.Progressbar im determinate-Modus (0100 %)
  • Prozentzahl als Text daneben
  • Kein Abbrechen-Button
  • apply_system_theme(root) für konsistentes Aussehen
  • root.resizable(False, False)

load_model_with_progress(model_name, compute_type, download_root) -> WhisperModel — öffentliche Funktion:

  1. Erstellt queue.Queue und startet Daemon-Thread mit WhisperModel(...)
  2. Öffnet tkinter-Fenster erst wenn erste tqdm-Meldung ankommt (kein leerer Dialog bei gecachtem Modell)
  3. Pollt Queue via root.after(50, ...) und aktualisiert Dialog
  4. Bei Sentinel (None in Queue): Dialog schließen, WhisperModel zurückgeben
  5. 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_progress kehrt 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)