# 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 ''..."` - 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) |