#!/usr/bin/env bash # ╔══════════════════════════════════════════════════════════════════════════╗ # ║ build.sh – Compilation + installation de TranscribeStation ║ # ║ JT-Tools by Johnny · H3Campus · Mars 2026 ║ # ╚══════════════════════════════════════════════════════════════════════════╝ # # Usage : # ./build.sh Build Linux + raccourci GNOME (défaut) # ./build.sh --linux Build Linux uniquement # ./build.sh --windows Génère build_windows.bat (cross depuis Linux) # ./build.sh --both Linux + Windows # ./build.sh --no-install Build sans créer de raccourcis # ./build.sh --install-only Raccourci seulement (binaire déjà compilé) # ./build.sh --uninstall Supprime le raccourci GNOME # ./build.sh --clean Supprime build/ dist/ icon.* # ./build.sh --help Affiche cette aide # set -euo pipefail IFS=$'\n\t' # ── Constantes ───────────────────────────────────────────────────────────────── APP_NAME="TranscribeStation" APP_VERSION="1.0.0" APP_COMMENT="Gestionnaire de transcription audio – JT-Tools by Johnny" APP_COMMENT_WIN="Gestionnaire de transcription audio - JT-Tools by Johnny" APP_CATS="Audio;AudioVideo;Office;" APP_KEYWORDS="transcription;audio;dictée;pédalier;olympus;" MAIN_SCRIPT="transcribe_station.py" ICON_PNG="icon.png" ICON_ICO="icon.ico" DIST_LINUX="dist/linux" DIST_WINDOWS="dist/windows" VENV_DIR=".venv" HIDDEN_IMPORTS=( "PyQt6.QtMultimedia" "PyQt6.QtCore" "PyQt6.QtGui" "PyQt6.QtWidgets" "soundfile" "numpy" "hid" "cffi" "_cffi_backend" ) # ── Couleurs ─────────────────────────────────────────────────────────────────── R='\033[0;31m' G='\033[0;32m' Y='\033[1;33m' C='\033[0;36m' B='\033[1m' N='\033[0m' ok() { echo -e "${G}[OK]${N} $*"; } info() { echo -e "${C}[..]${N} $*"; } warn() { echo -e "${Y}[!!]${N} $*"; } err() { echo -e "${R}[XX]${N} $*" >&2; } step() { echo -e "\n${B}══ $* ══${N}"; } die() { err "$*"; exit 1; } # ── Détection Python / venv ──────────────────────────────────────────────────── find_python() { if [[ -f "$VENV_DIR/bin/python" ]]; then echo "$VENV_DIR/bin/python" elif command -v python3 &>/dev/null; then echo "$(command -v python3)" else die "python3 introuvable. Installez Python 3.10+." fi } find_pip() { local py; py=$(find_python) echo "$py -m pip" } # ── Validation PyQt6 dans Wine ──────────────────────────────────────────────── check_wine_pyqt_runtime() { local wine_cmd="$1" local wine_python="$2" local wine_run=("env" "WINEDEBUG=-all,err+all" "$wine_cmd") local probe_output local rc probe_output=$("${wine_run[@]}" "$wine_python" -c $'from pathlib import Path\nimport importlib.metadata as md\nimport PyQt6\nqt_dir = Path(PyQt6.__file__).resolve().parent / "Qt6" / "bin"\nmissing = [name for name in ("icuuc.dll", "icuin.dll", "icudt.dll") if not (qt_dir / name).exists()]\nprint("PYQT6_VERSION=" + md.version("PyQt6"))\ntry:\n print("PYQT6_QT6_VERSION=" + md.version("PyQt6-Qt6"))\nexcept Exception:\n print("PYQT6_QT6_VERSION=?")\nprint("QT_BIN=" + str(qt_dir).replace(chr(92), "/"))\nprint("MISSING_DLLS=" + ",".join(missing))\nfrom PyQt6 import QtCore\nprint("QTCORE_OK=1")' 2>&1) rc=$? while IFS= read -r line; do [[ -n "$line" ]] && info "Wine Qt probe : $line" done <<< "$probe_output" if [[ $rc -ne 0 ]]; then local missing_dlls="" missing_dlls=$(printf '%s\n' "$probe_output" | sed -n 's/^MISSING_DLLS=//p' | tail -n1) err "PyQt6 est installé dans Wine, mais QtCore ne se charge pas." [[ -n "$missing_dlls" ]] && err "DLL Qt manquantes dans le wheel Windows : $missing_dlls" err "Le binaire Windows généré via Wine serait invalide." err "Utilisez dist/windows/build_windows.bat sur Windows natif." return 1 fi } # ── Génération des icônes via Python/Pillow ──────────────────────────────────── generate_icons() { step "Génération des icônes" local py; py=$(find_python) if ! $py -c "import PIL" 2>/dev/null; then info "Installation de Pillow..." $py -m pip install --quiet Pillow fi info "Création de $ICON_PNG et $ICON_ICO..." $py - << 'PYICON' from PIL import Image, ImageDraw import sys def draw_logo(draw, sz): draw.rounded_rectangle( [0, 0, sz - 1, sz - 1], radius=int(sz * 0.18), fill=(59, 110, 220, 255), ) bars = [0.25, 0.45, 0.70, 0.90, 0.65, 1.0, 0.55, 0.80, 0.40, 0.20] n, mg = len(bars), sz * 0.13 bw = (sz - 2 * mg) / (n * 1.7) gap = (sz - 2 * mg) / n mid = sz / 2 for i, h in enumerate(bars): x = mg + i * gap + (gap - bw) / 2 bh = h * sz * 0.36 draw.rounded_rectangle( [x, mid - bh, x + bw, mid + bh], radius=bw / 2, fill=(255, 255, 255, 215), ) sizes = [16, 32, 48, 64, 128, 256] images = [] for sz in sizes: img = Image.new("RGBA", (sz, sz), (0, 0, 0, 0)) draw_logo(ImageDraw.Draw(img), sz) images.append(img) images[-1].save("icon.png") images[0].save("icon.ico", format="ICO", sizes=[(s, s) for s in sizes], append_images=images[1:]) print("OK") PYICON [[ -f "$ICON_PNG" ]] && ok "$ICON_PNG généré" || warn "$ICON_PNG absent (Pillow échoué ?)" [[ -f "$ICON_ICO" ]] && ok "$ICON_ICO généré" || warn "$ICON_ICO absent" } # ── Build PyInstaller Linux ──────────────────────────────────────────────────── build_linux() { step "Build Linux (PyInstaller --onefile)" local py; py=$(find_python) # Vérifier / installer PyInstaller if ! $py -m PyInstaller --version &>/dev/null; then info "Installation de PyInstaller..." $py -m pip install --quiet pyinstaller fi generate_icons # Construire les arguments hidden-import local hi_args=() for h in "${HIDDEN_IMPORTS[@]}"; do hi_args+=("--hidden-import=$h") done # Argument icône (optionnel) local icon_arg=() [[ -f "$ICON_ICO" ]] && icon_arg=("--icon=$ICON_ICO") info "Lancement de PyInstaller..." $py -m PyInstaller \ --onefile \ --clean \ --noconfirm \ --name="$APP_NAME" \ --distpath="$DIST_LINUX" \ --workpath="build/linux" \ --specpath="build/linux" \ --log-level=WARN \ "${hi_args[@]}" \ "${icon_arg[@]}" \ "$MAIN_SCRIPT" local binary="$DIST_LINUX/$APP_NAME" if [[ -f "$binary" ]]; then chmod +x "$binary" local size size=$(du -m "$binary" | cut -f1) ok "Binaire : $binary (${size} Mo)" # Copier l'icône PNG à côté du binaire (chemin absolu dans le .desktop) if [[ -f "$ICON_PNG" ]]; then cp "$ICON_PNG" "$DIST_LINUX/$APP_NAME.png" ok "Icône copiée : $DIST_LINUX/$APP_NAME.png" fi else die "Binaire introuvable dans $DIST_LINUX/" fi } # ── Installation raccourci GNOME ─────────────────────────────────────────────── install_gnome_shortcut() { step "Installation raccourci GNOME" local binary binary="$(pwd)/$DIST_LINUX/$APP_NAME" [[ -f "$binary" ]] || die "Binaire introuvable : $binary (lancez d'abord --linux)" # 1. Icône XDG hicolor (prioritaire) + fallback chemin absolu local icon_dir="$HOME/.local/share/icons/hicolor/256x256/apps" local icon_ref="$APP_NAME" # référence symbolique XDG (défaut) local icon_sizes=("16x16" "32x32" "48x48" "64x64" "128x128" "256x256") mkdir -p "$icon_dir" # Chercher l'icône : priorité au dist/linux/ puis au répertoire courant local icon_src="" for candidate in "$DIST_LINUX/$APP_NAME.png" "$ICON_PNG"; do [[ -f "$candidate" ]] && icon_src="$candidate" && break done if [[ -n "$icon_src" ]]; then # Installer dans tous les formats XDG disponibles for sz in "${icon_sizes[@]}"; do local sz_dir="$HOME/.local/share/icons/hicolor/$sz/apps" mkdir -p "$sz_dir" done cp "$icon_src" "$icon_dir/$APP_NAME.png" ok "Icône XDG installée : $icon_dir/$APP_NAME.png" # Fallback : chemin absolu si XDG n'est pas disponible icon_ref="$icon_dir/$APP_NAME.png" else warn "Aucune icône trouvée — raccourci sans icône" warn "Relancez après --linux pour générer $ICON_PNG" fi # 2. Fichier .desktop local apps_dir="$HOME/.local/share/applications" mkdir -p "$apps_dir" local desktop="$apps_dir/$APP_NAME.desktop" cat > "$desktop" << DESKTOP [Desktop Entry] Version=1.0 Type=Application Name=$APP_NAME GenericName=Transcription Audio Comment=$APP_COMMENT Exec=$binary Icon=$icon_ref Terminal=false Categories=$APP_CATS StartupNotify=true StartupWMClass=$APP_NAME Keywords=$APP_KEYWORDS DESKTOP chmod 755 "$desktop" ok ".desktop installé : $desktop" # 3. Raccourci bureau (Bureau / Desktop / Escritorio) local placed=false for bureau_name in "Bureau" "Desktop" "Escritorio"; do local bureau_dir="$HOME/$bureau_name" if [[ -d "$bureau_dir" ]]; then cp "$desktop" "$bureau_dir/$APP_NAME.desktop" chmod 755 "$bureau_dir/$APP_NAME.desktop" ok "Raccourci bureau : $bureau_dir/$APP_NAME.desktop" placed=true break fi done $placed || warn "Dossier Bureau/Desktop introuvable — raccourci bureau ignoré" # 4. Mise à jour des bases de données for cmd_args in \ "update-desktop-database $apps_dir" \ "gtk-update-icon-cache -f -t $HOME/.local/share/icons/hicolor" \ "xdg-desktop-menu forceupdate" do local cmd; cmd=$(echo "$cmd_args" | cut -d' ' -f1) if command -v "$cmd" &>/dev/null; then # shellcheck disable=SC2086 $cmd_args 2>/dev/null && info "Mise à jour : $cmd" || true fi done ok "Raccourci GNOME installé — visible après reconnexion si nécessaire" } # ── Désinstallation raccourci GNOME ─────────────────────────────────────────── uninstall_gnome_shortcut() { step "Désinstallation raccourci GNOME" local removed=false local targets=( "$HOME/.local/share/applications/$APP_NAME.desktop" "$HOME/.local/share/icons/hicolor/256x256/apps/$APP_NAME.png" "$HOME/Bureau/$APP_NAME.desktop" "$HOME/Desktop/$APP_NAME.desktop" "$HOME/Escritorio/$APP_NAME.desktop" ) for t in "${targets[@]}"; do if [[ -e "$t" ]]; then rm -f "$t" ok "Supprimé : $t" removed=true fi done $removed || info "Aucun fichier à supprimer" if command -v update-desktop-database &>/dev/null; then update-desktop-database "$HOME/.local/share/applications" 2>/dev/null || true fi } # ── Génération build_windows.bat ────────────────────────────────────────────── build_windows() { step "Génération du script Windows" mkdir -p "$DIST_WINDOWS" # Construire la liste des --hidden-import pour le .bat local hi_bat="" for h in "${HIDDEN_IMPORTS[@]}"; do hi_bat+=" --hidden-import $h ^\n" done # Construire les arguments hidden-import pour le .bat local hi_bat_args="" for h in "${HIDDEN_IMPORTS[@]}"; do hi_bat_args+=" --hidden-import $h ^\\"$'\n' done cat > "$DIST_WINDOWS/build_windows.bat" << WINBAT @echo off chcp 65001 > nul setlocal EnableDelayedExpansion REM ═══════════════════════════════════════════════════════════════ REM build_windows.bat – $APP_NAME v$APP_VERSION REM JT-Tools by Johnny – H3Campus REM Compatible : Windows natif ET Wine (Linux) REM ═══════════════════════════════════════════════════════════════ echo. echo *** Build $APP_NAME v$APP_VERSION *** echo. REM ── Localiser le dossier projet ─────────────────────────────────────── set "SCRIPT_DIR=%~dp0" for %%I in ("%CD%") do set "PROJECT_ROOT=%%~fI" if exist "%PROJECT_ROOT%\$MAIN_SCRIPT" goto :ROOT_FOUND for %%I in ("%SCRIPT_DIR%\..\..") do set "PROJECT_ROOT=%%~fI" if exist "%PROJECT_ROOT%\$MAIN_SCRIPT" goto :ROOT_FOUND for %%I in ("%SCRIPT_DIR%") do set "PROJECT_ROOT=%%~fI" if exist "%PROJECT_ROOT%\$MAIN_SCRIPT" goto :ROOT_FOUND echo [XX] $MAIN_SCRIPT introuvable. echo [XX] Placez build_windows.bat dans dist\windows\ du projet, echo [XX] ou lancez-le depuis la racine contenant $MAIN_SCRIPT. pause & exit /b 1 :ROOT_FOUND cd /d "%PROJECT_ROOT%" echo [OK] Dossier projet : %PROJECT_ROOT% echo. REM ── Localiser Python (cherche dans les emplacements standard) ───────── set PYTHON_EXE= REM Essayer d'abord "python" dans le PATH python --version >nul 2>&1 if not errorlevel 1 ( set PYTHON_EXE=python & goto :PYTHON_FOUND ) REM Chercher dans les emplacements fixes courants for %%P in ( "C:\Python312\python.exe" "C:\Python311\python.exe" "C:\Python310\python.exe" "C:\Python39\python.exe" "%LOCALAPPDATA%\Programs\Python\Python312\python.exe" "%LOCALAPPDATA%\Programs\Python\Python311\python.exe" "%LOCALAPPDATA%\Programs\Python\Python310\python.exe" "%APPDATA%\..\Local\Programs\Python\Python312\python.exe" ) do ( if exist %%P ( set PYTHON_EXE=%%P & goto :PYTHON_FOUND ) ) REM Utiliser le launcher py si disponible py -3.12 --version >nul 2>&1 && set PYTHON_EXE=py -3.12 & goto :PYTHON_FOUND py -3.11 --version >nul 2>&1 && set PYTHON_EXE=py -3.11 & goto :PYTHON_FOUND py --version >nul 2>&1 && set PYTHON_EXE=py & goto :PYTHON_FOUND echo [XX] Python introuvable. Installez Python 3.12 : echo https://www.python.org/ftp/python/3.12.10/python-3.12.10-amd64.exe pause & exit /b 1 :PYTHON_FOUND for /f "tokens=2" %%v in ('%PYTHON_EXE% --version 2^>^&1') do set PY_FOUND=%%v echo [OK] Python trouve : %PYTHON_EXE% (%PY_FOUND%) echo. REM ── Ajouter Scripts/ au PATH pour que pip/pyinstaller soit accessible ─ for /f "delims=" %%d in ('%PYTHON_EXE% -c "import sysconfig; print(sysconfig.get_path(chr(115)+chr(99)+chr(114)+chr(105)+chr(112)+chr(116)+chr(115)))"') do set PY_SCRIPTS=%%d if defined PY_SCRIPTS ( set PATH=%PY_SCRIPTS%;%PATH% ) REM ── [1/4] Installation des dependances ─────────────────────────────── echo [1/4] Installation des dependances Python... %PYTHON_EXE% -m pip install --upgrade pip setuptools wheel --quiet %PYTHON_EXE% -m pip install pyinstaller PyQt6 PyQt6-Qt6 numpy soundfile hid Pillow ^ --prefer-binary --quiet ^ --trusted-host pypi.org ^ --trusted-host pypi.python.org ^ --trusted-host files.pythonhosted.org if errorlevel 1 ( echo [XX] Echec pip install. Nouvelle tentative sans --quiet... %PYTHON_EXE% -m pip install pyinstaller PyQt6 PyQt6-Qt6 numpy soundfile hid Pillow ^ --prefer-binary ^ --trusted-host pypi.org ^ --trusted-host pypi.python.org ^ --trusted-host files.pythonhosted.org if errorlevel 1 ( echo [XX] ERREUR installation paquets & pause & exit /b 1 ) ) echo [OK] Dependances installees. %PYTHON_EXE% -c "from PyQt6 import QtCore" if errorlevel 1 ( echo [XX] PyQt6 est installe mais QtCore ne se charge pas. echo [XX] Verifiez PyQt6 / PyQt6-Qt6 ou essayez une autre version. pause & exit /b 1 ) echo. REM ── [2/4] Generation des icones (via fichier temp) ─────────────────── echo [2/4] Generation des icones... REM Ecrire le script Python dans un fichier temp (evite les problemes de guillemets cmd) set ICON_PY=%TEMP%\build_icon_%RANDOM%.py ( echo from PIL import Image, ImageDraw echo def logo(d, sz^): echo d.rounded_rectangle([0,0,sz-1,sz-1], radius=int(sz*.18^), fill=(59,110,220,255^)^) echo bars=[.25,.45,.7,.9,.65,1.,.55,.8,.4,.2] echo n,mg=len(bars^),sz*.13; bw=(sz-2*mg^)/(n*1.7^); gap=(sz-2*mg^)/n; mid=sz/2 echo for i,h in enumerate(bars^): echo x=mg+i*gap+(gap-bw^)/2; bh=h*sz*.36 echo d.rounded_rectangle([x,mid-bh,x+bw,mid+bh],radius=bw/2,fill=(255,255,255,215^)^) echo sizes=[16,32,48,64,128,256]; imgs=[] echo for sz in sizes: echo img=Image.new('RGBA',(sz,sz^),(0,0,0,0^)^); logo(ImageDraw.Draw(img^),sz^); imgs.append(img^) echo imgs[-1].save('icon.png'^) echo imgs[0].save('icon.ico',format='ICO',sizes=[(s,s^) for s in sizes],append_images=imgs[1:]^) echo print('[OK] Icones generees'^) ) > "%ICON_PY%" %PYTHON_EXE% "%ICON_PY%" del "%ICON_PY%" 2>nul if not exist icon.ico ( echo [!!] Icone ignoree (Pillow absent ?^) ) else ( echo [OK] icon.ico et icon.png. ) echo. REM ── [3/4] Compilation PyInstaller ──────────────────────────────────── echo [3/4] Compilation PyInstaller... %PYTHON_EXE% -m PyInstaller ^ --onedir --windowed --clean --noconfirm ^ --name $APP_NAME ^ --distpath "%PROJECT_ROOT%\dist\windows" ^ --workpath "%PROJECT_ROOT%\build\windows" ^ --specpath "%PROJECT_ROOT%\build\windows" ^ --hidden-import PyQt6.QtMultimedia ^ --hidden-import PyQt6.QtCore ^ --hidden-import PyQt6.QtGui ^ --hidden-import PyQt6.QtWidgets ^ --hidden-import soundfile ^ --hidden-import numpy ^ --hidden-import hid ^ --hidden-import cffi ^ --hidden-import _cffi_backend ^ --icon "%PROJECT_ROOT%\icon.ico" ^ "%PROJECT_ROOT%\$MAIN_SCRIPT" if errorlevel 1 ( echo [XX] ERREUR PyInstaller & pause & exit /b 1 ) echo [OK] Binaire : %PROJECT_ROOT%\dist\windows\$APP_NAME\$APP_NAME.exe echo. REM ── [4/4] Raccourcis Bureau + Menu Demarrer ─────────────────────────── echo [4/4] Installation des raccourcis... if exist "%PROJECT_ROOT%\dist\windows\install_shortcut.ps1" ( powershell -NoProfile -ExecutionPolicy Bypass ^ -File "%PROJECT_ROOT%\dist\windows\install_shortcut.ps1" ^ -ExePath "%PROJECT_ROOT%\dist\windows\$APP_NAME\$APP_NAME.exe" ) else ( echo [!!] Script PowerShell introuvable : %PROJECT_ROOT%\dist\windows\install_shortcut.ps1 ) echo. echo *** Build termine ! *** echo Binaire : %PROJECT_ROOT%\dist\windows\$APP_NAME\$APP_NAME.exe echo Raccourcis : Bureau + Menu Demarrer echo. pause WINBAT # Script PowerShell standalone pour install raccourci seulement cat > "$DIST_WINDOWS/install_shortcut.ps1" << PSSCRIPT # install_shortcut.ps1 - Install shortcuts for $APP_NAME # Usage : powershell -ExecutionPolicy Bypass -File install_shortcut.ps1 param([string]\$ExePath = "") if (-not \$ExePath) { \$here = Split-Path \$MyInvocation.MyCommand.Path \$ExePath = Join-Path \$here "$APP_NAME\$APP_NAME.exe" if (-not (Test-Path \$ExePath)) { \$ExePath = Join-Path \$here "$APP_NAME.exe" } } if (-not (Test-Path \$ExePath)) { Write-Error "Executable introuvable : \$ExePath" exit 1 } \$exe = Resolve-Path \$ExePath \$desc = "$APP_COMMENT_WIN" \$dirs = @( [Environment]::GetFolderPath("Desktop"), "\$env:APPDATA\Microsoft\Windows\Start Menu\Programs" ) foreach (\$dir in \$dirs) { \$lnk = Join-Path \$dir "$APP_NAME.lnk" \$wsh = New-Object -ComObject WScript.Shell \$sc = \$wsh.CreateShortcut(\$lnk) \$sc.TargetPath = \$exe \$sc.IconLocation = "\$exe,0" \$sc.Description = \$desc \$sc.WorkingDirectory = Split-Path \$exe \$sc.Save() Write-Host "[OK] Raccourci : \$lnk" } Write-Host "" Write-Host "[OK] Installation terminee." PSSCRIPT ok "Script Windows : $DIST_WINDOWS/build_windows.bat" ok "Script PowerShell : $DIST_WINDOWS/install_shortcut.ps1" echo "" warn "Pour Windows natif : copiez dist/windows/ et double-cliquez build_windows.bat" echo "" # ── Compilation via Wine (appels directs, pas via cmd.exe) ─────────────── local wine_cmd wine_cmd=$(command -v wine64 || command -v wine || true) [[ -z "$wine_cmd" ]] && return step "Compilation Windows via Wine" info "Wine : $wine_cmd" # Chercher python.exe dans le préfixe Wine local wine_prefix="${WINEPREFIX:-$HOME/.wine}" local wine_python="" for pyver in Python312 Python311 Python310 Python39; do local candidate="$wine_prefix/drive_c/$pyver/python.exe" if [[ -f "$candidate" ]]; then wine_python="$candidate" break fi done # Chercher aussi dans Program Files if [[ -z "$wine_python" ]]; then for base in "$wine_prefix/drive_c/users/$USER/AppData/Local/Programs" "$wine_prefix/drive_c/Program Files" "$wine_prefix/drive_c/Program Files (x86)"; do for pyver in Python312 Python311 Python310; do local candidate="$base/Python/$pyver/python.exe" [[ -f "$candidate" ]] && wine_python="$candidate" && break 2 done done fi if [[ -z "$wine_python" ]]; then warn "Python Windows introuvable dans Wine ($wine_prefix)." warn "Installez-le avec :" info " wget https://www.python.org/ftp/python/3.12.10/python-3.12.10-amd64.exe" info " wine python-3.12.10-amd64.exe /quiet InstallAllUsers=0 TargetDir=C:\\Python312" info "Puis relancez : ./build.sh --windows" return fi ok "Python Wine : $wine_python" # Chemin Windows pour Wine (Z: = racine Linux sur Wine standard) local wine_py_win wine_py_win=$(winepath -w "$wine_python" 2>/dev/null || echo "$wine_python") # Générer les icônes côté Linux avant la compilation generate_icons # ── Avertissement Wine + numpy ─────────────────────────────────────────────── # ucrtbase.dll.crealf n'est pas implémenté dans Wine → numpy crashe PyInstaller. # PyInstaller découvre les hooks via les entry_points de TOUS les paquets installés. # Si numpy est présent dans le Python Wine, son hook crashe Wine même avec --exclude-module. # Solution : désinstaller numpy/soundfile AVANT PyInstaller, réinstaller si nécessaire. warn "Build Wine : numpy/soundfile seront temporairement désinstallés du Python Wine" warn " (ucrtbase.dll.crealf non implémenté → PyInstaller crasherait)." warn " La waveform sera absente du .exe Wine." warn " Pour un .exe complet → build_windows.bat sur Windows natif." echo "" # WINEDEBUG via variable d'environnement pour Wine lui-même (pas export bash) # Utiliser WINEDEBUG dans la commande wine directement local wine_run=("env" "WINEDEBUG=-all,err+all" "$wine_cmd") local pip_trust=( --trusted-host pypi.org --trusted-host pypi.python.org --trusted-host files.pythonhosted.org ) info "Mise à jour de pip dans Wine..." "${wine_run[@]}" "$wine_python" -m pip install --upgrade pip setuptools wheel --quiet "${pip_trust[@]}" 2>/dev/null || true # ── Désinstaller numpy/soundfile/scipy du Python Wine ──────────────────────── # (ils peuvent rester d'une installation précédente) info "Désinstallation de numpy/soundfile/scipy du Python Wine (anti-crash ucrtbase)..." "${wine_run[@]}" "$wine_python" -m pip uninstall -y numpy soundfile scipy PyInstaller-hooks-contrib 2>/dev/null || true # ── Installer les dépendances sans numpy/soundfile ──────────────────────────── info "Installation PyInstaller + PyQt6 (sans numpy/soundfile)..." "${wine_run[@]}" "$wine_python" -m pip install pyinstaller PyQt6 PyQt6-Qt6 hid Pillow --prefer-binary --quiet "${pip_trust[@]}" if [[ $? -ne 0 ]]; then err "Échec pip install Wine." return fi ok "Dépendances installées (PyQt6 + PyInstaller, sans numpy)." check_wine_pyqt_runtime "$wine_cmd" "$wine_python" || return 1 # ── Vérifier que numpy est bien absent ─────────────────────────────────────── if "${wine_run[@]}" "$wine_python" -c "import numpy" 2>/dev/null; then err "numpy est encore présent dans le Python Wine malgré la désinstallation." err "Désinstallez-le manuellement : wine $wine_python -m pip uninstall -y numpy" return fi ok "numpy absent du Python Wine — PyInstaller peut s'exécuter." info "Lancement de PyInstaller (Windows via Wine)..." # Hidden imports : exclure ce qui crashe Wine local hi_args_wine=() for h in "${HIDDEN_IMPORTS[@]}"; do case "$h" in numpy|soundfile|scipy|cffi|_cffi_backend) ;; *) hi_args_wine+=(--hidden-import "$h") ;; esac done local icon_arg_wine=() [[ -f "$ICON_ICO" ]] && icon_arg_wine=(--icon "$(winepath -w "$(pwd)/$ICON_ICO" 2>/dev/null || echo "$ICON_ICO")") local src_win; src_win=$(winepath -w "$(pwd)/$MAIN_SCRIPT" 2>/dev/null || echo "$MAIN_SCRIPT") local dist_win; dist_win=$(winepath -w "$(pwd)/$DIST_WINDOWS" 2>/dev/null || echo "$DIST_WINDOWS") local build_win; build_win=$(winepath -w "$(pwd)/build/windows" 2>/dev/null || echo "build/windows") "${wine_run[@]}" "$wine_python" -m PyInstaller --onefile --windowed --clean --noconfirm --name="$APP_NAME" --distpath="$dist_win" --workpath="$build_win" --specpath="$build_win" --log-level=WARN --exclude-module numpy --exclude-module soundfile --exclude-module scipy --exclude-module _multiarray_umath "${hi_args_wine[@]}" "${icon_arg_wine[@]}" "$src_win" 2>&1 | grep -v "^0[0-9a-f]*:fixme:\|^0[0-9a-f]*:err:\|WineDbg\|Register dump\|Stack dump\|Backtrace\|Modules:\|Threads:\|System info" local rc=${PIPESTATUS[0]} local binary="$DIST_WINDOWS/$APP_NAME.exe" if [[ -f "$binary" && $rc -eq 0 ]]; then local size; size=$(du -m "$binary" | cut -f1) ok "Binaire Windows : $binary (${size} Mo)" warn "Ce binaire N'INCLUT PAS la waveform (numpy exclu pour compatibilité Wine)." warn "Pour un .exe complet avec waveform :" warn " Copiez build_windows.bat sur une machine Windows et exécutez-le." else err "PyInstaller échoué (code $rc)." err "Solution recommandée : utilisez build_windows.bat sur Windows natif." fi } # ── Nettoyage ────────────────────────────────────────────────────────────────── clean() { step "Nettoyage" local removed=false for target in build dist "$ICON_PNG" "$ICON_ICO" *.spec; do if [[ -e "$target" ]]; then rm -rf "$target" ok "Supprimé : $target" removed=true fi done $removed || info "Rien à nettoyer" } # ── Aide ─────────────────────────────────────────────────────────────────────── usage() { sed -n '2,15p' "$0" | sed 's/^# \{0,1\}//' exit 0 } # ── Résumé final ─────────────────────────────────────────────────────────────── summary() { echo "" echo -e "${B}${G}╔══════════════════════════════════════════╗${N}" echo -e "${B}${G}║ $APP_NAME v$APP_VERSION – Build OK ║${N}" echo -e "${B}${G}╚══════════════════════════════════════════╝${N}" echo "" [[ -f "$DIST_LINUX/$APP_NAME" ]] && \ echo -e " Linux : ${C}$DIST_LINUX/$APP_NAME${N}" [[ -f "$DIST_WINDOWS/build_windows.bat" ]] && \ echo -e " Windows: ${C}$DIST_WINDOWS/build_windows.bat${N}" echo "" } # ── Point d'entrée ───────────────────────────────────────────────────────────── main() { # Aller dans le répertoire du script cd "$(dirname "$(realpath "$0")")" # Vérifications de base [[ $EUID -eq 0 ]] && die "Ne pas exécuter en root." [[ -f "$MAIN_SCRIPT" ]] || die "$MAIN_SCRIPT introuvable dans $(pwd)" local do_linux=false local do_windows=false local do_install=true local do_install_only=false local do_uninstall=false local do_clean=false # Défaut : si aucun argument, build linux [[ $# -eq 0 ]] && do_linux=true && do_install=true while [[ $# -gt 0 ]]; do case "$1" in --linux) do_linux=true ;; --windows) do_windows=true ;; --both) do_linux=true; do_windows=true ;; --no-install) do_install=false ;; --install-only) do_install_only=true ;; --uninstall) do_uninstall=true ;; --clean) do_clean=true ;; --help|-h) usage ;; *) die "Option inconnue : $1 (--help pour l'aide)" ;; esac shift done # ── Actions exclusives ────────────────────────────────────────────────── if $do_clean; then clean; exit 0; fi if $do_uninstall; then uninstall_gnome_shortcut; exit 0; fi if $do_install_only; then generate_icons install_gnome_shortcut exit 0 fi # ── Affichage de l'en-tête ───────────────────────────────────────────── echo -e "\n${B}╔══════════════════════════════════════════════╗${N}" echo -e "${B}║ $APP_NAME – Build v$APP_VERSION ║${N}" echo -e "${B}║ JT-Tools by Johnny · H3Campus ║${N}" echo -e "${B}╚══════════════════════════════════════════════╝${N}" # ── Build Linux ───────────────────────────────────────────────────────── if $do_linux; then build_linux $do_install && install_gnome_shortcut fi # ── Build Windows ─────────────────────────────────────────────────────── if $do_windows; then build_windows fi summary } main "$@"