diff --git a/scripts/doctor.ps1 b/scripts/doctor.ps1 new file mode 100644 index 0000000..d77e298 --- /dev/null +++ b/scripts/doctor.ps1 @@ -0,0 +1,173 @@ +# doctor.ps1 -- prepara el entorno para arrancar yt-scraper y delega en +# start-server.ps1. Pensado para usuarios no tecnicos: doble clic y listo. +# +# Uso: powershell -File doctor.ps1 [-CheckOnly] [args para start-server] +# -CheckOnly solo diagnostica e informa que haria; no instala ni abre nada. +# +# Exit codes: 0 ok | 10 sin Python instalable | 11 dependencias no reparadas +# (cualquier otro codigo viene propagado de start-server.ps1) + +param( + [switch]$CheckOnly +) + +$ErrorActionPreference = 'Stop' +$root = Split-Path -Parent $PSScriptRoot +Set-Location -LiteralPath $root +try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {} + +function Out-Line($msg, $color = $null) { + if ($color) { Write-Host $msg -ForegroundColor $color } + else { Write-Host $msg } + [Console]::Out.Flush() +} + +function Test-PortOpen($port, $timeoutMs = 350) { + $client = New-Object System.Net.Sockets.TcpClient + try { + $iar = $client.BeginConnect('127.0.0.1', [int]$port, $null, $null) + if (-not $iar.AsyncWaitHandle.WaitOne($timeoutMs)) { return $false } + $client.EndConnect($iar) + return $true + } catch { return $false } + finally { $client.Close() } +} + +# Devuelve la ruta CONCRETA de python.exe si el candidato es un Python +# >= 3.10 REAL; $null si no existe, es el stub de la Microsoft Store o es +# demasiado viejo. Resuelve via sys.executable porque el launcher 'py' puede +# cambiar su default al instalarse otro Python. +function Resolve-PythonCandidate($tokens) { + $name = $tokens[0] + $cmd = Get-Command $name -ErrorAction SilentlyContinue + if (-not $cmd) { return $null } + # Stub de la Microsoft Store: vive en WindowsApps y no ejecuta nada. + if ($cmd.Source -and $cmd.Source -like '*WindowsApps*') { return $null } + try { + $rest = @($tokens | Select-Object -Skip 1) + $out = & $cmd.Source @($rest + @('-c', 'import sys; print(sys.executable)')) 2>&1 + if ($LASTEXITCODE -ne 0) { return $null } + $exePath = (($out | Out-String).Trim() -split "`r?`n")[-1].Trim() + if (-not ($exePath -like '*python*')) { return $null } + $vout = & $exePath --version 2>&1 + if ($LASTEXITCODE -ne 0) { return $null } + $s = ($vout | Out-String).Trim() + if ($s -notmatch '^Python\s+(\d+)\.(\d+)') { return $null } + if ([int]$Matches[1] -lt 3 -or ([int]$Matches[1] -eq 3 -and [int]$Matches[2] -lt 10)) { return $null } + return $exePath + } catch { return $null } +} + +# True si ese interprete ya tiene las dependencias del proyecto instaladas. +function Test-Deps($exe) { + try { + & $exe -c 'import fastapi, uvicorn, sse_starlette, jinja2, yaml, yt_dlp, requests, slugify' *> $null + return ($LASTEXITCODE -eq 0) + } catch { return $false } +} + +Out-Line "" +Out-Line " [yt-scraper] verificando tu sistema..." Cyan + +# --- (1) Server ya vivo? Abrir navegador y terminar. ----------------------- +if (Test-Path '.run\server.info') { + $parts = ((Get-Content '.run\server.info' -TotalCount 1) -split '\s+') + if ($parts.Count -ge 1 -and $parts[0] -match '^\d+$' -and (Test-PortOpen ([int]$parts[0]) 350)) { + Out-Line " [ok] El servidor ya estaba corriendo - abriendo..." Green + Out-Line " http://127.0.0.1:$($parts[0])" + if (-not $CheckOnly) { try { Start-Process "http://127.0.0.1:$($parts[0])" } catch {} } + exit 0 + } +} + +# --- (2) Resolver Python ---------------------------------------------------- +# Maquinas con varios Pythons son comunes: se prefiere el primero que ademas +# pasa el import-test de dependencias; si ninguno lo pasa, vale el primero +# con version valida (la rama (3) instalara las deps ahi). +$candidates = @(,@('py','-3')) + @(,@('python')) + @(,@('python3')) +$pythonExe = $null +foreach ($cand in $candidates) { + $exe = Resolve-PythonCandidate $cand + if (-not $exe) { continue } + if (-not $pythonExe) { $pythonExe = $exe } + if (Test-Deps $exe) { $pythonExe = $exe; break } +} + +if (-not $pythonExe) { + Out-Line " [info] Python no esta instalado. Instalandolo automaticamente (~25 MB)..." Yellow + if ($CheckOnly) { + Out-Line " [check] AQUI: winget install Python.Python.3.12 + busqueda de ruta nueva" DarkGray + exit 10 + } + $winget = Get-Command winget -ErrorAction SilentlyContinue + if (-not $winget) { + Out-Line " [!] No pude instalar Python automaticamente (falta winget)." Red + Out-Line " Abriendo la pagina de descarga de Python..." Gray + Out-Line " Instalalo (marca 'Add python.exe to PATH') y vuelve a hacer doble clic." Gray + try { Start-Process 'https://www.python.org/downloads/' } catch {} + exit 10 + } + & winget install --id Python.Python.3.12 --silent --accept-package-agreements --accept-source-agreements | Out-Null + if ($LASTEXITCODE -ne 0) { + Out-Line " [!] La instalacion de Python fallo (codigo $LASTEXITCODE). Reinstala manualmente:" Red + Out-Line " https://www.python.org/downloads/" Gray + exit 10 + } + # El PATH de esta sesion no se refresca: sondear rutas conocidas. + $fresh = @() + $fresh += Get-ChildItem "$env:LOCALAPPDATA\Programs\Python\Python3*\python.exe" -ErrorAction SilentlyContinue + $fresh += Get-ChildItem 'C:\Program Files\Python3*\python.exe' -ErrorAction SilentlyContinue + foreach ($f in ($fresh | Sort-Object FullName -Descending)) { + $pythonExe = Resolve-PythonCandidate @($f.FullName) + if ($pythonExe) { break } + } + if (-not $pythonExe) { $pythonExe = Resolve-PythonCandidate @('py','-3') } + if (-not $pythonExe) { + Out-Line " [!] Se instalo Python pero no lo encuentro. Cierra esta ventana," Red + Out-Line " abre una nueva e intenta de nuevo (el PATH se refresca al reabrir)." Gray + exit 10 + } +} +Out-Line " [ok] Python encontrado: $pythonExe" DarkGray + +# --- (3) Dependencias -------------------------------------------------------- +$depsOk = Test-Deps $pythonExe + +if (-not $depsOk) { + if ($CheckOnly) { + Out-Line " [check] AQUI: instalar dependencias -> & '$pythonExe' -m pip install -e `".[web]`"" DarkGray + exit 11 + } + Out-Line " [info] Instalando las piezas que faltan por primera vez (puede tardar 1-2 min)..." Yellow + try { + & $pythonExe -m pip --version *> $null + if ($LASTEXITCODE -ne 0) { & $pythonExe -m ensurepip --upgrade | Out-Null } + } catch { + & $pythonExe -m ensurepip --upgrade | Out-Null + } + & $pythonExe -m pip install -e ".[web]" + if ($LASTEXITCODE -ne 0) { + Out-Line " [!] No pude instalar las dependencias (revisa tu conexion a internet)" Red + Out-Line " y vuelve a hacer doble clic en start-server.bat" Gray + exit 11 + } +} +if ($CheckOnly) { Out-Line " [check] dependencias: OK" DarkGray } + +# --- (4) ffmpeg (opcional: solo audio) -------------------------------------- +if (-not (Get-Command ffmpeg -ErrorAction SilentlyContinue)) { + Out-Line " [aviso] La descarga de AUDIO no estara disponible (falta ffmpeg)." Yellow + Out-Line " Todo lo demas funciona perfecto. Puedes ignorarlo." Gray +} + +# --- (5) Delegar al launcher ------------------------------------------------- +if ($CheckOnly) { + Out-Line " [check] AQUI: lanzaria scripts\start-server.ps1 -PythonExe '$pythonExe'" DarkGray + Out-Line "" + Out-Line " Diagnostico completo. Todo listo para arrancar." Green + exit 0 +} + +Out-Line "" +& (Join-Path $PSScriptRoot 'start-server.ps1') -PythonExe $pythonExe @args +exit $LASTEXITCODE