4d163294f4
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
98 lines
4.2 KiB
Markdown
98 lines
4.2 KiB
Markdown
# 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 (0–100 %)
|
||
- 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) |
|