docs: Design-Spec für Model-Download-Fortschrittsdialog
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,97 @@
|
|||||||
|
# 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) |
|
||||||
Reference in New Issue
Block a user