Files
whisper-local/docs/superpowers/specs/2026-04-12-model-download-progress-dialog-design.md
T
info bead04ff09 feat(tray): Modell-Lade-Wartebalken plattformübergreifend anzeigen
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>
2026-04-16 20:58:21 +02:00

5.0 KiB
Raw Blame History

Design: Modell-Lade-Wartebalken

Datum: 2026-04-12 Zuletzt geändert: 2026-04-16 Status: Umgesetzt

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 Statusanzeige.

Ziel

Ein kleiner tkinter-Dialog erscheint automatisch, wenn das Laden des Modells länger als 500 ms dauert, und zeigt einen animierten Wartebalken. Ist das Modell bereits vollständig gecacht und wird in unter 500 ms bereit, erscheint kein Dialog.

Verworfener Ansatz: tqdm-Monkey-Patch

Die ursprüngliche Idee war, tqdm.tqdm während des Downloads mit einer eigenen Klasse (TkProgressTqdm) zu ersetzen und echten Byte-Fortschritt anzuzeigen. Dieser Ansatz funktioniert nicht mehr, weil huggingface_hub für große Dateien (model.bin) die Xet-Engine (Rust) nutzt, die Python-tqdm komplett bypasst. Der Dialog blieb dadurch leer, bis der Download fertig war.

Die Klasse TkProgressTqdm existiert noch im Code (mit Unit-Tests), wird aber von der Lade-Funktion nicht mehr aktiviert — sie bleibt als möglicher Fallback erhalten, falls zukünftige faster_whisper-Versionen wieder durch Python-tqdm laufen.

Aktueller Ansatz: Timeout-basierter Wartebalken

  1. Worker-Thread startet WhisperModel(...).
  2. Hauptthread wartet via threading.Event.wait(timeout=0.5).
  3. Ist der Thread innerhalb von 500 ms fertig (vollständiger Cache) → kein Dialog, direkter Rückweg.
  4. Sonst → tkinter-Dialog mit indeterminatem ttk.Progressbar öffnen, bis der Worker signalisiert.

Architektur & Ablauf

main()
  └─ App.__init__()
       ├─ load_model_with_progress(model_name, compute_type, download_root)
       │    ├─ [Daemon-Thread] WhisperModel(...)
       │    └─ [Hauptthread] done_event.wait(0.5)
       │         ├─ sofort fertig → Modell zurückgeben (kein Dialog)
       │         └─ Timeout → tkinter-Fenster + Indeterminate-Progressbar
       │              └─ root.after(100, poll) bis done_event.is_set()
       └─ Transcriber(model=fertig_geladenes_modell)

Dialog läuft plattformübergreifend (Linux + Windows) — tkinter gehört zur Standardbibliothek.

Komponenten

Datei: whisper_local/tray/_download_progress.py

TkProgressTqdm — ungenutzter tqdm.tqdm-Ersatz (historischer Fallback):

  • Erbt von tqdm.tqdm
  • Akkumuliert Fortschritt und schreibt Messages in TkProgressTqdm._queue, falls gesetzt
  • Wird derzeit nicht aktiviert (Xet bypasst tqdm)

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

  1. Startet Daemon-Thread mit WhisperModel(...)
  2. Wartet 500 ms via done_event.wait(timeout=0.5)
  3. Kehrt bei schnellem Abschluss direkt zurück
  4. Öffnet sonst tkinter-Fenster mit indeterminatem ttk.Progressbar
  5. Pollt done_event alle 100 ms via root.after und beendet mainloop() bei Signal
  6. Bei Exception im Worker: messagebox.showerror + sys.exit(1)

Datei: whisper_local/__main__.py

App.__init__() ruft load_model_with_progress() auf (plattformunabhängig) und übergibt das fertige WhisperModel-Objekt an Transcriber.

Datei: whisper_local/transcriber.py

Transcriber.__init__() akzeptiert optional ein bereits geladenes WhisperModel-Objekt via Parameter model: WhisperModel | None = None.

Dialog-Darstellung

  • Titel: "whisper-local Modell wird geladen"
  • Label oben: "Lade Whisper-Modell '<model_name>'..."
  • Label darunter: "Bitte warten…" (grau)
  • ttk.Progressbar im indeterminate-Modus (Länge 320 px, via pb.start(10) animiert)
  • Kein Abbrechen-Button
  • apply_system_theme(root) für konsistentes Aussehen
  • root.resizable(False, False)

Threading-Modell

Thread Aufgabe
Hauptthread done_event.wait(0.5), tkinter mainloop(), Polling via root.after
Daemon-Thread WhisperModel(...)

Kommunikation über threading.Event (kein Monkey-Patch, keine Queue in der Lade-Funktion).

Fehlerbehandlung

  • Kein Internet / Download-Fehler: Exception im Thread → nach done_event.set() im Hauptthread geprüft → messagebox.showerror("Fehler beim Modell-Laden", str(exc))sys.exit(1)
  • Modell bereits gecacht und schnell bereit: done_event.wait(0.5) returnt True → kein Dialog erscheint

Plattform

Kein Plattform-Guard — load_model_with_progress() läuft auf Linux und Windows identisch. tkinter ist Teil der Python-Standardbibliothek und auf beiden Systemen verfügbar.

Dateien

Datei Status
whisper_local/tray/_download_progress.py Umgesetzt (Timeout-basierter Wartebalken)
whisper_local/__main__.py Umgesetzt (Preload plattformübergreifend)
whisper_local/transcriber.py Umgesetzt (optionaler model-Parameter)
tests/test_download_progress.py Vorhanden (deckt ungenutzte TkProgressTqdm ab)
tests/test_transcriber.py Vorhanden (deckt model-Parameter ab)