Files
Live-streaming/scripts/build.ps1
T
2026-08-15 14:43:56 +08:00

210 lines
6.4 KiB
PowerShell

[CmdletBinding()]
param(
[switch]$SkipInstall,
[switch]$FullTts,
[switch]$Lite,
[switch]$NoAdmin
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
$OutputEncoding = [System.Text.UTF8Encoding]::new($false)
$Root = Resolve-Path (Join-Path $PSScriptRoot "..")
$AdminRoot = Join-Path $Root "frontend\admin"
$DistRoot = Join-Path $Root "dist"
$AppDist = Join-Path $DistRoot "LiveStreaming"
$BuildRoot = Join-Path $Root "build"
$PyBuild = Join-Path $BuildRoot "pyinstaller"
function Write-Step([string]$Message) {
Write-Host ""
Write-Host "==> $Message" -ForegroundColor Cyan
}
function Invoke-Checked([string]$File, [string[]]$Arguments) {
& $File @Arguments
if ($LASTEXITCODE -ne 0) {
throw "$File $($Arguments -join ' ') failed with exit code $LASTEXITCODE."
}
}
function Get-ProjectPython {
$candidates = @(
$env:LIVE_STREAMING_PYTHON,
"E:\Programs\Anaconda3\envs\Live-streaming\python.exe",
"python"
) | Where-Object { $_ }
foreach ($candidate in $candidates) {
try {
$version = & $candidate -c "import sys; print(sys.version.split()[0])" 2>$null
if ($LASTEXITCODE -eq 0 -and $version) {
Write-Host "Python: $candidate ($version)"
return $candidate
}
} catch {
}
}
throw "No usable Python found. Install Python 3.10+, set LIVE_STREAMING_PYTHON, or use the Live-streaming conda env."
}
$Python = Get-ProjectPython
$PythonPrefix = (& $Python -c "import sys; print(sys.prefix)").Trim()
if ($LASTEXITCODE -ne 0 -or -not $PythonPrefix) {
throw "Unable to resolve Python prefix from $Python."
}
$CondaBin = Join-Path $PythonPrefix "Library\bin"
if (Test-Path $CondaBin) {
$env:PATH = "$CondaBin;$env:PATH"
}
if (-not $SkipInstall) {
Write-Step "Install Python runtime/build dependencies"
Invoke-Checked $Python @("-m", "pip", "install", "-r", (Join-Path $Root "requirements.txt"))
}
Write-Step "Build Vue admin dist"
if (-not (Test-Path (Join-Path $AdminRoot "node_modules"))) {
Invoke-Checked "npm" @("--prefix", $AdminRoot, "install")
}
Invoke-Checked "npm" @("--prefix", $AdminRoot, "run", "build")
Write-Step "Prepare PyInstaller output"
# 确保没有残留进程占用 dist 目录
$killed = Get-Process -Name "LiveStreaming" -ErrorAction SilentlyContinue
if ($killed) {
$killed | Stop-Process -Force -ErrorAction SilentlyContinue
$killed | Wait-Process -Timeout 5 -ErrorAction SilentlyContinue
Start-Sleep -Seconds 2
Write-Host " Stopped LiveStreaming.exe before build"
}
function Remove-WithRetry([string]$Path) {
if (-not (Test-Path $Path)) { return }
$retries = 3
while ($retries -gt 0) {
try {
Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction Stop
return
} catch {
$retries--
if ($retries -eq 0) {
Write-Host " WARNING: Cannot remove $Path, skipping" -ForegroundColor Yellow
} else {
Write-Host " Retry remove $Path ($retries left)..." -ForegroundColor Yellow
Start-Sleep -Seconds 2
}
}
}
}
Remove-WithRetry $AppDist
Remove-WithRetry $PyBuild
New-Item -ItemType Directory -Force -Path $DistRoot | Out-Null
New-Item -ItemType Directory -Force -Path $PyBuild | Out-Null
$excludeModules = @(
"tkinter",
"pytest",
"IPython",
"jupyter",
"notebook",
"matplotlib.tests",
"numpy.tests",
"pandas.tests",
"scipy.tests",
"torch.testing"
)
if ($Lite) {
$excludeModules += @(
"torch",
"torchaudio",
"torchvision",
"transformers",
"faster_qwen3_tts",
"dots_tts"
)
} else {
$excludeModules += @(
"torch.distributed",
"torch.utils.tensorboard",
"torchvision",
"torchaudio._internal",
"torch.utils.benchmark",
"torch.testing",
"torch.onnx",
"tensorboard",
"tensorboardX"
)
}
$pyiArgs = @(
"--noconfirm",
"--onedir",
"--name", "LiveStreaming",
"--distpath", $DistRoot,
"--workpath", $PyBuild,
"--specpath", $BuildRoot,
"--paths", (Join-Path $Root "app"),
"--hidden-import", "danmu_queue",
"--hidden-import", "music_monitor",
"--hidden-import", "tts_monitor",
"--hidden-import", "core.runtime_paths"
)
foreach ($module in $excludeModules) {
$pyiArgs += @("--exclude-module", $module)
}
$condaDlls = @("ffi.dll", "liblzma.dll", "libbz2.dll", "libexpat.dll")
if (Test-Path $CondaBin) {
foreach ($dll in $condaDlls) {
$dllPath = Join-Path $CondaBin $dll
if (Test-Path $dllPath) {
$pyiArgs += @("--add-binary", "$dllPath;.")
}
}
}
if (-not $NoAdmin) {
$pyiArgs += "--uac-admin"
}
$pyiArgs += (Join-Path $Root "app\main.py")
Write-Step "Package LiveStreaming.exe"
# PyInstaller 隔离模式与 torch DLL 冲突,设置环境变量跳过 CUDA 加载
$env:CUDA_VISIBLE_DEVICES = ""
$env:PYTORCH_NVFUSER_DISABLE = "1"
$SpecFile = Join-Path $BuildRoot "LiveStreaming.spec"
if (Test-Path $SpecFile) {
Write-Host " Reusing spec (incremental) ..."
Invoke-Checked $Python @("-m", "PyInstaller", "--noconfirm", $SpecFile)
} else {
Invoke-Checked $Python (@("-m", "PyInstaller") + $pyiArgs)
}
Write-Step "Create runtime directories"
$ExePath = Join-Path $AppDist "LiveStreaming.exe"
if (-not (Test-Path $ExePath)) {
throw "PyInstaller finished without creating $ExePath."
}
New-Item -ItemType Directory -Force -Path (Join-Path $AppDist "data") | Out-Null
New-Item -ItemType Directory -Force -Path (Join-Path $AppDist "logs") | Out-Null
Copy-Item -LiteralPath (Join-Path $Root "web") -Destination (Join-Path $AppDist "web") -Recurse -Force
Copy-Item -LiteralPath (Join-Path $Root "config") -Destination (Join-Path $AppDist "config") -Recurse -Force
Copy-Item -LiteralPath (Join-Path $Root "integrations") -Destination (Join-Path $AppDist "integrations") -Recurse -Force
Copy-Item -LiteralPath (Join-Path $Root "vendor") -Destination (Join-Path $AppDist "vendor") -Recurse -Force
Copy-Item -LiteralPath (Join-Path $Root "README.md") -Destination (Join-Path $AppDist "README.md") -Force
Write-Host ""
Write-Host "Build complete: $AppDist\LiveStreaming.exe" -ForegroundColor Green
if ($Lite) {
Write-Host "Lite build: TTS/GPU dependencies excluded."
} else {
Write-Host "Full build with TTS support."
}