feat: Windows-Packaging mit PyInstaller (ZIP ohne Python-Installation)

Fügt Build-Infrastruktur hinzu, mit der whisper-local als
selbständiges Windows-ZIP-Paket ohne Python-Installation
bereitgestellt werden kann.

- whisper_local.spec: PyInstaller onedir-Konfiguration für Windows 64-bit
  mit allen nativen DLLs (ctranslate2/CUDA, pywin32, PortAudio,
  onnxruntime, av/FFmpeg) und Hidden Imports für platform-bedingte Backends
- build.ps1: Build-Skript das versioniertes ZIP erstellt (.\build.ps1 -Clean)
- transcriber.py: portabler Modell-Cache neben der EXE im gebündelten Modus
- pyproject.toml: pyinstaller>=6.0 als [build]-Abhängigkeitsgruppe, v1.0.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-11 12:01:02 +02:00
parent 7319ff6299
commit 05ff5765bf
5 changed files with 350 additions and 3 deletions
+75
View File
@@ -0,0 +1,75 @@
#Requires -Version 5.1
<#
.SYNOPSIS
Baut whisper-local mit PyInstaller und erstellt ein versioniertes ZIP.
.DESCRIPTION
Liest die Version aus pyproject.toml, führt PyInstaller aus und packt
das dist-Verzeichnis als whisper-local-v{version}-win64.zip.
.EXAMPLE
.\build.ps1
.\build.ps1 -Clean # löscht dist/ und build/ vor dem Build
.\build.ps1 -SkipBuild # nur ZIP aus bestehendem dist/ erstellen
#>
param(
[switch]$Clean,
[switch]$SkipBuild
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
# ── Versionsnummer aus pyproject.toml lesen ───────────────────────────────────
$pyproject = Get-Content "pyproject.toml" -Raw
if ($pyproject -match 'version\s*=\s*"([^"]+)"') {
$version = $Matches[1]
} else {
Write-Error "Konnte Version nicht aus pyproject.toml lesen."
exit 1
}
$zipName = "whisper-local-v$version-win64.zip"
$distDir = "dist\whisper-local"
Write-Host "=== whisper-local Build v$version ===" -ForegroundColor Cyan
# ── Optional: Bereinigen ──────────────────────────────────────────────────────
if ($Clean) {
Write-Host "Bereinige build/ und dist/ ..." -ForegroundColor Yellow
if (Test-Path "build") { Remove-Item -Recurse -Force "build" }
if (Test-Path "dist") { Remove-Item -Recurse -Force "dist" }
if (Test-Path $zipName) { Remove-Item -Force $zipName }
}
# ── PyInstaller ausführen ────────────────────────────────────────────────────
if (-not $SkipBuild) {
Write-Host "Synchronisiere Build-Abhängigkeiten ..." -ForegroundColor Yellow
uv sync --group build
Write-Host "Starte PyInstaller ..." -ForegroundColor Yellow
uv run --group build python -m PyInstaller whisper_local.spec --noconfirm
if ($LASTEXITCODE -ne 0) {
Write-Error "PyInstaller fehlgeschlagen (Exit-Code $LASTEXITCODE)"
exit $LASTEXITCODE
}
}
# ── Prüfen ob dist-Ordner vorhanden ──────────────────────────────────────────
if (-not (Test-Path $distDir)) {
Write-Error "Ordner '$distDir' nicht gefunden — Build fehlgeschlagen?"
exit 1
}
# ── ZIP erstellen ─────────────────────────────────────────────────────────────
if (Test-Path $zipName) { Remove-Item -Force $zipName }
Write-Host "Erstelle $zipName ..." -ForegroundColor Yellow
Compress-Archive -Path $distDir -DestinationPath $zipName -CompressionLevel Optimal
$sizeMB = [math]::Round((Get-Item $zipName).Length / 1MB, 1)
Write-Host ""
Write-Host "Fertig: $zipName ($sizeMB MB)" -ForegroundColor Green
Write-Host ""
Write-Host "Testen:" -ForegroundColor Cyan
Write-Host " Expand-Archive $zipName -DestinationPath test-release"
Write-Host " .\test-release\whisper-local\whisper-local.exe"