Compare commits
4 Commits
Windows_Ve
...
main
| Author | SHA1 | Date |
|---|---|---|
|
|
4af7c4c47f | |
|
|
514b067153 | |
|
|
850303fad7 | |
|
|
5ee99efaff |
|
|
@ -0,0 +1,53 @@
|
||||||
|
[Setup]
|
||||||
|
AppName=TranscribeStation
|
||||||
|
AppVersion=1.2.0
|
||||||
|
AppVerName=TranscribeStation 1.2.0
|
||||||
|
AppPublisher=H3Campus
|
||||||
|
AppPublisherURL=https://github.com/h3campus/transcribe-station
|
||||||
|
AppSupportURL=https://github.com/h3campus/transcribe-station
|
||||||
|
DefaultDirName={localappdata}\Programs\TranscribeStation
|
||||||
|
DefaultGroupName=TranscribeStation
|
||||||
|
AllowNoIcons=yes
|
||||||
|
OutputDir=C:\ts\installer
|
||||||
|
OutputBaseFilename=TranscribeStation_Setup_v1.2.0
|
||||||
|
SetupIconFile=C:\ts\icon.ico
|
||||||
|
Compression=lzma2/ultra64
|
||||||
|
SolidCompression=yes
|
||||||
|
WizardStyle=modern
|
||||||
|
WizardSizePercent=120
|
||||||
|
PrivilegesRequired=lowest
|
||||||
|
ArchitecturesAllowed=x64compatible
|
||||||
|
ArchitecturesInstallIn64BitMode=x64compatible
|
||||||
|
UninstallDisplayIcon={app}\TranscribeStation.exe
|
||||||
|
UninstallDisplayName=TranscribeStation 1.2.0
|
||||||
|
CloseApplications=yes
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "french"; MessagesFile: "compiler:Languages\French.isl"
|
||||||
|
|
||||||
|
[Tasks]
|
||||||
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Source: "C:\ts\dist\TranscribeStation\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{group}\TranscribeStation"; Filename: "{app}\TranscribeStation.exe"
|
||||||
|
Name: "{userdesktop}\TranscribeStation"; Filename: "{app}\TranscribeStation.exe"; Tasks: desktopicon
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
Filename: "{app}\TranscribeStation.exe"; Description: "Lancer TranscribeStation"; Flags: nowait postinstall skipifsilent
|
||||||
|
|
||||||
|
[UninstallDelete]
|
||||||
|
Type: filesandordirs; Name: "{app}"
|
||||||
|
|
||||||
|
[Code]
|
||||||
|
// Verifie que l'architecture est x64
|
||||||
|
function InitializeSetup(): Boolean;
|
||||||
|
begin
|
||||||
|
Result := True;
|
||||||
|
if not Is64BitInstallMode then begin
|
||||||
|
MsgBox('TranscribeStation necessite Windows 64 bits.', mbError, MB_OK);
|
||||||
|
Result := False;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
sizes = [16, 32, 48, 64, 128, 256]
|
||||||
|
imgs = []
|
||||||
|
for sz in sizes:
|
||||||
|
img = Image.new('RGBA', (sz, sz), (0, 0, 0, 0))
|
||||||
|
d = ImageDraw.Draw(img)
|
||||||
|
d.rounded_rectangle([0, 0, sz-1, sz-1], radius=int(sz*.18), fill=(59, 110, 220, 255))
|
||||||
|
bars = [.25, .45, .7, .9, .65, 1., .55, .8, .4, .2]
|
||||||
|
n, mg = len(bars), sz*.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 * .36
|
||||||
|
d.rounded_rectangle([x, mid-bh, x+bw, mid+bh], radius=bw/2, fill=(255, 255, 255, 215))
|
||||||
|
imgs.append(img)
|
||||||
|
imgs[-1].save('icon.png')
|
||||||
|
imgs[0].save('icon.ico', format='ICO', sizes=[(s,s) for s in sizes], append_images=imgs[1:])
|
||||||
|
print('[OK] Icones generees')
|
||||||
|
|
@ -0,0 +1,851 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
build.py – Compilation + installation de TranscribeStation
|
||||||
|
Produit un binaire onefile PyInstaller et installe les raccourcis système.
|
||||||
|
|
||||||
|
Usage :
|
||||||
|
python build.py # build + install (plateforme courante)
|
||||||
|
python build.py --linux # build Linux
|
||||||
|
python build.py --windows # build Windows (Wine ou .bat natif)
|
||||||
|
python build.py --both # Linux + Windows
|
||||||
|
python build.py --install-only # installe sans recompiler
|
||||||
|
python build.py --clean # nettoyer build/ dist/ *.spec icon.*
|
||||||
|
|
||||||
|
Raccourcis créés :
|
||||||
|
Linux → ~/.local/share/applications/TranscribeStation.desktop
|
||||||
|
~/.local/share/icons/hicolor/256x256/apps/TranscribeStation.png
|
||||||
|
~/Bureau/TranscribeStation.desktop (si dossier existe)
|
||||||
|
Windows → %APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\TranscribeStation.lnk
|
||||||
|
%USERPROFILE%\\Desktop\\TranscribeStation.lnk
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
import argparse
|
||||||
|
import textwrap
|
||||||
|
import urllib.request
|
||||||
|
import zipfile
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# ─── Configuration ────────────────────────────────────────────────────────────
|
||||||
|
APP_NAME = "TranscribeStation"
|
||||||
|
APP_VERSION = "1.2.0"
|
||||||
|
APP_AUTHOR = "H3Campus"
|
||||||
|
APP_URL = "https://github.com/h3campus/transcribe-station"
|
||||||
|
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;"
|
||||||
|
MAIN_SCRIPT = "transcribe_station.py"
|
||||||
|
ICON_ICO = Path("icon.ico")
|
||||||
|
ICON_PNG = Path("icon.png")
|
||||||
|
BUILD_DIR = Path("build")
|
||||||
|
DIST_DIR = Path("dist")
|
||||||
|
|
||||||
|
HIDDEN_IMPORTS = [
|
||||||
|
"PySide6.QtMultimedia",
|
||||||
|
"PySide6.QtCore",
|
||||||
|
"PySide6.QtGui",
|
||||||
|
"PySide6.QtWidgets",
|
||||||
|
"soundfile",
|
||||||
|
"numpy",
|
||||||
|
"hid",
|
||||||
|
"cffi",
|
||||||
|
"_cffi_backend",
|
||||||
|
]
|
||||||
|
|
||||||
|
# --collect-all hid : inclut la DLL native hidapi.dll dans le bundle Windows
|
||||||
|
COLLECT_ALL = ["hid"]
|
||||||
|
|
||||||
|
ADD_DATA: list[str] = []
|
||||||
|
|
||||||
|
# hidapi.dll natif Windows (absent du package pip hid 1.x qui est un pur wrapper ctypes)
|
||||||
|
HIDAPI_VERSION = "0.14.0"
|
||||||
|
HIDAPI_DLL_URL = (
|
||||||
|
f"https://github.com/libusb/hidapi/releases/download/"
|
||||||
|
f"hidapi-{HIDAPI_VERSION}/hidapi-win.zip"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ─── Couleurs terminal ─────────────────────────────────────────────────────────
|
||||||
|
R='\033[0;31m'; G='\033[0;32m'; Y='\033[1;33m'
|
||||||
|
C='\033[0;36m'; B='\033[1m'; N='\033[0m'
|
||||||
|
def ok(m): print(f"{G}[OK]{N} {m}")
|
||||||
|
def info(m):print(f"{C}[..]{N} {m}")
|
||||||
|
def warn(m):print(f"{Y}[!!]{N} {m}")
|
||||||
|
def err(m): print(f"{R}[XX]{N} {m}", file=sys.stderr)
|
||||||
|
def step(m):print(f"\n{B}{m}{N}")
|
||||||
|
|
||||||
|
# ─── hidapi.dll ───────────────────────────────────────────────────────────────
|
||||||
|
def _fetch_hidapi_dll() -> Path | None:
|
||||||
|
"""Télécharge hidapi.dll (x64) depuis les releases GitHub, mise en cache dans build/."""
|
||||||
|
dll_cache = BUILD_DIR / "hidapi.dll"
|
||||||
|
if dll_cache.exists():
|
||||||
|
ok(f"hidapi.dll en cache : {dll_cache}")
|
||||||
|
return dll_cache
|
||||||
|
info(f"Téléchargement hidapi.dll v{HIDAPI_VERSION} depuis GitHub...")
|
||||||
|
BUILD_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
try:
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp:
|
||||||
|
tmp_path = tmp.name
|
||||||
|
urllib.request.urlretrieve(HIDAPI_DLL_URL, tmp_path)
|
||||||
|
with zipfile.ZipFile(tmp_path) as zf:
|
||||||
|
candidates = [n for n in zf.namelist()
|
||||||
|
if n.lower().endswith("hidapi.dll") and "x64" in n.lower()]
|
||||||
|
if not candidates:
|
||||||
|
candidates = [n for n in zf.namelist()
|
||||||
|
if n.lower().endswith("hidapi.dll")]
|
||||||
|
if candidates:
|
||||||
|
with zf.open(candidates[0]) as src, open(dll_cache, "wb") as dst:
|
||||||
|
dst.write(src.read())
|
||||||
|
ok(f"hidapi.dll extrait ({candidates[0]}) → {dll_cache}")
|
||||||
|
return dll_cache
|
||||||
|
warn("hidapi.dll introuvable dans l'archive")
|
||||||
|
except Exception as exc:
|
||||||
|
warn(f"Échec téléchargement hidapi.dll : {exc}")
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
os.unlink(tmp_path)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _install_hidapi_dll_onedir(internal_dir: Path) -> None:
|
||||||
|
"""Copie hidapi.dll dans _internal/ d'un bundle onedir."""
|
||||||
|
if not internal_dir.exists():
|
||||||
|
warn(f"Dossier _internal introuvable : {internal_dir}")
|
||||||
|
return
|
||||||
|
dll = _fetch_hidapi_dll()
|
||||||
|
if dll:
|
||||||
|
dest = internal_dir / "hidapi.dll"
|
||||||
|
shutil.copy2(dll, dest)
|
||||||
|
ok(f"hidapi.dll installé → {dest}")
|
||||||
|
else:
|
||||||
|
warn("hidapi.dll non disponible — le pédalier ne fonctionnera pas sans cette DLL.")
|
||||||
|
|
||||||
|
|
||||||
|
# ─── Helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
def run(cmd: list, check=True) -> int:
|
||||||
|
info(" ".join(str(c) for c in cmd))
|
||||||
|
r = subprocess.run(cmd, check=False)
|
||||||
|
if check and r.returncode != 0:
|
||||||
|
err(f"Échec (code {r.returncode})")
|
||||||
|
sys.exit(r.returncode)
|
||||||
|
return r.returncode
|
||||||
|
|
||||||
|
def pip_install(pkgs: list, python=sys.executable):
|
||||||
|
run([python, "-m", "pip", "install", "--quiet"] + pkgs)
|
||||||
|
|
||||||
|
def validate_wine_pyside(wine: str, wine_python: str) -> bool:
|
||||||
|
probe = subprocess.run(
|
||||||
|
[
|
||||||
|
wine, wine_python, "-c",
|
||||||
|
(
|
||||||
|
"from pathlib import Path; "
|
||||||
|
"import importlib.metadata as md; "
|
||||||
|
"import PySide6; "
|
||||||
|
"qt_dir = Path(PySide6.__file__).resolve().parent; "
|
||||||
|
"missing = [name for name in ('Qt6Core.dll', 'Qt6Multimedia.dll') if not (qt_dir / name).exists()]; "
|
||||||
|
"print('PYSIDE6_VERSION=' + md.version('PySide6')); "
|
||||||
|
"print('QT_DIR=' + str(qt_dir).replace(chr(92), '/')); "
|
||||||
|
"print('MISSING_DLLS=' + ','.join(missing)); "
|
||||||
|
"from PySide6 import QtCore; "
|
||||||
|
"print('QTCORE_OK=1')"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
check=False,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
output = (probe.stdout or "") + (probe.stderr or "")
|
||||||
|
for line in output.splitlines():
|
||||||
|
if line.strip():
|
||||||
|
info(f"Wine Qt probe : {line}")
|
||||||
|
if probe.returncode != 0:
|
||||||
|
missing = ""
|
||||||
|
for line in output.splitlines():
|
||||||
|
if line.startswith("MISSING_DLLS="):
|
||||||
|
missing = line.split("=", 1)[1].strip()
|
||||||
|
break
|
||||||
|
err("PySide6 est installé dans Wine, mais QtCore ne se charge pas.")
|
||||||
|
if missing:
|
||||||
|
err(f"DLL Qt manquantes dans le wheel Windows : {missing}")
|
||||||
|
err("Le binaire Windows généré via Wine serait invalide.")
|
||||||
|
err("Utilisez le script build_windows.bat sur Windows natif.")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
# ─── Génération d'icônes ──────────────────────────────────────────────────────
|
||||||
|
def _draw_icon(draw, sz):
|
||||||
|
"""Dessine le logo waveform TranscribeStation."""
|
||||||
|
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),
|
||||||
|
)
|
||||||
|
|
||||||
|
def generate_icons() -> bool:
|
||||||
|
"""Génère icon.png (256px) et icon.ico (multi-tailles). Retourne True si OK."""
|
||||||
|
try:
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
except ImportError:
|
||||||
|
warn("Pillow absent → icônes ignorées (pip install Pillow)")
|
||||||
|
return False
|
||||||
|
|
||||||
|
sizes_ico = [16, 32, 48, 64, 128, 256]
|
||||||
|
images = []
|
||||||
|
for sz in sizes_ico:
|
||||||
|
img = Image.new("RGBA", (sz, sz), (0, 0, 0, 0))
|
||||||
|
_draw_icon(ImageDraw.Draw(img), sz)
|
||||||
|
images.append(img)
|
||||||
|
|
||||||
|
# PNG 256 px pour Linux XDG
|
||||||
|
images[-1].save(str(ICON_PNG))
|
||||||
|
ok(f"Icône PNG : {ICON_PNG}")
|
||||||
|
|
||||||
|
# ICO multi-tailles pour Windows
|
||||||
|
images[0].save(
|
||||||
|
str(ICON_ICO), format="ICO",
|
||||||
|
sizes=[(s, s) for s in sizes_ico],
|
||||||
|
append_images=images[1:],
|
||||||
|
)
|
||||||
|
ok(f"Icône ICO : {ICON_ICO}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# ─── Raccourci GNOME / Linux ──────────────────────────────────────────────────
|
||||||
|
def install_linux_shortcut(binary: Path):
|
||||||
|
step("📌 Installation du raccourci GNOME...")
|
||||||
|
|
||||||
|
binary = binary.resolve()
|
||||||
|
|
||||||
|
# 1. Icône → XDG hicolor
|
||||||
|
xdg_icon_dir = Path.home() / ".local/share/icons/hicolor/256x256/apps"
|
||||||
|
xdg_icon_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
xdg_icon = xdg_icon_dir / f"{APP_NAME}.png"
|
||||||
|
if ICON_PNG.exists():
|
||||||
|
shutil.copy(ICON_PNG, xdg_icon)
|
||||||
|
ok(f"Icône installée : {xdg_icon}")
|
||||||
|
else:
|
||||||
|
warn("icon.png absent — raccourci sans icône")
|
||||||
|
|
||||||
|
# 2. Fichier .desktop → applications
|
||||||
|
apps_dir = Path.home() / ".local/share/applications"
|
||||||
|
apps_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
desktop_path = apps_dir / f"{APP_NAME}.desktop"
|
||||||
|
|
||||||
|
desktop_content = textwrap.dedent(f"""\
|
||||||
|
[Desktop Entry]
|
||||||
|
Version=1.0
|
||||||
|
Type=Application
|
||||||
|
Name={APP_NAME}
|
||||||
|
GenericName=Transcription Audio
|
||||||
|
Comment={APP_COMMENT}
|
||||||
|
Exec={binary}
|
||||||
|
Icon={APP_NAME}
|
||||||
|
Terminal=false
|
||||||
|
Categories={APP_CATS}
|
||||||
|
StartupNotify=true
|
||||||
|
StartupWMClass={APP_NAME}
|
||||||
|
Keywords=transcription;audio;dictée;pédalier;
|
||||||
|
""")
|
||||||
|
desktop_path.write_text(desktop_content, encoding="utf-8")
|
||||||
|
desktop_path.chmod(0o755)
|
||||||
|
ok(f".desktop installé : {desktop_path}")
|
||||||
|
|
||||||
|
# 3. Raccourci Bureau (Desktop)
|
||||||
|
for bureau in ["Bureau", "Desktop", "Escritorio"]:
|
||||||
|
bureau_dir = Path.home() / bureau
|
||||||
|
if bureau_dir.is_dir():
|
||||||
|
bureau_link = bureau_dir / f"{APP_NAME}.desktop"
|
||||||
|
shutil.copy(desktop_path, bureau_link)
|
||||||
|
bureau_link.chmod(0o755)
|
||||||
|
ok(f"Raccourci bureau : {bureau_link}")
|
||||||
|
break
|
||||||
|
|
||||||
|
# 4. Mettre à jour la base des .desktop
|
||||||
|
for cmd in [
|
||||||
|
["update-desktop-database", str(apps_dir)],
|
||||||
|
["gtk-update-icon-cache", "-f", "-t",
|
||||||
|
str(Path.home() / ".local/share/icons/hicolor")],
|
||||||
|
["xdg-desktop-menu", "forceupdate"],
|
||||||
|
]:
|
||||||
|
if shutil.which(cmd[0]):
|
||||||
|
run(cmd, check=False)
|
||||||
|
|
||||||
|
ok("Raccourci GNOME installé. Visible au prochain rechargement de session.")
|
||||||
|
|
||||||
|
def uninstall_linux_shortcut():
|
||||||
|
step("🗑 Désinstallation raccourci GNOME...")
|
||||||
|
targets = [
|
||||||
|
Path.home() / f".local/share/applications/{APP_NAME}.desktop",
|
||||||
|
Path.home() / f".local/share/icons/hicolor/256x256/apps/{APP_NAME}.png",
|
||||||
|
]
|
||||||
|
for bureau in ["Bureau", "Desktop", "Escritorio"]:
|
||||||
|
targets.append(Path.home() / bureau / f"{APP_NAME}.desktop")
|
||||||
|
for t in targets:
|
||||||
|
if t.exists():
|
||||||
|
t.unlink()
|
||||||
|
ok(f"Supprimé : {t}")
|
||||||
|
|
||||||
|
# ─── Raccourci Windows (depuis Windows natif) ─────────────────────────────────
|
||||||
|
def install_windows_shortcut(binary: Path):
|
||||||
|
"""Crée Menu Démarrer + Bureau via PowerShell (à appeler depuis Windows)."""
|
||||||
|
step("📌 Installation du raccourci Windows...")
|
||||||
|
binary = binary.resolve()
|
||||||
|
|
||||||
|
# param() en premiere ligne : ExePath est passe par le bat via -ExePath "..."
|
||||||
|
# Ne PAS hardcoder $ExePath dans le corps du script (serait ecrase par la ligne param)
|
||||||
|
ps_script = textwrap.dedent(f"""
|
||||||
|
param([string]$ExePath)
|
||||||
|
$AppName = "{APP_NAME}"
|
||||||
|
$Comment = "{APP_COMMENT_WIN}"
|
||||||
|
|
||||||
|
function New-Shortcut([string]$dest) {{
|
||||||
|
$wsh = New-Object -ComObject WScript.Shell
|
||||||
|
$lnk = $wsh.CreateShortcut($dest)
|
||||||
|
$lnk.TargetPath = $ExePath
|
||||||
|
$lnk.IconLocation = "$ExePath,0"
|
||||||
|
$lnk.Description = $Comment
|
||||||
|
$lnk.WorkingDirectory = [System.IO.Path]::GetDirectoryName($ExePath)
|
||||||
|
$lnk.Save()
|
||||||
|
Write-Host "[OK] Raccourci : $dest"
|
||||||
|
}}
|
||||||
|
|
||||||
|
$startMenu = "$env:APPDATA\\Microsoft\\Windows\\Start Menu\\Programs"
|
||||||
|
New-Shortcut "$startMenu\\$AppName.lnk"
|
||||||
|
|
||||||
|
$desktop = [Environment]::GetFolderPath("Desktop")
|
||||||
|
New-Shortcut "$desktop\\$AppName.lnk"
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "[OK] Shortcuts installed successfully."
|
||||||
|
""").strip()
|
||||||
|
|
||||||
|
ps_file = DIST_DIR / "windows" / "install_shortcut.ps1"
|
||||||
|
ps_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
ps_file.write_text(ps_script, encoding="utf-8-sig")
|
||||||
|
ok(f"Script PowerShell généré : {ps_file}")
|
||||||
|
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
info("Exécution de PowerShell...")
|
||||||
|
run(["powershell", "-ExecutionPolicy", "Bypass",
|
||||||
|
"-File", str(ps_file)], check=False)
|
||||||
|
else:
|
||||||
|
warn("Cross-compilation : exécutez ce script sur la machine Windows cible :")
|
||||||
|
warn(f" powershell -ExecutionPolicy Bypass -File {ps_file}")
|
||||||
|
|
||||||
|
# ─── Build Linux ──────────────────────────────────────────────────────────────
|
||||||
|
def build_linux(install: bool = True):
|
||||||
|
step("🐧 Build Linux")
|
||||||
|
pip_install(["pyinstaller", "Pillow"])
|
||||||
|
generate_icons()
|
||||||
|
|
||||||
|
run([sys.executable, "-m", "PyInstaller",
|
||||||
|
"--onefile", "--clean", "--noconfirm",
|
||||||
|
f"--name={APP_NAME}",
|
||||||
|
"--distpath=dist/linux",
|
||||||
|
"--workpath=build/linux",
|
||||||
|
"--specpath=build/linux",
|
||||||
|
"--log-level=WARN",
|
||||||
|
*(f"--hidden-import={h}" for h in HIDDEN_IMPORTS),
|
||||||
|
*(f"--collect-all={m}" for m in COLLECT_ALL),
|
||||||
|
*(f"--add-data={d}" for d in ADD_DATA),
|
||||||
|
f"--icon={ICON_ICO}" if ICON_ICO.exists() else "",
|
||||||
|
MAIN_SCRIPT,
|
||||||
|
])
|
||||||
|
|
||||||
|
binary = Path(f"dist/linux/{APP_NAME}")
|
||||||
|
if binary.exists():
|
||||||
|
binary.chmod(0o755)
|
||||||
|
size = binary.stat().st_size // (1024 * 1024)
|
||||||
|
ok(f"Binaire Linux : {binary} ({size} Mo)")
|
||||||
|
if install:
|
||||||
|
install_linux_shortcut(binary)
|
||||||
|
else:
|
||||||
|
err("Binaire introuvable dans dist/linux/")
|
||||||
|
|
||||||
|
# ─── Build Windows ────────────────────────────────────────────────────────────
|
||||||
|
def build_windows(install: bool = True):
|
||||||
|
step("🪟 Build Windows")
|
||||||
|
|
||||||
|
# ── Sur machine Windows native ────────────────────────────────────────────
|
||||||
|
if platform.system() == "Windows":
|
||||||
|
pip_install(["pyinstaller", "Pillow"])
|
||||||
|
generate_icons()
|
||||||
|
icon_arg = f"--icon={ICON_ICO.resolve()}" if ICON_ICO.exists() else ""
|
||||||
|
main_script = str(Path(MAIN_SCRIPT).resolve())
|
||||||
|
run([sys.executable, "-m", "PyInstaller",
|
||||||
|
"--onedir", "--clean", "--noconfirm", "--windowed",
|
||||||
|
f"--name={APP_NAME}",
|
||||||
|
"--distpath=dist/windows",
|
||||||
|
"--workpath=build/windows",
|
||||||
|
"--specpath=build/windows",
|
||||||
|
"--log-level=WARN",
|
||||||
|
*(f"--hidden-import={h}" for h in HIDDEN_IMPORTS),
|
||||||
|
*(f"--collect-all={m}" for m in COLLECT_ALL),
|
||||||
|
icon_arg,
|
||||||
|
main_script,
|
||||||
|
])
|
||||||
|
binary = Path(f"dist/windows/{APP_NAME}/{APP_NAME}.exe")
|
||||||
|
if binary.exists():
|
||||||
|
ok(f"Binaire Windows : {binary}")
|
||||||
|
_install_hidapi_dll_onedir(Path(f"dist/windows/{APP_NAME}/_internal"))
|
||||||
|
if install:
|
||||||
|
install_windows_shortcut(binary)
|
||||||
|
else:
|
||||||
|
err("Binaire introuvable.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# ── Cross-compilation depuis Linux via Wine ────────────────────────────────
|
||||||
|
wine = shutil.which("wine") or shutil.which("wine64")
|
||||||
|
wine_python = None
|
||||||
|
if wine:
|
||||||
|
for candidate in [
|
||||||
|
Path.home() / ".wine/drive_c/Python312/python.exe",
|
||||||
|
Path.home() / ".wine/drive_c/Python311/python.exe",
|
||||||
|
Path.home() / ".wine/drive_c/Python310/python.exe",
|
||||||
|
]:
|
||||||
|
if candidate.exists():
|
||||||
|
wine_python = candidate
|
||||||
|
break
|
||||||
|
|
||||||
|
if wine and wine_python:
|
||||||
|
info(f"Wine Python : {wine_python}")
|
||||||
|
pip_install(["pyinstaller", "PySide6", "numpy", "soundfile", "hid", "Pillow"],
|
||||||
|
python=str(wine_python))
|
||||||
|
generate_icons()
|
||||||
|
if not validate_wine_pyside(wine, str(wine_python)):
|
||||||
|
sys.exit(1)
|
||||||
|
# Récupérer hidapi.dll pour l'embarquer dans le onefile via --add-binary
|
||||||
|
hidapi_dll = _fetch_hidapi_dll()
|
||||||
|
add_binary_args = [f"--add-binary={hidapi_dll}:."] if hidapi_dll else []
|
||||||
|
if not hidapi_dll:
|
||||||
|
warn("hidapi.dll non disponible — le pédalier ne fonctionnera pas dans le .exe Wine")
|
||||||
|
run([wine, str(wine_python), "-m", "PyInstaller",
|
||||||
|
"--onefile", "--clean", "--noconfirm", "--windowed",
|
||||||
|
f"--name={APP_NAME}",
|
||||||
|
"--distpath=dist/windows",
|
||||||
|
"--workpath=build/windows",
|
||||||
|
"--specpath=build/windows",
|
||||||
|
"--log-level=WARN",
|
||||||
|
*(f"--hidden-import={h}" for h in HIDDEN_IMPORTS),
|
||||||
|
*(f"--collect-all={m}" for m in COLLECT_ALL),
|
||||||
|
*add_binary_args,
|
||||||
|
f"--icon={ICON_ICO}" if ICON_ICO.exists() else "",
|
||||||
|
MAIN_SCRIPT,
|
||||||
|
])
|
||||||
|
binary = Path(f"dist/windows/{APP_NAME}.exe")
|
||||||
|
if binary.exists():
|
||||||
|
size = binary.stat().st_size // (1024 * 1024)
|
||||||
|
ok(f"Binaire Windows : {binary} ({size} Mo)")
|
||||||
|
install_windows_shortcut(Path(f"dist/windows/{APP_NAME}.exe"))
|
||||||
|
else:
|
||||||
|
warn("Wine ou Python Windows introuvable → génération du script natif Windows")
|
||||||
|
_write_windows_bat(install=install)
|
||||||
|
|
||||||
|
def _write_windows_bat(install: bool = True):
|
||||||
|
"""Genere build_windows.bat + _build_icons.py dans dist/ pour execution native sur Windows."""
|
||||||
|
out_dir = Path("dist") / "windows"
|
||||||
|
out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
bat_path = out_dir / "build_windows.bat"
|
||||||
|
icons_path = out_dir / "_build_icons.py"
|
||||||
|
|
||||||
|
# ── Script Python icones dans un fichier separe (evite l'interpretation batch) ──
|
||||||
|
icon_py = (
|
||||||
|
"from PIL import Image, ImageDraw\n"
|
||||||
|
"sizes = [16, 32, 48, 64, 128, 256]\n"
|
||||||
|
"imgs = []\n"
|
||||||
|
"for sz in sizes:\n"
|
||||||
|
" img = Image.new('RGBA', (sz, sz), (0, 0, 0, 0))\n"
|
||||||
|
" d = ImageDraw.Draw(img)\n"
|
||||||
|
" d.rounded_rectangle([0, 0, sz-1, sz-1], radius=int(sz*.18), fill=(59, 110, 220, 255))\n"
|
||||||
|
" bars = [.25, .45, .7, .9, .65, 1., .55, .8, .4, .2]\n"
|
||||||
|
" n, mg = len(bars), sz*.13\n"
|
||||||
|
" bw = (sz - 2*mg) / (n * 1.7)\n"
|
||||||
|
" gap = (sz - 2*mg) / n\n"
|
||||||
|
" mid = sz / 2\n"
|
||||||
|
" for i, h in enumerate(bars):\n"
|
||||||
|
" x = mg + i*gap + (gap - bw) / 2\n"
|
||||||
|
" bh = h * sz * .36\n"
|
||||||
|
" d.rounded_rectangle([x, mid-bh, x+bw, mid+bh], radius=bw/2, fill=(255, 255, 255, 215))\n"
|
||||||
|
" imgs.append(img)\n"
|
||||||
|
"imgs[-1].save('icon.png')\n"
|
||||||
|
"imgs[0].save('icon.ico', format='ICO', sizes=[(s,s) for s in sizes], append_images=imgs[1:])\n"
|
||||||
|
"print('[OK] Icones generees')\n"
|
||||||
|
)
|
||||||
|
icons_path.write_text(icon_py, encoding="utf-8")
|
||||||
|
ok(f"Script icones genere : {icons_path}")
|
||||||
|
|
||||||
|
# ── Arguments hidden-import + collect-all PyInstaller ──
|
||||||
|
hi_lines = " ^\n ".join(f"--hidden-import {h}" for h in HIDDEN_IMPORTS)
|
||||||
|
ca_lines = " ^\n ".join(f"--collect-all {m}" for m in COLLECT_ALL)
|
||||||
|
|
||||||
|
shortcut_block = ""
|
||||||
|
if install:
|
||||||
|
shortcut_block = (
|
||||||
|
"echo [5/5] Installation des raccourcis...\n"
|
||||||
|
"REM Raccourci vers binaire LOCAL (WScript.Shell refuse les chemins reseau/partages)\n"
|
||||||
|
f'set "LOCAL_EXE=%LOCAL_DIST%\\{APP_NAME}\\{APP_NAME}.exe"\n'
|
||||||
|
f'if exist "%PROJECT_ROOT%dist\\windows\\install_shortcut.ps1" (\n'
|
||||||
|
f' powershell -NoProfile -ExecutionPolicy Bypass ^\n'
|
||||||
|
f' -File "%PROJECT_ROOT%dist\\windows\\install_shortcut.ps1" ^\n'
|
||||||
|
f' -ExePath "%LOCAL_EXE%"\n'
|
||||||
|
f') else (\n'
|
||||||
|
f' echo [!!] Script PowerShell introuvable\n'
|
||||||
|
f')\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Venv court pour contourner MAX_PATH avec PySide6 (Microsoft Store Python)
|
||||||
|
# C:\ts\.venv\Lib\site-packages\ = 30 chars vs ~140 chars pour AppData\Packages\...
|
||||||
|
VENV = "C:\\ts\\.venv"
|
||||||
|
|
||||||
|
# ASCII uniquement dans le bat
|
||||||
|
bat = (
|
||||||
|
"@echo off\n"
|
||||||
|
"setlocal EnableDelayedExpansion\n"
|
||||||
|
f"REM build_windows.bat -- {APP_NAME} v{APP_VERSION}\n"
|
||||||
|
"REM A executer sur une machine Windows avec Python 3.12+\n"
|
||||||
|
"echo.\n"
|
||||||
|
f"echo *** Build {APP_NAME} v{APP_VERSION} pour Windows ***\n"
|
||||||
|
"echo.\n"
|
||||||
|
"\n"
|
||||||
|
"REM -- Localisation du projet\n"
|
||||||
|
"set \"SCRIPT_DIR=%~dp0\"\n"
|
||||||
|
"for %%I in (\"%SCRIPT_DIR%\\..\") do set \"PROJECT_ROOT=%%~fI\\\"\n"
|
||||||
|
f"if exist \"%PROJECT_ROOT%{MAIN_SCRIPT}\" goto ROOT_FOUND\n"
|
||||||
|
"for %%I in (\"%SCRIPT_DIR%\\..\\..\") do set \"PROJECT_ROOT=%%~fI\\\"\n"
|
||||||
|
f"if exist \"%PROJECT_ROOT%{MAIN_SCRIPT}\" goto ROOT_FOUND\n"
|
||||||
|
f"echo [XX] {MAIN_SCRIPT} introuvable.\n"
|
||||||
|
"pause & exit /b 1\n"
|
||||||
|
"\n"
|
||||||
|
":ROOT_FOUND\n"
|
||||||
|
"echo [OK] Dossier projet : %PROJECT_ROOT%\n"
|
||||||
|
"echo.\n"
|
||||||
|
"\n"
|
||||||
|
"REM -- Localisation d'un vrai Python (evite le stub Windows Store)\n"
|
||||||
|
"set \"REAL_PYTHON=\"\n"
|
||||||
|
"\n"
|
||||||
|
"REM Essai 1 : py launcher (installe avec Python officiel)\n"
|
||||||
|
"where py >nul 2>&1\n"
|
||||||
|
"if not errorlevel 1 (\n"
|
||||||
|
" py -3 -c \"import sys; print(sys.executable)\" >nul 2>&1\n"
|
||||||
|
" if not errorlevel 1 (\n"
|
||||||
|
" for /f \"delims=\" %%P in ('py -3 -c \"import sys; print(sys.executable)\"') do set \"REAL_PYTHON=%%P\"\n"
|
||||||
|
" )\n"
|
||||||
|
")\n"
|
||||||
|
"\n"
|
||||||
|
"REM Essai 2 : emplacements standard Python 3.x\n"
|
||||||
|
"if not defined REAL_PYTHON (\n"
|
||||||
|
" for %%V in (313 312 311 310 39) do (\n"
|
||||||
|
" if not defined REAL_PYTHON (\n"
|
||||||
|
" if exist \"C:\\Python%%V\\python.exe\" set \"REAL_PYTHON=C:\\Python%%V\\python.exe\"\n"
|
||||||
|
" )\n"
|
||||||
|
" )\n"
|
||||||
|
")\n"
|
||||||
|
"REM Essai 3 : AppData\\Local\\Programs\\Python\n"
|
||||||
|
"if not defined REAL_PYTHON (\n"
|
||||||
|
" for %%V in (313 312 311 310 39) do (\n"
|
||||||
|
" if not defined REAL_PYTHON (\n"
|
||||||
|
" set \"_CANDIDATE=%LOCALAPPDATA%\\Programs\\Python\\Python%%V\\python.exe\"\n"
|
||||||
|
" if exist \"!_CANDIDATE!\" set \"REAL_PYTHON=!_CANDIDATE!\"\n"
|
||||||
|
" )\n"
|
||||||
|
" )\n"
|
||||||
|
")\n"
|
||||||
|
"\n"
|
||||||
|
"if not defined REAL_PYTHON (\n"
|
||||||
|
" echo [XX] Python 3 introuvable. Installez Python depuis https://www.python.org/downloads/\n"
|
||||||
|
" echo [XX] Assurez-vous de cocher \"Add python.exe to PATH\" lors de l'installation.\n"
|
||||||
|
" pause & exit /b 1\n"
|
||||||
|
")\n"
|
||||||
|
"echo [OK] Python trouve : %REAL_PYTHON%\n"
|
||||||
|
"\n"
|
||||||
|
"REM -- Venv a chemin court pour eviter MAX_PATH avec PySide6\n"
|
||||||
|
f"set \"VENV={VENV}\"\n"
|
||||||
|
"set \"VPYTHON=%VENV%\\Scripts\\python.exe\"\n"
|
||||||
|
"set \"VPIP=%VENV%\\Scripts\\pip.exe\"\n"
|
||||||
|
"echo [1/5] Preparation venv dans %VENV%...\n"
|
||||||
|
"if exist \"%VENV%\" (\n"
|
||||||
|
" \"%VPYTHON%\" -c \"import sys\" >nul 2>&1\n"
|
||||||
|
" if errorlevel 1 (\n"
|
||||||
|
" echo [!!] Venv existant invalide - recreation...\n"
|
||||||
|
" rmdir /S /Q \"%VENV%\"\n"
|
||||||
|
" )\n"
|
||||||
|
")\n"
|
||||||
|
"if not exist \"%VPYTHON%\" (\n"
|
||||||
|
" mkdir C:\\ts 2>nul\n"
|
||||||
|
" \"%REAL_PYTHON%\" -m venv \"%VENV%\"\n"
|
||||||
|
" if errorlevel 1 ( echo ERREUR venv & pause & exit /b 1 )\n"
|
||||||
|
")\n"
|
||||||
|
"echo [OK] Venv pret.\n"
|
||||||
|
"\n"
|
||||||
|
"echo [2/5] Installation des dependances Python...\n"
|
||||||
|
"\"%VPIP%\" install --quiet --upgrade pip\n"
|
||||||
|
"\"%VPIP%\" install --quiet pyinstaller PySide6 numpy soundfile hid Pillow\n"
|
||||||
|
"if errorlevel 1 ( echo ERREUR pip & pause & exit /b 1 )\n"
|
||||||
|
"\"%VPYTHON%\" -c \"from PySide6.QtMultimedia import QMediaPlayer; print('[OK] PySide6 + QtMultimedia OK')\"\n"
|
||||||
|
"if errorlevel 1 ( echo ERREUR PySide6.QtMultimedia & pause & exit /b 1 )\n"
|
||||||
|
"\n"
|
||||||
|
"echo [3/5] Generation des icones...\n"
|
||||||
|
"copy /Y \"%SCRIPT_DIR%_build_icons.py\" \"%PROJECT_ROOT%_build_icons.py\" > nul\n"
|
||||||
|
"\"%VPYTHON%\" \"%PROJECT_ROOT%_build_icons.py\"\n"
|
||||||
|
"if errorlevel 1 ( echo ERREUR icones & pause & exit /b 1 )\n"
|
||||||
|
"del \"%PROJECT_ROOT%_build_icons.py\" > nul 2>&1\n"
|
||||||
|
"\n"
|
||||||
|
"REM -- Build dans C:\\ts (chemin local court) pour eviter les erreurs reseau/MAX_PATH\n"
|
||||||
|
"set \"LOCAL_DIST=C:\\ts\\dist\"\n"
|
||||||
|
"set \"LOCAL_BUILD=C:\\ts\\build\"\n"
|
||||||
|
"\n"
|
||||||
|
"echo [4/5] Compilation PyInstaller (sortie locale C:\\ts\\dist)...\n"
|
||||||
|
"\"%VENV%\\Scripts\\pyinstaller.exe\" --onedir --windowed --clean --noconfirm ^\n"
|
||||||
|
f" --name {APP_NAME} ^\n"
|
||||||
|
" --distpath \"%LOCAL_DIST%\" ^\n"
|
||||||
|
" --workpath \"%LOCAL_BUILD%\" ^\n"
|
||||||
|
" --specpath \"%LOCAL_BUILD%\" ^\n"
|
||||||
|
" --icon \"%PROJECT_ROOT%icon.ico\" ^\n"
|
||||||
|
f" {hi_lines} ^\n"
|
||||||
|
f" {ca_lines} ^\n"
|
||||||
|
f" \"%PROJECT_ROOT%{MAIN_SCRIPT}\"\n"
|
||||||
|
"if errorlevel 1 ( echo ERREUR PyInstaller & pause & exit /b 1 )\n"
|
||||||
|
"\n"
|
||||||
|
"REM -- Ajout de hidapi.dll (absent du package pip hid)\n"
|
||||||
|
f"set \"HIDAPI_URL=https://github.com/libusb/hidapi/releases/download/hidapi-{HIDAPI_VERSION}/hidapi-win.zip\"\n"
|
||||||
|
f"set \"HIDAPI_ZIP=C:\\ts\\hidapi-win.zip\"\n"
|
||||||
|
f"set \"HIDAPI_DLL=%LOCAL_DIST%\\{APP_NAME}\\_internal\\hidapi.dll\"\n"
|
||||||
|
"if not exist \"%HIDAPI_DLL%\" (\n"
|
||||||
|
" echo Telechargement hidapi.dll...\n"
|
||||||
|
" powershell -NoProfile -Command \"Invoke-WebRequest -Uri '%HIDAPI_URL%' -OutFile '%HIDAPI_ZIP%'\"\n"
|
||||||
|
" if not errorlevel 1 (\n"
|
||||||
|
" powershell -NoProfile -Command \""
|
||||||
|
f"Expand-Archive -Force '%HIDAPI_ZIP%' 'C:\\\\ts\\\\hidapi_tmp'; "
|
||||||
|
f"$dll = Get-ChildItem 'C:\\\\ts\\\\hidapi_tmp' -Recurse -Filter hidapi.dll | "
|
||||||
|
f"Where-Object {{ $_.FullName -match 'x64' }} | Select-Object -First 1; "
|
||||||
|
f"if ($dll) {{ Copy-Item $dll.FullName '%HIDAPI_DLL%'; Write-Host '[OK] hidapi.dll installe' }} "
|
||||||
|
f"else {{ Write-Host '[!!] hidapi.dll x64 introuvable dans archive' }}; "
|
||||||
|
f"Remove-Item 'C:\\\\ts\\\\hidapi_tmp' -Recurse -Force\"\n"
|
||||||
|
" ) else (\n"
|
||||||
|
" echo [!!] Echec telechargement hidapi.dll - le pedalier ne fonctionnera pas.\n"
|
||||||
|
" )\n"
|
||||||
|
") else (\n"
|
||||||
|
" echo [OK] hidapi.dll deja present.\n"
|
||||||
|
")\n"
|
||||||
|
"\n"
|
||||||
|
"echo Copie du binaire vers le dossier projet...\n"
|
||||||
|
f"if exist \"%PROJECT_ROOT%dist\\windows\\{APP_NAME}\" rmdir /S /Q \"%PROJECT_ROOT%dist\\windows\\{APP_NAME}\"\n"
|
||||||
|
f"xcopy /E /I /Y \"%LOCAL_DIST%\\{APP_NAME}\" \"%PROJECT_ROOT%dist\\windows\\{APP_NAME}\\\"\n"
|
||||||
|
"if errorlevel 1 ( echo ERREUR copie xcopy & pause & exit /b 1 )\n"
|
||||||
|
f"echo [OK] Binaire copie dans %PROJECT_ROOT%dist\\windows\\{APP_NAME}\\\n"
|
||||||
|
"\n"
|
||||||
|
+ shortcut_block +
|
||||||
|
"\n"
|
||||||
|
"copy /Y \"%PROJECT_ROOT%icon.ico\" \"C:\\ts\\icon.ico\" > nul\n"
|
||||||
|
"\n"
|
||||||
|
"echo [6/6] Creation de l'installeur (Inno Setup)...\n"
|
||||||
|
"set \"ISCC_EXE=\"\n"
|
||||||
|
"if exist \"C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe\" "
|
||||||
|
"set \"ISCC_EXE=C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe\"\n"
|
||||||
|
"if exist \"C:\\Program Files\\Inno Setup 6\\ISCC.exe\" "
|
||||||
|
"set \"ISCC_EXE=C:\\Program Files\\Inno Setup 6\\ISCC.exe\"\n"
|
||||||
|
"if not defined ISCC_EXE (\n"
|
||||||
|
" echo [!!] Inno Setup 6 non installe - installeur ignore.\n"
|
||||||
|
" echo [!!] Telecharger gratuitement : https://jrsoftware.org/isdl.php\n"
|
||||||
|
" echo [!!] Puis relancez ce script pour generer l'installeur.\n"
|
||||||
|
" goto BUILD_DONE\n"
|
||||||
|
")\n"
|
||||||
|
f"mkdir C:\\ts\\installer 2>nul\n"
|
||||||
|
f"echo Compilation Inno Setup...\n"
|
||||||
|
f"\"%ISCC_EXE%\" \"%SCRIPT_DIR%{APP_NAME}.iss\"\n"
|
||||||
|
"if errorlevel 1 ( echo [!!] Erreur Inno Setup & goto BUILD_DONE )\n"
|
||||||
|
f"echo [OK] Installeur : C:\\ts\\installer\\{APP_NAME}_Setup_v{APP_VERSION}.exe\n"
|
||||||
|
"\n"
|
||||||
|
":BUILD_DONE\n"
|
||||||
|
"echo.\n"
|
||||||
|
f"echo *** Build termine ! ***\n"
|
||||||
|
f"echo Binaire : %PROJECT_ROOT%dist\\windows\\{APP_NAME}\\{APP_NAME}.exe\n"
|
||||||
|
f"echo Installeur : C:\\ts\\installer\\{APP_NAME}_Setup_v{APP_VERSION}.exe (si Inno Setup installe)\n"
|
||||||
|
"echo.\n"
|
||||||
|
"pause\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
bat_crlf = bat.replace("\n", "\r\n")
|
||||||
|
bat_path.write_bytes(bat_crlf.encode("ascii", errors="replace"))
|
||||||
|
ok(f"Script Windows genere : {bat_path}")
|
||||||
|
ok(f"Script icones genere : {icons_path}")
|
||||||
|
generate_inno_script()
|
||||||
|
warn(f"Sur Windows : lancez dist\\windows\\build_windows.bat depuis la racine du projet")
|
||||||
|
warn(f"Inno Setup requis pour l'installeur : https://jrsoftware.org/isdl.php")
|
||||||
|
|
||||||
|
# ─── Installeur Inno Setup ────────────────────────────────────────────────────
|
||||||
|
def generate_inno_script():
|
||||||
|
"""
|
||||||
|
Génère TranscribeStation.iss pour Inno Setup 6.
|
||||||
|
Sortie : dist/windows/TranscribeStation.iss (CRLF + BOM UTF-8)
|
||||||
|
Compilation : ISCC.exe TranscribeStation.iss
|
||||||
|
Résultat : C:\\ts\\installer\\TranscribeStation_Setup_v{APP_VERSION}.exe
|
||||||
|
"""
|
||||||
|
# On utilise une string ordinaire pour éviter les conflits entre
|
||||||
|
# les accolades Inno Setup {app} et le f-string Python.
|
||||||
|
# N = APP_NAME, V = APP_VERSION, A = APP_AUTHOR, U = APP_URL
|
||||||
|
N, V, A, U = APP_NAME, APP_VERSION, APP_AUTHOR, APP_URL
|
||||||
|
iss = (
|
||||||
|
"[Setup]\n"
|
||||||
|
f"AppName={N}\n"
|
||||||
|
f"AppVersion={V}\n"
|
||||||
|
f"AppVerName={N} {V}\n"
|
||||||
|
f"AppPublisher={A}\n"
|
||||||
|
f"AppPublisherURL={U}\n"
|
||||||
|
f"AppSupportURL={U}\n"
|
||||||
|
f"DefaultDirName={{localappdata}}\\Programs\\{N}\n"
|
||||||
|
f"DefaultGroupName={N}\n"
|
||||||
|
"AllowNoIcons=yes\n"
|
||||||
|
"OutputDir=C:\\ts\\installer\n"
|
||||||
|
f"OutputBaseFilename={N}_Setup_v{V}\n"
|
||||||
|
"SetupIconFile=C:\\ts\\icon.ico\n"
|
||||||
|
"Compression=lzma2/ultra64\n"
|
||||||
|
"SolidCompression=yes\n"
|
||||||
|
"WizardStyle=modern\n"
|
||||||
|
"WizardSizePercent=120\n"
|
||||||
|
"PrivilegesRequired=lowest\n"
|
||||||
|
"ArchitecturesAllowed=x64compatible\n"
|
||||||
|
"ArchitecturesInstallIn64BitMode=x64compatible\n"
|
||||||
|
f"UninstallDisplayIcon={{app}}\\{N}.exe\n"
|
||||||
|
f"UninstallDisplayName={N} {V}\n"
|
||||||
|
"CloseApplications=yes\n"
|
||||||
|
"\n"
|
||||||
|
"[Languages]\n"
|
||||||
|
'Name: "french"; MessagesFile: "compiler:Languages\\French.isl"\n'
|
||||||
|
"\n"
|
||||||
|
"[Tasks]\n"
|
||||||
|
'Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; '
|
||||||
|
'GroupDescription: "{cm:AdditionalIcons}"\n'
|
||||||
|
"\n"
|
||||||
|
"[Files]\n"
|
||||||
|
f'Source: "C:\\ts\\dist\\{N}\\*"; '
|
||||||
|
'DestDir: "{app}"; '
|
||||||
|
"Flags: ignoreversion recursesubdirs createallsubdirs\n"
|
||||||
|
"\n"
|
||||||
|
"[Icons]\n"
|
||||||
|
f'Name: "{{group}}\\{N}"; Filename: "{{app}}\\{N}.exe"\n'
|
||||||
|
f'Name: "{{userdesktop}}\\{N}"; Filename: "{{app}}\\{N}.exe"; Tasks: desktopicon\n'
|
||||||
|
"\n"
|
||||||
|
"[Run]\n"
|
||||||
|
f'Filename: "{{app}}\\{N}.exe"; '
|
||||||
|
f'Description: "Lancer {N}"; '
|
||||||
|
"Flags: nowait postinstall skipifsilent\n"
|
||||||
|
"\n"
|
||||||
|
"[UninstallDelete]\n"
|
||||||
|
'Type: filesandordirs; Name: "{app}"\n'
|
||||||
|
"\n"
|
||||||
|
"[Code]\n"
|
||||||
|
"// Verifie que l'architecture est x64\n"
|
||||||
|
"function InitializeSetup(): Boolean;\n"
|
||||||
|
"begin\n"
|
||||||
|
" Result := True;\n"
|
||||||
|
" if not Is64BitInstallMode then begin\n"
|
||||||
|
f" MsgBox('{N} necessite Windows 64 bits.', mbError, MB_OK);\n"
|
||||||
|
" Result := False;\n"
|
||||||
|
" end;\n"
|
||||||
|
"end;\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
out = DIST_DIR / "windows" / f"{APP_NAME}.iss"
|
||||||
|
out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
# BOM UTF-8 + CRLF obligatoires pour Inno Setup
|
||||||
|
iss_crlf = iss.replace("\n", "\r\n")
|
||||||
|
out.write_bytes(b"\xef\xbb\xbf" + iss_crlf.encode("utf-8"))
|
||||||
|
ok(f"Script Inno Setup généré : {out}")
|
||||||
|
return out
|
||||||
|
|
||||||
|
# ─── Nettoyage ────────────────────────────────────────────────────────────────
|
||||||
|
def clean():
|
||||||
|
step("🧹 Nettoyage...")
|
||||||
|
for d in [BUILD_DIR, DIST_DIR]:
|
||||||
|
if d.exists():
|
||||||
|
shutil.rmtree(d); ok(f"Supprimé : {d}/")
|
||||||
|
for f in Path(".").glob("*.spec"):
|
||||||
|
f.unlink(); ok(f"Supprimé : {f}")
|
||||||
|
for icon in [ICON_ICO, ICON_PNG]:
|
||||||
|
if icon.exists():
|
||||||
|
icon.unlink(); ok(f"Supprimé : {icon}")
|
||||||
|
ok("Nettoyage terminé.")
|
||||||
|
|
||||||
|
# ─── Point d'entrée ───────────────────────────────────────────────────────────
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description=f"Build + install {APP_NAME}",
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog=textwrap.dedent("""
|
||||||
|
Exemples :
|
||||||
|
python build.py build + raccourcis (auto-detect OS)
|
||||||
|
python build.py --linux build Linux + raccourci GNOME
|
||||||
|
python build.py --windows build Windows + raccourci Menu Démarrer
|
||||||
|
python build.py --both les deux
|
||||||
|
python build.py --no-install build sans créer de raccourcis
|
||||||
|
python build.py --uninstall supprimer le raccourci GNOME
|
||||||
|
python build.py --clean supprimer artefacts build/dist/
|
||||||
|
"""),
|
||||||
|
)
|
||||||
|
parser.add_argument("--linux", action="store_true")
|
||||||
|
parser.add_argument("--windows", action="store_true")
|
||||||
|
parser.add_argument("--both", action="store_true")
|
||||||
|
parser.add_argument("--no-install", action="store_true",
|
||||||
|
help="Ne pas installer de raccourci")
|
||||||
|
parser.add_argument("--install-only", action="store_true",
|
||||||
|
help="Installer raccourci sans recompiler")
|
||||||
|
parser.add_argument("--uninstall", action="store_true",
|
||||||
|
help="Désinstaller raccourci GNOME")
|
||||||
|
parser.add_argument("--clean", action="store_true")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
os.chdir(Path(__file__).parent)
|
||||||
|
do_install = not args.no_install
|
||||||
|
|
||||||
|
if args.clean:
|
||||||
|
clean(); return
|
||||||
|
|
||||||
|
if args.uninstall:
|
||||||
|
uninstall_linux_shortcut(); return
|
||||||
|
|
||||||
|
if args.install_only:
|
||||||
|
binary = Path(f"dist/linux/{APP_NAME}")
|
||||||
|
if binary.exists():
|
||||||
|
generate_icons()
|
||||||
|
install_linux_shortcut(binary)
|
||||||
|
else:
|
||||||
|
err(f"Binaire introuvable : {binary}")
|
||||||
|
err("Lancez d'abord : python build.py --linux")
|
||||||
|
return
|
||||||
|
|
||||||
|
if args.both:
|
||||||
|
build_linux(install=do_install)
|
||||||
|
build_windows(install=do_install)
|
||||||
|
return
|
||||||
|
|
||||||
|
if args.windows:
|
||||||
|
build_windows(install=do_install); return
|
||||||
|
|
||||||
|
if args.linux or platform.system() == "Linux":
|
||||||
|
build_linux(install=do_install)
|
||||||
|
elif platform.system() == "Windows":
|
||||||
|
build_windows(install=do_install)
|
||||||
|
else:
|
||||||
|
err(f"Plateforme inconnue : {platform.system()}")
|
||||||
|
err("Utilisez --linux ou --windows")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -0,0 +1,783 @@
|
||||||
|
#!/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 "$@"
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
@echo off
|
||||||
|
setlocal EnableDelayedExpansion
|
||||||
|
REM build_windows.bat -- TranscribeStation v1.0.1
|
||||||
|
REM A executer sur une machine Windows avec Python 3.12+
|
||||||
|
echo.
|
||||||
|
echo *** Build TranscribeStation v1.0.1 pour Windows ***
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM -- Localisation du projet
|
||||||
|
set "SCRIPT_DIR=%~dp0"
|
||||||
|
for %%I in ("%SCRIPT_DIR%\..") do set "PROJECT_ROOT=%%~fI\"
|
||||||
|
if exist "%PROJECT_ROOT%transcribe_station.py" goto ROOT_FOUND
|
||||||
|
for %%I in ("%SCRIPT_DIR%\..\..") do set "PROJECT_ROOT=%%~fI\"
|
||||||
|
if exist "%PROJECT_ROOT%transcribe_station.py" goto ROOT_FOUND
|
||||||
|
echo [XX] transcribe_station.py introuvable.
|
||||||
|
pause & exit /b 1
|
||||||
|
|
||||||
|
:ROOT_FOUND
|
||||||
|
echo [OK] Dossier projet : %PROJECT_ROOT%
|
||||||
|
echo.
|
||||||
|
|
||||||
|
REM -- Localisation d'un vrai Python (evite le stub Windows Store)
|
||||||
|
set "REAL_PYTHON="
|
||||||
|
|
||||||
|
REM Essai 1 : py launcher (installe avec Python officiel)
|
||||||
|
where py >nul 2>&1
|
||||||
|
if not errorlevel 1 (
|
||||||
|
py -3 -c "import sys; print(sys.executable)" >nul 2>&1
|
||||||
|
if not errorlevel 1 (
|
||||||
|
for /f "delims=" %%P in ('py -3 -c "import sys; print(sys.executable)"') do set "REAL_PYTHON=%%P"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Essai 2 : emplacements standard Python 3.x
|
||||||
|
if not defined REAL_PYTHON (
|
||||||
|
for %%V in (313 312 311 310 39) do (
|
||||||
|
if not defined REAL_PYTHON (
|
||||||
|
if exist "C:\Python%%V\python.exe" set "REAL_PYTHON=C:\Python%%V\python.exe"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
REM Essai 3 : AppData\Local\Programs\Python
|
||||||
|
if not defined REAL_PYTHON (
|
||||||
|
for %%V in (313 312 311 310 39) do (
|
||||||
|
if not defined REAL_PYTHON (
|
||||||
|
set "_CANDIDATE=%LOCALAPPDATA%\Programs\Python\Python%%V\python.exe"
|
||||||
|
if exist "!_CANDIDATE!" set "REAL_PYTHON=!_CANDIDATE!"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not defined REAL_PYTHON (
|
||||||
|
echo [XX] Python 3 introuvable. Installez Python depuis https://www.python.org/downloads/
|
||||||
|
echo [XX] Assurez-vous de cocher "Add python.exe to PATH" lors de l'installation.
|
||||||
|
pause & exit /b 1
|
||||||
|
)
|
||||||
|
echo [OK] Python trouve : %REAL_PYTHON%
|
||||||
|
|
||||||
|
REM -- Venv a chemin court pour eviter MAX_PATH avec PySide6
|
||||||
|
set "VENV=C:\ts\.venv"
|
||||||
|
set "VPYTHON=%VENV%\Scripts\python.exe"
|
||||||
|
set "VPIP=%VENV%\Scripts\pip.exe"
|
||||||
|
echo [1/5] Preparation venv dans %VENV%...
|
||||||
|
if exist "%VENV%" (
|
||||||
|
"%VPYTHON%" -c "import sys" >nul 2>&1
|
||||||
|
if errorlevel 1 (
|
||||||
|
echo [!!] Venv existant invalide - recreation...
|
||||||
|
rmdir /S /Q "%VENV%"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if not exist "%VPYTHON%" (
|
||||||
|
mkdir C:\ts 2>nul
|
||||||
|
"%REAL_PYTHON%" -m venv "%VENV%"
|
||||||
|
if errorlevel 1 ( echo ERREUR venv & pause & exit /b 1 )
|
||||||
|
)
|
||||||
|
echo [OK] Venv pret.
|
||||||
|
|
||||||
|
echo [2/5] Installation des dependances Python...
|
||||||
|
"%VPIP%" install --quiet --upgrade pip
|
||||||
|
"%VPIP%" install --quiet pyinstaller PySide6 numpy soundfile hid Pillow
|
||||||
|
if errorlevel 1 ( echo ERREUR pip & pause & exit /b 1 )
|
||||||
|
"%VPYTHON%" -c "from PySide6.QtMultimedia import QMediaPlayer; print('[OK] PySide6 + QtMultimedia OK')"
|
||||||
|
if errorlevel 1 ( echo ERREUR PySide6.QtMultimedia & pause & exit /b 1 )
|
||||||
|
|
||||||
|
echo [3/5] Generation des icones...
|
||||||
|
copy /Y "%SCRIPT_DIR%_build_icons.py" "%PROJECT_ROOT%_build_icons.py" > nul
|
||||||
|
"%VPYTHON%" "%PROJECT_ROOT%_build_icons.py"
|
||||||
|
if errorlevel 1 ( echo ERREUR icones & pause & exit /b 1 )
|
||||||
|
del "%PROJECT_ROOT%_build_icons.py" > nul 2>&1
|
||||||
|
|
||||||
|
REM -- Build dans C:\ts (chemin local court) pour eviter les erreurs reseau/MAX_PATH
|
||||||
|
set "LOCAL_DIST=C:\ts\dist"
|
||||||
|
set "LOCAL_BUILD=C:\ts\build"
|
||||||
|
|
||||||
|
echo [4/5] Compilation PyInstaller (sortie locale C:\ts\dist)...
|
||||||
|
"%VENV%\Scripts\pyinstaller.exe" --onedir --windowed --clean --noconfirm ^
|
||||||
|
--name TranscribeStation ^
|
||||||
|
--distpath "%LOCAL_DIST%" ^
|
||||||
|
--workpath "%LOCAL_BUILD%" ^
|
||||||
|
--specpath "%LOCAL_BUILD%" ^
|
||||||
|
--icon "%PROJECT_ROOT%icon.ico" ^
|
||||||
|
--hidden-import PySide6.QtMultimedia ^
|
||||||
|
--hidden-import PySide6.QtCore ^
|
||||||
|
--hidden-import PySide6.QtGui ^
|
||||||
|
--hidden-import PySide6.QtWidgets ^
|
||||||
|
--hidden-import soundfile ^
|
||||||
|
--hidden-import numpy ^
|
||||||
|
--hidden-import hid ^
|
||||||
|
--hidden-import cffi ^
|
||||||
|
--hidden-import _cffi_backend ^
|
||||||
|
--collect-all hid ^
|
||||||
|
"%PROJECT_ROOT%transcribe_station.py"
|
||||||
|
if errorlevel 1 ( echo ERREUR PyInstaller & pause & exit /b 1 )
|
||||||
|
|
||||||
|
REM -- Ajout de hidapi.dll (absent du package pip hid)
|
||||||
|
set "HIDAPI_URL=https://github.com/libusb/hidapi/releases/download/hidapi-0.14.0/hidapi-win.zip"
|
||||||
|
set "HIDAPI_ZIP=C:\ts\hidapi-win.zip"
|
||||||
|
set "HIDAPI_DLL=%LOCAL_DIST%\TranscribeStation\_internal\hidapi.dll"
|
||||||
|
if not exist "%HIDAPI_DLL%" (
|
||||||
|
echo Telechargement hidapi.dll...
|
||||||
|
powershell -NoProfile -Command "Invoke-WebRequest -Uri '%HIDAPI_URL%' -OutFile '%HIDAPI_ZIP%'"
|
||||||
|
if not errorlevel 1 (
|
||||||
|
powershell -NoProfile -Command "Expand-Archive -Force '%HIDAPI_ZIP%' 'C:\\ts\\hidapi_tmp'; $dll = Get-ChildItem 'C:\\ts\\hidapi_tmp' -Recurse -Filter hidapi.dll | Where-Object { $_.FullName -match 'x64' } | Select-Object -First 1; if ($dll) { Copy-Item $dll.FullName '%HIDAPI_DLL%'; Write-Host '[OK] hidapi.dll installe' } else { Write-Host '[!!] hidapi.dll x64 introuvable dans archive' }; Remove-Item 'C:\\ts\\hidapi_tmp' -Recurse -Force"
|
||||||
|
) else (
|
||||||
|
echo [!!] Echec telechargement hidapi.dll - le pedalier ne fonctionnera pas.
|
||||||
|
)
|
||||||
|
) else (
|
||||||
|
echo [OK] hidapi.dll deja present.
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Copie du binaire vers le dossier projet...
|
||||||
|
if exist "%PROJECT_ROOT%dist\windows\TranscribeStation" rmdir /S /Q "%PROJECT_ROOT%dist\windows\TranscribeStation"
|
||||||
|
xcopy /E /I /Y "%LOCAL_DIST%\TranscribeStation" "%PROJECT_ROOT%dist\windows\TranscribeStation\"
|
||||||
|
if errorlevel 1 ( echo ERREUR copie xcopy & pause & exit /b 1 )
|
||||||
|
echo [OK] Binaire copie dans %PROJECT_ROOT%dist\windows\TranscribeStation\
|
||||||
|
|
||||||
|
echo [5/5] Installation des raccourcis...
|
||||||
|
REM Raccourci vers binaire LOCAL (WScript.Shell refuse les chemins reseau/partages)
|
||||||
|
set "LOCAL_EXE=%LOCAL_DIST%\TranscribeStation\TranscribeStation.exe"
|
||||||
|
if exist "%PROJECT_ROOT%dist\windows\install_shortcut.ps1" (
|
||||||
|
powershell -NoProfile -ExecutionPolicy Bypass ^
|
||||||
|
-File "%PROJECT_ROOT%dist\windows\install_shortcut.ps1" ^
|
||||||
|
-ExePath "%LOCAL_EXE%"
|
||||||
|
) else (
|
||||||
|
echo [!!] Script PowerShell introuvable
|
||||||
|
)
|
||||||
|
|
||||||
|
copy /Y "%PROJECT_ROOT%icon.ico" "C:\ts\icon.ico" > nul
|
||||||
|
|
||||||
|
echo [6/6] Creation de l'installeur (Inno Setup)...
|
||||||
|
set "ISCC_EXE="
|
||||||
|
if exist "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" set "ISCC_EXE=C:\Program Files (x86)\Inno Setup 6\ISCC.exe"
|
||||||
|
if exist "C:\Program Files\Inno Setup 6\ISCC.exe" set "ISCC_EXE=C:\Program Files\Inno Setup 6\ISCC.exe"
|
||||||
|
if not defined ISCC_EXE (
|
||||||
|
echo [!!] Inno Setup 6 non installe - installeur ignore.
|
||||||
|
echo [!!] Telecharger gratuitement : https://jrsoftware.org/isdl.php
|
||||||
|
echo [!!] Puis relancez ce script pour generer l'installeur.
|
||||||
|
goto BUILD_DONE
|
||||||
|
)
|
||||||
|
mkdir C:\ts\installer 2>nul
|
||||||
|
echo Compilation Inno Setup...
|
||||||
|
"%ISCC_EXE%" "%SCRIPT_DIR%TranscribeStation.iss"
|
||||||
|
if errorlevel 1 ( echo [!!] Erreur Inno Setup & goto BUILD_DONE )
|
||||||
|
echo [OK] Installeur : C:\ts\installer\TranscribeStation_Setup_v1.0.1.exe
|
||||||
|
|
||||||
|
:BUILD_DONE
|
||||||
|
echo.
|
||||||
|
echo *** Build termine ! ***
|
||||||
|
echo Binaire : %PROJECT_ROOT%dist\windows\TranscribeStation\TranscribeStation.exe
|
||||||
|
echo Installeur : C:\ts\installer\TranscribeStation_Setup_v1.0.1.exe (si Inno Setup installe)
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# install.sh – Installation de TranscribeStation
|
||||||
|
# Testé sur Debian 12 (Bookworm) et Debian 13 (Trixie)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ─── Couleurs terminal ────────────────────────────────────────────────────────
|
||||||
|
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
|
||||||
|
CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
|
||||||
|
|
||||||
|
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
|
||||||
|
success() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||||
|
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||||||
|
error() { echo -e "${RED}[ERR]${NC} $*" >&2; exit 1; }
|
||||||
|
step() { echo -e "\n${BOLD}$*${NC}"; }
|
||||||
|
|
||||||
|
echo -e "${BOLD}"
|
||||||
|
echo "╔══════════════════════════════════════════════╗"
|
||||||
|
echo "║ TranscribeStation – Installation ║"
|
||||||
|
echo "║ Debian 12/13 · Python · Qt6 ║"
|
||||||
|
echo "╚══════════════════════════════════════════════╝"
|
||||||
|
echo -e "${NC}"
|
||||||
|
|
||||||
|
# ─── Vérifications préliminaires ─────────────────────────────────────────────
|
||||||
|
if [[ $EUID -eq 0 ]]; then
|
||||||
|
error "Ne pas lancer ce script en root. Utilisez votre compte normal."
|
||||||
|
fi
|
||||||
|
|
||||||
|
command -v python3 >/dev/null 2>&1 || error "python3 introuvable."
|
||||||
|
PYTHON_VERSION=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")')
|
||||||
|
info "Python détecté : $PYTHON_VERSION"
|
||||||
|
|
||||||
|
# ─── Étape 1 : Dépendances système ───────────────────────────────────────────
|
||||||
|
step "[1/6] Installation des dépendances système..."
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y \
|
||||||
|
python3 python3-pip python3-venv \
|
||||||
|
libhidapi-dev libhidapi-hidraw0 libusb-1.0-0 \
|
||||||
|
gstreamer1.0-plugins-good \
|
||||||
|
gstreamer1.0-plugins-bad \
|
||||||
|
gstreamer1.0-plugins-ugly \
|
||||||
|
gstreamer1.0-libav \
|
||||||
|
fonts-noto-color-emoji \
|
||||||
|
2>&1 | grep -E "^(Inst|Conf|E:)" || true
|
||||||
|
success "Dépendances système installées."
|
||||||
|
|
||||||
|
# ─── Étape 2 : Environnement virtuel Python ───────────────────────────────────
|
||||||
|
step "[2/6] Création de l'environnement virtuel..."
|
||||||
|
if [ -d ".venv" ]; then
|
||||||
|
warn "Environnement .venv existant détecté — réutilisation."
|
||||||
|
else
|
||||||
|
python3 -m venv .venv
|
||||||
|
success "Environnement virtuel créé dans .venv/"
|
||||||
|
fi
|
||||||
|
source .venv/bin/activate
|
||||||
|
|
||||||
|
# ─── Étape 3 : Mise à jour de pip ────────────────────────────────────────────
|
||||||
|
step "[3/6] Mise à jour de pip..."
|
||||||
|
pip install --upgrade pip --quiet
|
||||||
|
success "pip mis à jour : $(pip --version | awk '{print $2}')"
|
||||||
|
|
||||||
|
# ─── Étape 4 : Dépendances Python ────────────────────────────────────────────
|
||||||
|
step "[4/6] Installation des dépendances Python..."
|
||||||
|
|
||||||
|
info "Installation de PySide6 (interface + lecteur audio)..."
|
||||||
|
pip install "PySide6>=6.5.0" --quiet
|
||||||
|
success "PySide6 $(pip show PySide6 | awk '/^Version/{print $2}')"
|
||||||
|
|
||||||
|
info "Installation de numpy + soundfile (affichage waveform)..."
|
||||||
|
pip install "numpy>=1.24.0" "soundfile>=0.12.0" --quiet
|
||||||
|
success "numpy $(pip show numpy | awk '/^Version/{print $2}') soundfile $(pip show soundfile | awk '/^Version/{print $2}')"
|
||||||
|
|
||||||
|
info "Installation de hid (support pédalier USB HID)..."
|
||||||
|
if pip install "hid>=1.0.5" --quiet 2>/dev/null; then
|
||||||
|
success "hid $(pip show hid | awk '/^Version/{print $2}')"
|
||||||
|
else
|
||||||
|
warn "Le paquet 'hid' n'a pas pu être installé automatiquement."
|
||||||
|
warn "Tentative via hidapi..."
|
||||||
|
if pip install "hidapi>=0.14.0" --quiet 2>/dev/null; then
|
||||||
|
success "hidapi installé (alias hid)."
|
||||||
|
else
|
||||||
|
warn "Support pédalier désactivé. Pour l'activer plus tard :"
|
||||||
|
warn " pip install hid ou pip install hidapi"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Vérification des imports Python..."
|
||||||
|
python3 - << 'PYCHECK'
|
||||||
|
import importlib
|
||||||
|
for pkg, mod in [("PySide6","PySide6.QtWidgets"),("numpy","numpy"),
|
||||||
|
("soundfile","soundfile"),("hid","hid")]:
|
||||||
|
try:
|
||||||
|
importlib.import_module(mod)
|
||||||
|
print(f" \u2705 {pkg}")
|
||||||
|
except ImportError:
|
||||||
|
print(f" \u26a0\ufe0f {pkg} (non disponible)")
|
||||||
|
PYCHECK
|
||||||
|
|
||||||
|
# ─── Étape 5 : Règle udev pédalier Olympus ───────────────────────────────────
|
||||||
|
step "[5/6] Configuration udev pour le pédalier Olympus..."
|
||||||
|
UDEV_FILE="/etc/udev/rules.d/99-olympus-pedal.rules"
|
||||||
|
RULE='SUBSYSTEM=="hidraw", ATTRS{idVendor}=="07b4", MODE="0666", GROUP="plugdev"'
|
||||||
|
|
||||||
|
if [ -f "$UDEV_FILE" ]; then
|
||||||
|
warn "Règle udev déjà présente : $UDEV_FILE"
|
||||||
|
else
|
||||||
|
echo "$RULE" | sudo tee "$UDEV_FILE" > /dev/null
|
||||||
|
sudo udevadm control --reload-rules
|
||||||
|
sudo udevadm trigger
|
||||||
|
success "Règle udev créée : $UDEV_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if groups "$USER" | grep -q plugdev; then
|
||||||
|
success "Utilisateur '$USER' déjà dans le groupe 'plugdev'."
|
||||||
|
else
|
||||||
|
sudo usermod -aG plugdev "$USER"
|
||||||
|
success "Utilisateur '$USER' ajouté au groupe 'plugdev'."
|
||||||
|
warn "Déconnectez-vous et reconnectez-vous pour activer le groupe."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ─── Étape 6 : Création du lanceur ───────────────────────────────────────────
|
||||||
|
step "[6/6] Création du script de lancement..."
|
||||||
|
cat > launch.sh << 'LAUNCHEOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$SCRIPT_DIR/.venv/bin/activate"
|
||||||
|
exec python "$SCRIPT_DIR/transcribe_station.py" "$@"
|
||||||
|
LAUNCHEOF
|
||||||
|
chmod +x launch.sh
|
||||||
|
success "Script de lancement créé : ./launch.sh"
|
||||||
|
|
||||||
|
# ─── Résumé ──────────────────────────────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}${BOLD}Installation terminée !${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " Lancer l'application :"
|
||||||
|
echo -e " ${CYAN}./launch.sh${NC}"
|
||||||
|
echo -e " ou"
|
||||||
|
echo -e " ${CYAN}source .venv/bin/activate && python transcribe_station.py${NC}"
|
||||||
|
echo ""
|
||||||
|
echo -e " ${YELLOW}Rappel :${NC} si vous avez été ajouté au groupe 'plugdev',"
|
||||||
|
echo -e " déconnectez-vous/reconnectez-vous avant de brancher le pédalier."
|
||||||
|
echo ""
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
source "$SCRIPT_DIR/.venv/bin/activate"
|
||||||
|
exec python "$SCRIPT_DIR/transcribe_station.py" "$@"
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,5 @@
|
||||||
|
# TranscribeStation – dépendances
|
||||||
|
PySide6>=6.5.0
|
||||||
|
numpy>=1.24.0
|
||||||
|
soundfile>=0.12.0
|
||||||
|
hid>=1.0.5 # support pédalier USB HID
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue