feat(splash): splash natif PyInstaller — couvre la décompression onefile
L'exe --onefile décompresse ~720 Mo dans %TEMP% au lancement. Sur Windows, cela prend 15-30 s AVANT que Python ne démarre. Pendant ce temps : - Aucune fenêtre visible (même le splash tkinter existant n'était pas encore exécuté, car il faut d'abord l'import de Python). - L'utilisateur clique parfois plusieurs fois, croit que l'app est plantée. Solution : Splash natif PyInstaller (Splash() dans le .spec). L'image est affichée PAR LE BOOTLOADER de l'exe, AVANT même le démarrage Python. Le texte sous l'image est actualisable via pyi_splash.update_text(), puis fermé via pyi_splash.close() une fois le splash tkinter visible. Changements : - assets/splash.png (480x240) : titre + sous-titre + indication de durée - anonymisation_onefile.spec : Splash() + splash/splash.binaries dans EXE() - launcher.py : import pyi_splash (fallback silencieux en mode dev), helpers _splash_update / _splash_close, fermeture du splash natif dès que le splash tkinter est à l'écran (évite superposition). - .gitignore : exception !assets/** pour versionner l'image du splash (règle générale *.png exclut tout le reste). Effet utilisateur attendu : fenêtre visible IMMÉDIATEMENT au double-clic, avec message "Démarrage en cours — merci de patienter…". Suppression du trou noir de 15-30 s. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -40,6 +40,9 @@ models/
|
||||
*.jpg
|
||||
*.jpeg
|
||||
*.gif
|
||||
# Exception : assets embarqués dans l'exe (splash, icônes…) doivent être versionnés
|
||||
!assets/**
|
||||
!assets
|
||||
*.mp3
|
||||
*.wav
|
||||
*.mp4
|
||||
|
||||
@@ -45,8 +45,27 @@ a = Analysis(
|
||||
noarchive=False,
|
||||
)
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
|
||||
# Splash natif PyInstaller : image affichée AU LANCEMENT DE L'EXE,
|
||||
# avant même que Python démarre. Couvre les ~15-30 s de décompression
|
||||
# du bundle --onefile dans %TEMP% qui laissaient l'écran vide auparavant.
|
||||
# Le launcher ferme le splash via pyi_splash.close() une fois la GUI prête.
|
||||
splash = Splash(
|
||||
os.path.join(app_dir, 'assets', 'splash.png'),
|
||||
binaries=a.binaries,
|
||||
datas=a.datas,
|
||||
text_pos=(10, 215),
|
||||
text_size=10,
|
||||
text_color='white',
|
||||
minify_script=True,
|
||||
always_on_top=False,
|
||||
)
|
||||
|
||||
exe = EXE(
|
||||
pyz, a.scripts, a.binaries, a.zipfiles, a.datas, [],
|
||||
pyz, a.scripts,
|
||||
splash, # image affichée immédiatement
|
||||
splash.binaries, # bootloader splash
|
||||
a.binaries, a.zipfiles, a.datas, [],
|
||||
name='Anonymisation',
|
||||
debug=False,
|
||||
strip=False,
|
||||
|
||||
BIN
assets/splash.png
Normal file
BIN
assets/splash.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
45
launcher.py
45
launcher.py
@@ -9,6 +9,35 @@ from pathlib import Path
|
||||
import threading
|
||||
import logging
|
||||
|
||||
# pyi_splash : module injecté par PyInstaller quand --splash est utilisé.
|
||||
# Permet d'actualiser / fermer le splash natif affiché au démarrage de l'exe
|
||||
# pendant la décompression --onefile (~15-30 s sur Windows). En mode dev
|
||||
# (pas frozen), le module n'existe pas → fallback silencieux.
|
||||
try:
|
||||
import pyi_splash # type: ignore
|
||||
_HAS_PYI_SPLASH = True
|
||||
except Exception:
|
||||
pyi_splash = None
|
||||
_HAS_PYI_SPLASH = False
|
||||
|
||||
|
||||
def _splash_update(text: str) -> None:
|
||||
"""Met à jour le texte affiché sous le splash natif PyInstaller (si actif)."""
|
||||
if _HAS_PYI_SPLASH:
|
||||
try:
|
||||
pyi_splash.update_text(text)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _splash_close() -> None:
|
||||
"""Ferme le splash natif PyInstaller (si actif)."""
|
||||
if _HAS_PYI_SPLASH:
|
||||
try:
|
||||
pyi_splash.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Single-instance guard (lock file in user's temp directory)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -80,10 +109,16 @@ def launch_gui():
|
||||
|
||||
Le chargement des gazetteers (INSEE 200k+ noms, FINESS 100k+ établissements,
|
||||
BDPM 7k+ médicaments) prend 10–30 s. Sans feedback visuel, l'utilisateur
|
||||
croit que l'application est plantée. Le splash permet d'indiquer l'avancée.
|
||||
croit que l'application est plantée. Deux splashes collaborent :
|
||||
- Splash natif PyInstaller (image) : visible AVANT le démarrage Python,
|
||||
pendant la décompression --onefile dans %TEMP%.
|
||||
- Splash tkinter ci-dessous : progressbar + messages pendant l'import
|
||||
des modèles et la construction de la GUI.
|
||||
"""
|
||||
log.info("Launching GUI...")
|
||||
|
||||
_splash_update("Chargement de l'interface…")
|
||||
|
||||
splash = tk.Tk()
|
||||
splash.title("Anonymisation")
|
||||
splash.geometry("440x200")
|
||||
@@ -122,6 +157,11 @@ def launch_gui():
|
||||
|
||||
threading.Thread(target=_do_import, daemon=True).start()
|
||||
|
||||
# Fermer le splash natif PyInstaller dès que le splash tkinter est à l'écran
|
||||
# (sinon les deux se superposent pendant tout l'import).
|
||||
splash.update() # force le rendu initial
|
||||
_splash_close()
|
||||
|
||||
def _poll():
|
||||
# Met à jour le message selon l'étape atteinte par le thread d'import
|
||||
if result.get("step") == "gui" and not result["done"]:
|
||||
@@ -358,8 +398,11 @@ def main():
|
||||
|
||||
try:
|
||||
if check_models_ready():
|
||||
_splash_update("Modèles déjà installés — chargement…")
|
||||
launch_gui()
|
||||
else:
|
||||
_splash_update("Premier lancement — configuration initiale")
|
||||
_splash_close() # laisse place à la SetupWindow qui a sa propre UI
|
||||
setup = SetupWindow()
|
||||
setup.run()
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user