diff --git a/.gitignore b/.gitignore index 8a3585e..3d58b09 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ _office_convertiti_pdf/ build/ dist/ *.spec +*.exe diff --git a/README.md b/README.md index f607072..e983f62 100644 --- a/README.md +++ b/README.md @@ -141,9 +141,30 @@ Per Office COM serve anche `pywin32`, installato nell'ambiente in cui viene eseg Il progetto e pensato per essere trasformato in un eseguibile Windows con PyInstaller. -Da valutare: +Script disponibile: + +```powershell +.\build_exe.ps1 -Clean +``` + +Lo script: + +- installa le dipendenze Python; +- installa/aggiorna PyInstaller; +- include lo splash screen `assets/splash.png`; +- crea un eseguibile Windows `dist\ZipBundler.exe`; +- prova a includere Ghostscript se trova `gswin64c`, `gswin32c` o `gs` nel PATH. + +Lo splash screen riporta `ZipBundler`, `versione 1.0` e `ai.teamstudio.it`. + +Componenti inclusi o gestiti: + +- CustomTkinter, Pillow e PyMuPDF vengono inclusi da PyInstaller come dipendenze Python; +- Ghostscript viene incluso se rilevato in fase di build; +- Microsoft Office non viene incluso: la conversione Office -> PDF usa Office COM e richiede Office installato sul PC di destinazione. + +Da valutare in futuro: -- inclusione o rilevamento esterno di Ghostscript; - gestione piu esplicita di `pywin32`; - eventuale icona applicativa; - firma dell'eseguibile, se necessaria in ambiente aziendale. diff --git a/assets/splash.png b/assets/splash.png new file mode 100644 index 0000000..603c95f Binary files /dev/null and b/assets/splash.png differ diff --git a/build_exe.ps1 b/build_exe.ps1 new file mode 100644 index 0000000..4690cf9 --- /dev/null +++ b/build_exe.ps1 @@ -0,0 +1,77 @@ +param( + [switch]$Clean, + [switch]$SkipGhostscriptBundle +) + +$ErrorActionPreference = "Stop" + +$ProjectRoot = Split-Path -Parent $MyInvocation.MyCommand.Path +Set-Location $ProjectRoot + +$AppName = "ZipBundler" +$EntryPoint = Join-Path $ProjectRoot "zipbundler.py" +$SplashImage = Join-Path $ProjectRoot "assets\splash.png" + +if (-not (Test-Path -LiteralPath $EntryPoint)) { + throw "File principale non trovato: $EntryPoint" +} + +if (-not (Test-Path -LiteralPath $SplashImage)) { + throw "Immagine splash non trovata: $SplashImage" +} + +if ($Clean) { + Remove-Item -LiteralPath (Join-Path $ProjectRoot "build") -Recurse -Force -ErrorAction SilentlyContinue + Remove-Item -LiteralPath (Join-Path $ProjectRoot "dist") -Recurse -Force -ErrorAction SilentlyContinue + Remove-Item -LiteralPath (Join-Path $ProjectRoot "$AppName.spec") -Force -ErrorAction SilentlyContinue +} + +python -m pip install --upgrade pip +python -m pip install -r requirements.txt +python -m pip install --upgrade pyinstaller + +$argsList = @( + "--noconfirm", + "--clean", + "--windowed", + "--onefile", + "--name", $AppName, + "--splash", $SplashImage, + "--add-data", "$SplashImage;assets", + "--hidden-import", "customtkinter", + "--hidden-import", "PIL._tkinter_finder", + "--hidden-import", "fitz", + "--hidden-import", "win32com", + "--hidden-import", "win32com.client", + "--hidden-import", "pythoncom" +) + +if (-not $SkipGhostscriptBundle) { + $ghostscriptCommand = Get-Command gswin64c.exe, gswin32c.exe, gs.exe -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($ghostscriptCommand) { + $ghostscriptBin = Split-Path -Parent $ghostscriptCommand.Source + $ghostscriptRoot = Split-Path -Parent $ghostscriptBin + Write-Host "Includo Ghostscript da: $ghostscriptRoot" + $argsList += @("--add-data", "$ghostscriptRoot;ghostscript") + } + else { + Write-Warning "Ghostscript non trovato nel PATH. Il programma usera Ghostscript solo se installato sul PC di destinazione." + } +} +else { + Write-Warning "Bundle Ghostscript saltato per richiesta esplicita." +} + +$argsList += $EntryPoint + +python -m PyInstaller @argsList + +$exePath = Join-Path $ProjectRoot "dist\$AppName.exe" +if (Test-Path -LiteralPath $exePath) { + Write-Host "" + Write-Host "Build completata:" + Write-Host $exePath +} +else { + throw "Build completata senza trovare l'eseguibile atteso: $exePath" +} diff --git a/zipbundler.py b/zipbundler.py index 08d6717..5acbe46 100644 --- a/zipbundler.py +++ b/zipbundler.py @@ -141,6 +141,14 @@ def shortened_stem(stem, max_length=80): def find_ghostscript(): + bundled_root = getattr(sys, "_MEIPASS", None) + if bundled_root: + bundled_ghostscript = Path(bundled_root) / "ghostscript" + for executable in ("gswin64c.exe", "gswin32c.exe", "gs.exe"): + matches = list(bundled_ghostscript.rglob(executable)) + if matches: + return str(matches[0]) + for executable in ("gswin64c", "gswin32c", "gs"): path = shutil.which(executable) if path: @@ -1432,6 +1440,13 @@ class ZipBundlerApp(ctk.CTk): def run_gui(): app = ZipBundlerApp() + try: + import pyi_splash + + pyi_splash.close() + except Exception: + pass + app.mainloop()