| [CmdletBinding()] |
| param( |
| [string]$TargetRoot = "" |
| ) |
|
|
| $ErrorActionPreference = "Stop" |
| if (-not $TargetRoot) { $TargetRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path } |
| if (!(Test-Path $TargetRoot)) { throw "Target root does not exist: $TargetRoot" } |
| $TargetRoot = (Resolve-Path $TargetRoot).Path |
| $assetConfigPath = Join-Path $TargetRoot "config\voice-assets.json" |
| if (!(Test-Path $assetConfigPath)) { throw "Voice asset configuration is missing: $assetConfigPath" } |
| $assetConfig = Get-Content -Raw $assetConfigPath | ConvertFrom-Json |
| $assets = $assetConfig.windows_x64 |
| $shared = $assetConfig.shared |
|
|
| $cache = Join-Path $TargetRoot ".jackailocal-builder\voice-cache" |
| $whisperDir = Join-Path $TargetRoot "backends\whisper.cpp\windows" |
| $piperDir = Join-Path $TargetRoot "backends\piper\windows" |
| $whisperModelDir = Join-Path $TargetRoot "models\whisper" |
| $piperModelDir = Join-Path $TargetRoot "models\piper" |
| New-Item -ItemType Directory -Force -Path $cache,$whisperDir,$piperDir,$whisperModelDir,$piperModelDir | Out-Null |
|
|
| function Download-Verified([string]$Url, [string]$Sha256, [string]$Destination) { |
| if (Test-Path $Destination) { |
| $existing = (Get-FileHash -Algorithm SHA256 -Path $Destination).Hash.ToLowerInvariant() |
| if ($existing -eq $Sha256.ToLowerInvariant()) { return } |
| Remove-Item -LiteralPath $Destination -Force |
| } |
| Write-Host "Downloading $Url" -ForegroundColor Cyan |
| Invoke-WebRequest -Uri $Url -OutFile $Destination -UseBasicParsing |
| $actual = (Get-FileHash -Algorithm SHA256 -Path $Destination).Hash.ToLowerInvariant() |
| if ($actual -ne $Sha256.ToLowerInvariant()) { |
| Remove-Item -LiteralPath $Destination -Force |
| throw "SHA256 verification failed for $Url. Expected $Sha256, got $actual." |
| } |
| } |
|
|
| function Copy-ArchiveContents([string]$Archive, [string]$Destination, [string]$RequiredFile) { |
| $extract = Join-Path $cache ([IO.Path]::GetFileNameWithoutExtension($Archive)) |
| if (Test-Path $extract) { Remove-Item -LiteralPath $extract -Recurse -Force } |
| New-Item -ItemType Directory -Force -Path $extract | Out-Null |
| Expand-Archive -Path $Archive -DestinationPath $extract -Force |
| $required = Get-ChildItem -Path $extract -Recurse -File -Filter $RequiredFile | Select-Object -First 1 |
| if (-not $required) { throw "Required file $RequiredFile was not found in $Archive." } |
| $sourceDir = Split-Path $required.FullName -Parent |
| Copy-Item -Path (Join-Path $sourceDir "*") -Destination $Destination -Recurse -Force |
| } |
|
|
| $whisperArchive = Join-Path $cache "whisper-bin-x64.zip" |
| $piperArchive = Join-Path $cache "piper-windows-amd64.zip" |
| $whisperModel = Join-Path $whisperModelDir "ggml-base.bin" |
| $piperVoice = Join-Path $piperModelDir "en_US-libritts_r-medium.onnx" |
| $piperVoiceConfig = Join-Path $piperModelDir "en_US-libritts_r-medium.onnx.json" |
|
|
| if (!(Test-Path (Join-Path $whisperDir "whisper-cli.exe"))) { |
| Download-Verified $assets.whisper.archive_url $assets.whisper.archive_sha256 $whisperArchive |
| Copy-ArchiveContents $whisperArchive $whisperDir "whisper-cli.exe" |
| } |
| if (!(Test-Path (Join-Path $piperDir "piper.exe"))) { |
| Download-Verified $assets.piper.archive_url $assets.piper.archive_sha256 $piperArchive |
| Copy-ArchiveContents $piperArchive $piperDir "piper.exe" |
| } |
|
|
| Download-Verified $shared.whisper.model_url $shared.whisper.model_sha256 $whisperModel |
| Download-Verified $shared.piper.voice_url $shared.piper.voice_sha256 $piperVoice |
| Download-Verified $shared.piper.voice_config_url $shared.piper.voice_config_sha256 $piperVoiceConfig |
|
|
| Write-Host "Voice assets installed and verified under $TargetRoot" -ForegroundColor Green |
|
|