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

98 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```python
# 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) |