2026-04-16 20:56:34 +02:00
# Modell-Lade-Wartebalken — Implementierungsplan
2026-04-12 12:24:34 +02:00
2026-04-16 20:56:34 +02:00
**Status: ** Umgesetzt (Stand 2026-04-16)
**Zuletzt geändert: ** 2026-04-16
2026-04-12 12:24:34 +02:00
2026-04-16 20:56:34 +02:00
**Goal: ** Beim ersten Start zeigt die App einen tkinter-Dialog mit indeterminatem Wartebalken, der erscheint, wenn das Laden des Whisper-Modells länger als 500 ms dauert. Bei schnell bereitem Modell erscheint kein Dialog.
2026-04-12 12:24:34 +02:00
2026-04-16 20:56:34 +02:00
**Architecture: ** Ein Daemon-Thread lädt `WhisperModel` . Der Hauptthread wartet 500 ms via `threading.Event` ; erst bei Timeout öffnet er einen tkinter-Dialog mit indeterminatem `ttk.Progressbar` , der via `root.after(100, poll)` auf `done_event.is_set()` pollt. Ein früher geplanter tqdm-Monkey-Patch (`TkProgressTqdm` ) wurde verworfen, weil `huggingface_hub` für `model.bin` die Xet-Rust-Engine nutzt, die Python-tqdm bypasst.
2026-04-12 12:24:34 +02:00
2026-04-16 20:56:34 +02:00
**Tech Stack: ** Python 3.13+, tkinter, `faster_whisper` , `threading` , `queue` (historisch), `tqdm` (historisch)
2026-04-12 12:24:34 +02:00
---
## Dateien
2026-04-16 20:56:34 +02:00
| Datei | Status | Verantwortlichkeit |
|-------|--------|-------------------|
| `whisper_local/tray/_download_progress.py` | Umgesetzt | `TkProgressTqdm` (Fallback, ungenutzt), `load_model_with_progress()` |
| `whisper_local/transcriber.py` | Umgesetzt | Optionaler `model` -Parameter in `Transcriber.__init__` |
| `whisper_local/__main__.py` | Umgesetzt | Aufruf von `load_model_with_progress` (plattformübergreifend) |
| `tests/test_download_progress.py` | Umgesetzt | Unit-Tests für `TkProgressTqdm` |
| `tests/test_transcriber.py` | Umgesetzt | Test für neuen `model` -Parameter + Signatur-Korrektur |
2026-04-12 12:24:34 +02:00
---
2026-04-16 20:56:34 +02:00
## Historie der Umsetzung
2026-04-12 12:24:34 +02:00
2026-04-16 20:56:34 +02:00
Die Implementierung erfolgte in zwei Phasen:
2026-04-12 12:24:34 +02:00
2026-04-16 20:56:34 +02:00
1. **Initialer Plan (April 2026): ** tqdm-Monkey-Patch mit determinater Progressbar, Dateiname- und Prozentanzeige (Commits `e92f5f5` , `3067499` , `44c8d8e` , `c26dfa3` , `3d9f95b` , `e31230f` ).
2. **Korrektur (Commit `753dbc5`): ** Monkey-Patch verworfen, da Xet-Engine tqdm bypasst. Ersetzt durch Timeout-basierten indeterminaten Wartebalken.
2026-04-12 12:24:34 +02:00
2026-04-16 20:56:34 +02:00
Die Tasks unten beschreiben den umgesetzten Endzustand.
2026-04-12 12:24:34 +02:00
---
2026-04-16 20:56:34 +02:00
## Task 1: `Transcriber` um optionalen `model`-Parameter erweitern *(erledigt)*
2026-04-12 12:24:34 +02:00
**Files: **
2026-04-16 20:56:34 +02:00
- `whisper_local/transcriber.py`
- `tests/test_transcriber.py`
2026-04-12 12:24:34 +02:00
2026-04-16 20:56:34 +02:00
`Transcriber.__init__` akzeptiert `model: WhisperModel | None = None` . Wenn gesetzt, wird `WhisperModel` nicht selbst instanziiert. Test `test_init_with_preloaded_model` prüft diesen Pfad; `test_init_loads_model` wurde an die neue Signatur mit `download_root=None` angepasst.
2026-04-12 12:24:34 +02:00
2026-04-16 20:56:34 +02:00
---
2026-04-12 12:24:34 +02:00
2026-04-16 20:56:34 +02:00
## Task 2: `TkProgressTqdm` implementieren und testen *(erledigt, aber ungenutzt)*
2026-04-12 12:24:34 +02:00
2026-04-16 20:56:34 +02:00
**Files: **
- `whisper_local/tray/_download_progress.py`
- `tests/test_download_progress.py`
2026-04-12 12:24:34 +02:00
2026-04-16 20:56:34 +02:00
Klasse `TkProgressTqdm` erbt von `tqdm.tqdm` , akkumuliert Fortschritt in `_accumulated_n` und schreibt `{"file", "n", "total"}` -Dicts in `TkProgressTqdm._queue` , falls gesetzt. Unit-Tests decken Queue-Weiterleitung, Null-Queue-Fall und Akkumulation ab.
2026-04-12 12:24:34 +02:00
2026-04-16 20:56:34 +02:00
**Hinweis: ** `load_model_with_progress` nutzt die Klasse nicht mehr (Xet-Bypass, siehe Historie). Die Klasse + Tests bleiben als dokumentierter Fallback erhalten — wird `faster_whisper` irgendwann wieder durch Python-tqdm laufen, kann sie reaktiviert werden.
2026-04-12 12:24:34 +02:00
---
2026-04-16 20:56:34 +02:00
## Task 3: `load_model_with_progress` implementieren *(erledigt, Ansatz geändert)*
2026-04-12 12:24:34 +02:00
**Files: **
2026-04-16 20:56:34 +02:00
- `whisper_local/tray/_download_progress.py`
2026-04-12 12:24:34 +02:00
2026-04-16 20:56:34 +02:00
Die finale Version nutzt **kein ** tqdm-Patching. Stattdessen:
2026-04-12 12:24:34 +02:00
``` python
def load_model_with_progress (
model_name : str ,
compute_type : str ,
download_root : str | None ,
) - > Any :
import tkinter as tk
from tkinter import messagebox , ttk
from faster_whisper import WhisperModel
from whisper_local . tray . _theme import apply_system_theme
2026-04-16 20:56:34 +02:00
result : list [ Any ] = [ None ]
2026-04-12 12:24:34 +02:00
error : list [ BaseException | None ] = [ None ]
2026-04-16 20:56:34 +02:00
done_event = threading . Event ( )
2026-04-12 12:24:34 +02:00
def worker ( ) - > None :
try :
result [ 0 ] = WhisperModel (
2026-04-16 20:56:34 +02:00
model_name , compute_type = compute_type , download_root = download_root
2026-04-12 12:24:34 +02:00
)
except Exception as exc :
error [ 0 ] = exc
finally :
2026-04-16 20:56:34 +02:00
done_event . set ( )
2026-04-12 12:24:34 +02:00
thread = threading . Thread ( target = worker , daemon = True )
thread . start ( )
2026-04-16 20:56:34 +02:00
# Kurz warten – schneller Cache-Hit überspringt Dialog
if done_event . wait ( timeout = 0.5 ) :
if error [ 0 ] is not None :
root = tk . Tk ( )
root . withdraw ( )
messagebox . showerror ( " Fehler beim Modell-Laden " , str ( error [ 0 ] ) )
root . destroy ( )
sys . exit ( 1 )
return result [ 0 ]
# Dialog mit indeterminatem Wartebalken
2026-04-12 12:24:34 +02:00
root = tk . Tk ( )
root . title ( " whisper-local – Modell wird geladen " )
root . resizable ( False , False )
apply_system_theme ( root )
frame = ttk . Frame ( root , padding = 16 )
frame . pack ( fill = tk . BOTH , expand = True )
ttk . Label ( frame , text = f " Lade Whisper-Modell ' { model_name } ' ... " ) . pack ( anchor = tk . W )
2026-04-16 20:56:34 +02:00
ttk . Label ( frame , text = " Bitte warten \u2026 " , foreground = " gray " ) . pack (
anchor = tk . W , pady = ( 4 , 8 )
)
2026-04-12 12:24:34 +02:00
2026-04-16 20:56:34 +02:00
pb = ttk . Progressbar ( frame , length = 320 , mode = " indeterminate " )
pb . pack ( fill = tk . X )
pb . start ( 10 )
2026-04-12 12:24:34 +02:00
def poll ( ) - > None :
2026-04-16 20:56:34 +02:00
if done_event . is_set ( ) :
root . quit ( )
return
root . after ( 100 , poll )
root . after ( 100 , poll )
2026-04-12 12:24:34 +02:00
root . mainloop ( )
if error [ 0 ] is not None :
2026-04-16 20:56:34 +02:00
root . withdraw ( )
messagebox . showerror ( " Fehler beim Modell-Laden " , str ( error [ 0 ] ) )
root . destroy ( )
2026-04-12 12:24:34 +02:00
sys . exit ( 1 )
2026-04-16 20:56:34 +02:00
root . destroy ( )
2026-04-12 12:24:34 +02:00
return result [ 0 ]
```
---
2026-04-16 20:56:34 +02:00
## Task 4: `App.__init__` in `__main__.py` anpassen *(erledigt)*
2026-04-12 12:24:34 +02:00
**Files: **
2026-04-16 20:56:34 +02:00
- `whisper_local/__main__.py`
2026-04-12 12:24:34 +02:00
2026-04-16 20:56:34 +02:00
Plattformübergreifend — der anfängliche `sys.platform == "win32"` -Guard wurde entfernt, weil der Dialog auch auf Linux gewünscht ist und tkinter zur Standardbibliothek gehört.
2026-04-12 12:24:34 +02:00
``` python
2026-04-16 20:56:34 +02:00
from whisper_local . tray . _download_progress import load_model_with_progress
from whisper_local . transcriber import _model_cache_dir
_preloaded_model = load_model_with_progress (
2026-04-12 12:24:34 +02:00
model_name = config . whisper_model ,
compute_type = config . compute_type ,
2026-04-16 20:56:34 +02:00
download_root = _model_cache_dir ( ) ,
2026-04-12 12:24:34 +02:00
)
self . transcriber = Transcriber (
model_name = config . whisper_model ,
compute_type = config . compute_type ,
language = config . language ,
model = _preloaded_model ,
)
```
---
2026-04-16 20:56:34 +02:00
## Task 5: Manueller Test *(empfohlen nach Regressionen)*
2026-04-12 12:24:34 +02:00
- [ ] **Schritt 1: Modell-Cache temporär umbenennen (Download erzwingen) **
``` bash
# Zeigt den Cache-Pfad
uv run python -c "from whisper_local.transcriber import _model_cache_dir; print(_model_cache_dir())"
```
2026-04-16 20:56:34 +02:00
Modell-Ordner umbenennen (`models` → `models_bak` ).
2026-04-12 12:24:34 +02:00
- [ ] **Schritt 2: App starten und Dialog beobachten **
```
uv run whisper-local
```
Erwartet:
2026-04-16 20:56:34 +02:00
- Fenster mit Label `"Lade Whisper-Modell 'small'..."` + `"Bitte warten…"` erscheint nach ca. 500 ms
- Indeterminater Wartebalken animiert durchlaufend
- Dialog schließt automatisch nach Abschluss des Downloads, Tray-Icon erscheint
2026-04-12 12:24:34 +02:00
- [ ] **Schritt 3: Neustart mit gecachtem Modell **
```
uv run whisper-local
```
2026-04-16 20:56:34 +02:00
Erwartet: **kein Dialog ** erscheint — App startet direkt mit Tray-Icon (Modell < 500 ms bereit).
2026-04-12 12:24:34 +02:00
- [ ] **Schritt 4: Cache wiederherstellen **
2026-04-16 20:56:34 +02:00
`models_bak` zurück zu `models` benennen.