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"
+4 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "whisper-local" name = "whisper-local"
version = "0.1.0" version = "1.0.0"
requires-python = ">=3.13" requires-python = ">=3.13"
dependencies = [ dependencies = [
"faster-whisper>=1.1.0", "faster-whisper>=1.1.0",
@@ -27,3 +27,6 @@ dev = [
"pytest>=8.0.0", "pytest>=8.0.0",
"pytest-asyncio>=0.24.0", "pytest-asyncio>=0.24.0",
] ]
build = [
"pyinstaller>=6.0",
]
Generated
+85 -1
View File
@@ -2,6 +2,15 @@ version = 1
revision = 3 revision = 3
requires-python = ">=3.13" requires-python = ">=3.13"
[[package]]
name = "altgraph"
version = "0.17.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7e/f8/97fdf103f38fed6792a1601dbc16cc8aac56e7459a9fff08c812d8ae177a/altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7", size = 48428, upload-time = "2025-11-21T20:35:50.583Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a9/ba/000a1996d4308bc65120167c21241a3b205464a2e0b58deda26ae8ac21d1/altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597", size = 21228, upload-time = "2025-11-21T20:35:49.444Z" },
]
[[package]] [[package]]
name = "annotated-doc" name = "annotated-doc"
version = "0.0.4" version = "0.0.4"
@@ -313,6 +322,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" },
] ]
[[package]]
name = "macholib"
version = "1.16.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "altgraph" },
]
sdist = { url = "https://files.pythonhosted.org/packages/10/2f/97589876ea967487978071c9042518d28b958d87b17dceb7cdc1d881f963/macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362", size = 59427, upload-time = "2025-11-22T08:28:38.373Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/d1/a9f36f8ecdf0fb7c9b1e78c8d7af12b8c8754e74851ac7b94a8305540fc7/macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea", size = 38117, upload-time = "2025-11-22T08:28:36.939Z" },
]
[[package]] [[package]]
name = "markdown-it-py" name = "markdown-it-py"
version = "4.0.0" version = "4.0.0"
@@ -430,6 +451,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" },
] ]
[[package]]
name = "pefile"
version = "2024.8.26"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/03/4f/2750f7f6f025a1507cd3b7218691671eecfd0bbebebe8b39aa0fe1d360b8/pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632", size = 76008, upload-time = "2024-08-26T20:58:38.155Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f", size = 74766, upload-time = "2024-08-26T21:01:02.632Z" },
]
[[package]] [[package]]
name = "pillow" name = "pillow"
version = "12.2.0" version = "12.2.0"
@@ -498,6 +528,47 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
] ]
[[package]]
name = "pyinstaller"
version = "6.19.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "altgraph" },
{ name = "macholib", marker = "sys_platform == 'darwin'" },
{ name = "packaging" },
{ name = "pefile", marker = "sys_platform == 'win32'" },
{ name = "pyinstaller-hooks-contrib" },
{ name = "pywin32-ctypes", marker = "sys_platform == 'win32'" },
{ name = "setuptools" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c8/63/fd62472b6371d89dc138d40c36d87a50dc2de18a035803bbdc376b4ffac4/pyinstaller-6.19.0.tar.gz", hash = "sha256:ec73aeb8bd9b7f2f1240d328a4542e90b3c6e6fbc106014778431c616592a865", size = 4036072, upload-time = "2026-02-14T18:06:28.718Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e3/eb/23374721fecfa72677e79800921cb6aceefa6ba48574dc404f3f6c6c3be7/pyinstaller-6.19.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:4190e76b74f0c4b5c5f11ac360928cd2e36ec8e3194d437bf6b8648c7bc0c134", size = 1040563, upload-time = "2026-02-14T18:05:22.436Z" },
{ url = "https://files.pythonhosted.org/packages/cd/7e/dfd724b0b533f5aaec0ee5df406fe2319987ed6964480a706f85478b12ea/pyinstaller-6.19.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8bd68abd812d8a6ba33b9f1810e91fee0f325969733721b78151f0065319ca11", size = 735477, upload-time = "2026-02-14T18:05:27.143Z" },
{ url = "https://files.pythonhosted.org/packages/88/c9/ee3a4101c31f26344e66896c73c1fd6ed8282bf871473365b7f8674af406/pyinstaller-6.19.0-py3-none-manylinux2014_i686.whl", hash = "sha256:1ec54ef967996ca61dacba676227e2b23219878ccce5ee9d6f3aada7b8ed8abf", size = 747143, upload-time = "2026-02-14T18:05:31.488Z" },
{ url = "https://files.pythonhosted.org/packages/da/0a/fc77e9f861be8cf300ac37155f59cc92aff99b29f2ddd78546f563a5b5a6/pyinstaller-6.19.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:4ab2bb52e58448e14ddf9450601bdedd66800465043501c1d8f1cab87b60b122", size = 744849, upload-time = "2026-02-14T18:05:35.492Z" },
{ url = "https://files.pythonhosted.org/packages/6d/e3/6872e020ee758afe0b821663858492c10745608b07150e5e2c824a5b3e1c/pyinstaller-6.19.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:da6d5c6391ccefe73554b9fa29b86001c8e378e0f20c2a4004f836ba537eff63", size = 741590, upload-time = "2026-02-14T18:05:39.59Z" },
{ url = "https://files.pythonhosted.org/packages/53/60/b8db5f1a4b0fb228175f2ea0aa33f949adcc097fbe981cc524f9faf85777/pyinstaller-6.19.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a0fc5f6b3c55aa54353f0c74ffa59b1115433c1850c6f655d62b461a2ed6cbbe", size = 741448, upload-time = "2026-02-14T18:05:45.636Z" },
{ url = "https://files.pythonhosted.org/packages/6f/4d/63b0600f2694e9141b83129fbc1c488ec84d5a0770b1448ec154dcd0fee9/pyinstaller-6.19.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:e649ba6bd1b0b89b210ad92adb5fbdc8a42dd2c5ca4f72ef3a0bfec83a424b83", size = 740613, upload-time = "2026-02-14T18:05:49.726Z" },
{ url = "https://files.pythonhosted.org/packages/01/d4/e812ad36178093a0e9fd4b8127577748dd85b0cb71de912229dca21fd741/pyinstaller-6.19.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:481a909c8e60c8692fc60fcb1344d984b44b943f8bc9682f2fcdae305ad297e6", size = 740350, upload-time = "2026-02-14T18:05:54.093Z" },
{ url = "https://files.pythonhosted.org/packages/52/03/b2c2ee41fb8e10fd2a45d21f5ec2ef25852cfb978dbf762972eed59e3d63/pyinstaller-6.19.0-py3-none-win32.whl", hash = "sha256:3c5c251054fe4cfaa04c34a363dcfbf811545438cb7198304cd444756bc2edd2", size = 1324317, upload-time = "2026-02-14T18:06:00.085Z" },
{ url = "https://files.pythonhosted.org/packages/9c/d3/6d5e62b8270e2b53a6065e281b3a7785079b00e9019c8019952828dd1669/pyinstaller-6.19.0-py3-none-win_amd64.whl", hash = "sha256:b5bb6536c6560330d364d91522250f254b107cf69129d9cbcd0e6727c570be33", size = 1384894, upload-time = "2026-02-14T18:06:06.425Z" },
{ url = "https://files.pythonhosted.org/packages/81/65/458cd523308a101a22fd2742893405030cc24994cc74b1b767cecf137160/pyinstaller-6.19.0-py3-none-win_arm64.whl", hash = "sha256:c2d5a539b0bfe6159d5522c8c70e1c0e487f22c2badae0f97d45246223b798ea", size = 1325374, upload-time = "2026-02-14T18:06:12.804Z" },
]
[[package]]
name = "pyinstaller-hooks-contrib"
version = "2026.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "packaging" },
{ name = "setuptools" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c7/fe/9278c29394bf69169febc21f96b4252c3ee7c8ec22c2fc545004bed47e71/pyinstaller_hooks_contrib-2026.4.tar.gz", hash = "sha256:766c281acb1ecc32e21c8c667056d7ebf5da0aabd5e30c219f9c2a283620eeaa", size = 173050, upload-time = "2026-03-31T14:10:51.188Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/f4/035fb8c06deff827f540a9a4ed9122c54e5376fca3e42eddf0c263730775/pyinstaller_hooks_contrib-2026.4-py3-none-any.whl", hash = "sha256:1de1a5e49a878122010b88c7e295502bc69776c157c4a4dc78741a4e6178b00f", size = 455496, upload-time = "2026-03-31T14:10:49.867Z" },
]
[[package]] [[package]]
name = "pynput" name = "pynput"
version = "1.8.1" version = "1.8.1"
@@ -578,6 +649,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" },
] ]
[[package]]
name = "pywin32-ctypes"
version = "0.2.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" },
]
[[package]] [[package]]
name = "pyyaml" name = "pyyaml"
version = "6.0.3" version = "6.0.3"
@@ -755,7 +835,7 @@ wheels = [
[[package]] [[package]]
name = "whisper-local" name = "whisper-local"
version = "0.1.0" version = "1.0.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "darkdetect", marker = "sys_platform == 'win32'" }, { name = "darkdetect", marker = "sys_platform == 'win32'" },
@@ -771,6 +851,9 @@ dependencies = [
] ]
[package.dev-dependencies] [package.dev-dependencies]
build = [
{ name = "pyinstaller" },
]
dev = [ dev = [
{ name = "pytest" }, { name = "pytest" },
{ name = "pytest-asyncio" }, { name = "pytest-asyncio" },
@@ -791,6 +874,7 @@ requires-dist = [
] ]
[package.metadata.requires-dev] [package.metadata.requires-dev]
build = [{ name = "pyinstaller", specifier = ">=6.0" }]
dev = [ dev = [
{ name = "pytest", specifier = ">=8.0.0" }, { name = "pytest", specifier = ">=8.0.0" },
{ name = "pytest-asyncio", specifier = ">=0.24.0" }, { name = "pytest-asyncio", specifier = ">=0.24.0" },
+170
View File
@@ -0,0 +1,170 @@
# whisper_local.spec
# PyInstaller-Build-Konfiguration für whisper-local (Windows 64-bit, onedir)
#
# Ausführen: uv run pyinstaller whisper_local.spec --noconfirm
# Oder via: .\build.ps1
import sys
from pathlib import Path
from PyInstaller.utils.hooks import collect_data_files
block_cipher = None
SP = Path(".venv/Lib/site-packages")
# ── Binaries ──────────────────────────────────────────────────────────────────
# ctranslate2: Haupt-DLL, CUDA (cudnn) und Intel OpenMP
# DLLs landen in ctranslate2/, damit ctranslate2's os.add_dll_directory() greift
binaries_list = [
(str(SP / "ctranslate2/ctranslate2.dll"), "ctranslate2"),
(str(SP / "ctranslate2/cudnn64_9.dll"), "ctranslate2"),
(str(SP / "ctranslate2/libiomp5md.dll"), "ctranslate2"),
# pywin32: COM- und WinTypes-DLLs müssen neben der EXE liegen
(str(SP / "pywin32_system32/pythoncom313.dll"), "."),
(str(SP / "pywin32_system32/pywintypes313.dll"), "."),
# sounddevice / PortAudio
(str(SP / "_sounddevice_data/portaudio-binaries/libportaudio64bit.dll"),
"_sounddevice_data/portaudio-binaries"),
(str(SP / "_sounddevice_data/portaudio-binaries/libportaudio64bit-asio.dll"),
"_sounddevice_data/portaudio-binaries"),
# onnxruntime (für Silero VAD in faster-whisper)
(str(SP / "onnxruntime/capi/onnxruntime.dll"),
"onnxruntime/capi"),
(str(SP / "onnxruntime/capi/onnxruntime_providers_shared.dll"),
"onnxruntime/capi"),
]
# av/FFmpeg: DLL-Namen enthalten Hashes — per Glob gesammelt
av_libs_dir = SP / "av.libs"
binaries_list += [(str(dll), "av.libs") for dll in av_libs_dir.glob("*.dll")]
# ── Datas ─────────────────────────────────────────────────────────────────────
datas_list = [
# Silero VAD ONNX-Modell (benötigt von faster-whisper)
(str(SP / "faster_whisper/assets/silero_vad_v6.onnx"),
"faster_whisper/assets"),
# sv_ttk Theme (TCL-Skripte + PNG-Spritesheets)
(str(SP / "sv_ttk/sv.tcl"), "sv_ttk"),
(str(SP / "sv_ttk/theme"), "sv_ttk/theme"),
# Beispiel-Konfiguration als Referenz
("config.example.toml", "."),
]
# certifi CA-Bundle für HTTPS (huggingface_hub Modell-Download)
datas_list += collect_data_files("certifi")
# ── Hidden Imports ────────────────────────────────────────────────────────────
# Alle Module hinter sys.platform-Guards oder lazy imports (importlib, __import__)
hidden_imports_list = [
# Eigene Windows-Backends (hinter sys.platform == "win32" Guards)
"whisper_local.hotkey._pynput",
"whisper_local.inserter._win32",
"whisper_local.tray._tray",
"whisper_local.tray._icon",
"whisper_local.tray._settings",
"whisper_local.tray._theme",
# pynput: Backend wird per importlib dynamisch gewählt
"pynput.keyboard._win32",
"pynput.mouse._win32",
"pynput._util",
"pynput._util.win32",
"pynput._util.win32_vks",
# pywin32
"win32api",
"win32con",
"win32gui",
"win32clipboard",
"pywintypes",
# pystray Windows-Backend + Utility-Unterpaket (relative imports)
"pystray._win32",
"pystray._util",
"pystray._util.win32",
# tkinter: lazy import in tray/_settings.py
"tkinter",
"tkinter.ttk",
# sv_ttk und darkdetect
"sv_ttk",
"darkdetect",
# onnxruntime Inference Collection
"onnxruntime.capi.onnxruntime_inference_collection",
# huggingface_hub für Modell-Download zur Laufzeit
"huggingface_hub",
"huggingface_hub.file_download",
"huggingface_hub.utils",
]
# ── Excludes ──────────────────────────────────────────────────────────────────
excludes_list = [
# Linux-only (nicht installiert auf Windows)
"evdev",
"whisper_local.hotkey._evdev",
"whisper_local.inserter._wayland",
# Build- und Test-Infrastruktur
"pytest",
"pytest_asyncio",
"hatchling",
# Nicht benötigt im gebündelten Binary
"unittest",
"doctest",
"pdb",
]
# ── Analysis ──────────────────────────────────────────────────────────────────
a = Analysis(
["whisper_local/__main__.py"],
pathex=["."],
binaries=binaries_list,
datas=datas_list,
hiddenimports=hidden_imports_list,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=excludes_list,
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True, # onedir-Modus: DLLs bleiben separat
name="whisper-local",
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False, # UPX mit CUDA-DLLs deaktiviert
console=False, # kein Konsolenfenster (Tray-App)
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=False,
upx_exclude=[],
name="whisper-local",
)
+16 -1
View File
@@ -1,6 +1,8 @@
"""Whisper-Transkription via faster-whisper.""" """Whisper-Transkription via faster-whisper."""
import logging import logging
import sys
from pathlib import Path
import numpy as np import numpy as np
from faster_whisper import WhisperModel from faster_whisper import WhisperModel
@@ -8,11 +10,24 @@ from faster_whisper import WhisperModel
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def _model_cache_dir() -> str | None:
"""Im gebündelten Modus: Modell neben der EXE cachen (portable).
Im Entwicklungsmodus: None → HuggingFace-Standard-Cache."""
if getattr(sys, "frozen", False):
cache = Path(sys.executable).parent / "models"
try:
cache.mkdir(exist_ok=True)
return str(cache)
except OSError:
return None # Fallback auf HuggingFace-Standard-Cache
return None
class Transcriber: class Transcriber:
def __init__(self, model_name: str = "small", compute_type: str = "int8", language: str = "de"): def __init__(self, model_name: str = "small", compute_type: str = "int8", language: str = "de"):
self.language = language self.language = language
logger.info("Lade Whisper-Modell '%s' (compute_type=%s)...", model_name, compute_type) logger.info("Lade Whisper-Modell '%s' (compute_type=%s)...", model_name, compute_type)
self.model = WhisperModel(model_name, compute_type=compute_type) self.model = WhisperModel(model_name, compute_type=compute_type, download_root=_model_cache_dir())
logger.info("Modell geladen") logger.info("Modell geladen")
def transcribe(self, audio: np.ndarray) -> str: def transcribe(self, audio: np.ndarray) -> str: