install.ps1
Rama main · réplica de lectura del repositorio original.
# Speech-to-Text Installation Script for Windows
# Installs Python, ffmpeg, creates venv, and installs all dependencies
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
# Configuration
$INSTALL_DIR = $PSScriptRoot
$SERVICE_NAME = "stt-server"
$PYTHON_BIN = ""
$VENV_DIR = Join-Path $INSTALL_DIR ".venv"
# --- Output helpers --------------------------------------------------
function Print-Status { param($msg) Write-Host "[INFO] " -ForegroundColor Blue -NoNewline; Write-Host $msg }
function Print-Success { param($msg) Write-Host "[OK] " -ForegroundColor Green -NoNewline; Write-Host $msg }
function Print-Error { param($msg) Write-Host "[ERROR] " -ForegroundColor Red -NoNewline; Write-Host $msg }
function Print-Warning { param($msg) Write-Host "[WARNING] " -ForegroundColor Yellow -NoNewline; Write-Host $msg }
# --- Check admin -----------------------------------------------------
function Test-Admin {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = New-Object Security.Principal.WindowsPrincipal($identity)
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
}
# --- Refresh PATH from registry (picks up new installs without restart) --
function Refresh-Path {
$machinePath = [Environment]::GetEnvironmentVariable("Path", "Machine")
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
$env:Path = "$machinePath;$userPath"
}
# --- Check if a command exists ---------------------------------------
function Test-Command {
param($Name)
return [bool](Get-Command $Name -ErrorAction SilentlyContinue)
}
# --- Install winget packages ----------------------------------------
function Install-SystemDeps {
Print-Status "Checking system dependencies..."
# --- Python ---
if (Test-Command "python") {
$pyVer = & python --version 2>&1
Print-Success "Python already installed: $pyVer"
} else {
Print-Status "Installing Python via winget..."
if (Test-Command "winget") {
winget install --id Python.Python.3.11 --accept-source-agreements --accept-package-agreements
Refresh-Path
} else {
Print-Error "Python not found and winget is not available."
Print-Error "Please install Python 3.10+ from https://www.python.org/downloads/"
Print-Error "IMPORTANT: Check 'Add Python to PATH' during installation."
exit 1
}
}
# Verify Python
if (-not (Test-Command "python")) {
Refresh-Path
if (-not (Test-Command "python")) {
Print-Error "Python not found in PATH after installation."
Print-Error "Please install Python manually and ensure 'Add Python to PATH' is checked."
exit 1
}
}
# --- Git ---
if (Test-Command "git") {
Print-Success "Git already installed"
} else {
Print-Status "Installing Git via winget..."
if (Test-Command "winget") {
winget install --id Git.Git --accept-source-agreements --accept-package-agreements
Refresh-Path
} else {
Print-Warning "Git not found. Install from https://git-scm.com/download/win"
Print-Warning "Not required to install: auto-updates fall back to archive downloads."
}
}
# --- FFmpeg ---
if (Test-Command "ffmpeg") {
Print-Success "FFmpeg already installed"
} else {
Print-Status "Installing FFmpeg via winget..."
if (Test-Command "winget") {
winget install --id Gyan.FFmpeg --accept-source-agreements --accept-package-agreements
Refresh-Path
}
if (-not (Test-Command "ffmpeg")) {
Print-Warning "FFmpeg not found in PATH after install attempt."
Print-Warning "You may need to add FFmpeg to your PATH manually or restart your terminal."
Print-Warning "Download from: https://www.gyan.dev/ffmpeg/builds/"
}
}
Print-Success "System dependency check complete"
}
# --- Install uv ------------------------------------------------------
function Install-Uv {
Print-Status "Installing uv package manager..."
if (Test-Command "uv") {
$uvVer = & uv --version 2>&1
Print-Success "uv already installed: $uvVer"
return
}
Print-Status "Downloading uv installer..."
Invoke-RestMethod https://astral.sh/uv/install.ps1 | Invoke-Expression
Refresh-Path
# Also check common install location
$uvPath = Join-Path $env:USERPROFILE ".local\bin"
if (Test-Path $uvPath) {
$env:Path = "$uvPath;$env:Path"
}
# Also check cargo bin (alternative install location)
$cargoPath = Join-Path $env:USERPROFILE ".cargo\bin"
if (Test-Path $cargoPath) {
$env:Path = "$cargoPath;$env:Path"
}
if (Test-Command "uv") {
$uvVer = & uv --version 2>&1
Print-Success "uv installed: $uvVer"
} else {
Print-Error "uv installation failed. Falling back to pip."
}
}
# --- Create virtual environment --------------------------------------
function Create-Venv {
Print-Status "Creating Python virtual environment..."
if (Test-Command "uv") {
# --clear replaces an existing venv without the interactive y/n prompt
& uv venv --clear $VENV_DIR
} else {
& python -m venv $VENV_DIR
}
$script:PYTHON_BIN = Join-Path $VENV_DIR "Scripts\python.exe"
if (-not (Test-Path $PYTHON_BIN)) {
Print-Error "Failed to create virtual environment"
exit 1
}
Print-Success "Virtual environment created at $VENV_DIR"
}
# --- Detect GPU ------------------------------------------------------
function Detect-Gpu {
Print-Status "Detecting GPU..."
if (Test-Command "nvidia-smi") {
Print-Success "NVIDIA GPU detected:"
# To the host, not the pipeline: a bare command's output becomes part of the
# function's return value, so the name never printed and callers got an array.
& nvidia-smi --query-gpu=name,memory.total --format=csv,noheader 2>$null |
ForEach-Object { Write-Host " $_" }
$cudaLine = & nvidia-smi 2>$null | Select-String "CUDA Version"
if ($cudaLine) {
$cudaVer = ($cudaLine -split '\s+' | Where-Object { $_ -match '^\d+\.\d+' } | Select-Object -Last 1)
Print-Status "CUDA Version: $cudaVer"
}
return $true
} else {
Print-Warning "No NVIDIA GPU detected (nvidia-smi not found)"
Print-Warning "Will install CPU-only PyTorch"
return $false
}
}
# --- Pick the torch wheel index this GPU can actually run -----------
# cu128 dropped the pre-Volta architectures. On a Pascal card (GTX 10-series, sm_61)
# a cu128 install still reports cuda.is_available() True and then fails every kernel
# launch with "no kernel image is available for execution on the device" -- so the
# index is chosen by compute capability, not by the presence of nvidia-smi.
# Mirrors stt/cuda_index.py, which carries the tests; keep the two in step.
function Get-TorchIndexUrl {
$cap = & nvidia-smi --query-gpu=compute_cap --format=csv,noheader 2>$null
$lowest = $null
foreach ($line in @($cap)) {
$value = 0.0
if ([double]::TryParse(("" + $line).Trim(), [ref]$value)) {
if ($null -eq $lowest -or $value -lt $lowest) { $lowest = $value }
}
}
if ($null -eq $lowest) {
# Unreadable capability: install the default wheel rather than guess. A CPU
# build is slow and obvious; a CUDA build with no kernels for the card looks
# perfect until the first service.
Print-Warning "Could not read GPU compute capability -- installing default wheels"
return $null
}
if ($lowest -ge 7.0) { return "https://download.pytorch.org/whl/cu128" }
Print-Status "GPU compute capability $lowest is pre-Volta -- using the cu126 wheels"
return "https://download.pytorch.org/whl/cu126"
}
# Compiled packages that must arrive as wheels or not at all. uv/pip otherwise
# fall back to building from source when no wheel matches the platform, which
# needs a compiler this machine may not have; refusing the fallback makes the
# resolver pick a release that does ship a wheel here. Mirrors
# stt/wheel_policy.py, which is the source of truth and carries the tests.
$ONLY_BINARY = "llvmlite,numba,numpy,scikit-learn,scipy,soxr"
# --- Install Python packages ----------------------------------------
function Install-PythonDeps {
Print-Status "Installing Python dependencies from requirements.txt..."
$reqFile = Join-Path $INSTALL_DIR "requirements.txt"
$hasGpu = Detect-Gpu
$indexUrl = if ($hasGpu) { Get-TorchIndexUrl } else { $null }
if (Test-Command "uv") {
if ($indexUrl) {
Print-Status "Installing GPU-enabled packages ($indexUrl)..."
& uv pip install --python $PYTHON_BIN -r $reqFile --only-binary $ONLY_BINARY --extra-index-url $indexUrl
} else {
Print-Status "Installing default packages..."
& uv pip install --python $PYTHON_BIN -r $reqFile --only-binary $ONLY_BINARY
}
} else {
# Fallback to pip
& $PYTHON_BIN -m pip install --upgrade pip
if ($indexUrl) {
Print-Status "Installing GPU-enabled packages ($indexUrl)..."
& $PYTHON_BIN -m pip install -r $reqFile --only-binary $ONLY_BINARY --extra-index-url $indexUrl
} else {
Print-Status "Installing default packages..."
& $PYTHON_BIN -m pip install -r $reqFile --only-binary $ONLY_BINARY
}
}
if ($LASTEXITCODE -ne 0) {
Print-Error "Some packages failed to install. Check the output above."
Print-Warning "You can try installing manually: .venv\Scripts\pip install -r requirements.txt --only-binary $ONLY_BINARY"
} else {
Print-Success "Python dependencies installed"
}
}
# --- Verify installation --------------------------------------------
function Verify-PythonDeps {
Print-Status "Verifying Python installation..."
$verifyScript = @"
import sys
errors = []
try:
import torch
print(f' PyTorch {torch.__version__}')
if torch.cuda.is_available():
print(f' CUDA available: {torch.cuda.get_device_name(0)}')
else:
print(' GPU not available (CPU mode)')
except ImportError as e:
errors.append(str(e))
for mod in ['speech_recognition', 'numpy', 'flask', 'soundfile']:
try:
__import__(mod)
print(f' {mod} OK')
except ImportError as e:
errors.append(str(e))
if errors:
print(f'\nMissing packages: {errors}')
sys.exit(1)
else:
print('\n All critical packages verified!')
"@
& $PYTHON_BIN -c $verifyScript
if ($LASTEXITCODE -eq 0) {
Print-Success "Python verification passed"
} else {
Print-Warning "Some packages could not be verified (see above)"
}
}
# --- Verify scripts exist --------------------------------------------
function Verify-Scripts {
Print-Status "Checking server management scripts..."
$scripts = @("start_server.bat", "stop_server.bat", "restart_server.bat")
foreach ($s in $scripts) {
$path = Join-Path $INSTALL_DIR $s
if (Test-Path $path) {
Print-Success "$s found"
} else {
Print-Warning "$s not found -- download it from the repository"
}
}
}
# --- Optional: Task Scheduler auto-start -----------------------------
function Setup-TaskScheduler {
Write-Host ""
$reply = Read-Host "Would you like to set up auto-start on login via Task Scheduler? (y/N)"
if ($reply -notmatch '^[Yy]$') { return }
if (-not (Test-Admin)) {
Print-Warning "Task Scheduler setup requires Administrator privileges."
Print-Warning "Please re-run this script as Administrator to set up auto-start."
return
}
$taskAction = New-ScheduledTaskAction `
-Execute (Join-Path $VENV_DIR "Scripts\python.exe") `
-Argument "speech_to_text.py" `
-WorkingDirectory $INSTALL_DIR
$taskTrigger = New-ScheduledTaskTrigger -AtLogOn
# ExecutionTimeLimit zero = no limit. The default is 72 hours, after which Task
# Scheduler kills the task: a server left running over a long weekend just stopped.
$taskSettings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit ([TimeSpan]::Zero) `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-RestartCount 3 `
-RestartInterval (New-TimeSpan -Minutes 1)
Register-ScheduledTask `
-TaskName $SERVICE_NAME `
-Action $taskAction `
-Trigger $taskTrigger `
-Settings $taskSettings `
-Description "Speech-to-Text Server" `
-RunLevel Highest `
-Force
Print-Success "Task Scheduler entry created: $SERVICE_NAME"
Print-Status "The server will auto-start when you log in."
Print-Status "Manage it in Task Scheduler (taskschd.msc) or with:"
Print-Status " schtasks /Run /TN $SERVICE_NAME - Start now"
Print-Status " schtasks /End /TN $SERVICE_NAME - Stop"
Print-Status " schtasks /Delete /TN $SERVICE_NAME - Remove"
}
# --- Final instructions ---------------------------------------------
function Show-FinalInstructions {
Write-Host ""
Write-Host "========================================"
Write-Host " Installation Complete!"
Write-Host "========================================"
Write-Host ""
Print-Success "Speech-to-Text has been installed successfully"
Write-Host ""
Write-Host "Quick Start:"
Write-Host " 1. Run: start_server.bat"
Write-Host " 2. Open browser: http://localhost:8080"
Write-Host " 3. Go to /model-manager to download and select a model"
Write-Host " 4. Go to /live-settings to configure audio"
Write-Host ""
Write-Host "Configuration:"
# The server seeds and reads its own copy under the data dir. Naming the bundled
# template here sent operators to edit a file nothing reads: settings looked
# ignored, with no warning, until the startup log line gave the real path away.
Write-Host " - Main config: $env:USERPROFILE\.stt\config\config.json"
Write-Host " (created on first run; set STT_DATA_DIR to put the data dir elsewhere)"
Write-Host ""
Write-Host "Manual start (from this directory):"
Write-Host " .venv\Scripts\python.exe speech_to_text.py"
Write-Host ""
if (-not (Test-Command "ffmpeg")) {
Print-Warning "FFmpeg was not found in PATH. You may need to:"
Print-Warning " 1. Restart your terminal/PC after installation"
Print-Warning " 2. Or install manually from https://www.gyan.dev/ffmpeg/builds/"
Write-Host ""
}
}
# --- Main ------------------------------------------------------------
function Main {
Write-Host ""
Write-Host "========================================"
Write-Host " Speech-to-Text Windows Installer"
Write-Host "========================================"
Write-Host ""
Push-Location $INSTALL_DIR
try {
Install-SystemDeps
Install-Uv
Create-Venv
Install-PythonDeps
Verify-PythonDeps
Verify-Scripts
Setup-TaskScheduler
Show-FinalInstructions
} finally {
Pop-Location
}
}
# Run
Main