Videos view: multi-select (checkboxes) + select-all/deselect-all + bulk action
bar (Download .md batch, Download thumbnails, Download audio). Per-video buttons:
.md download (done) or Process to .md (pending), clip.
Channels: per-channel pending count + 'Download pending .md' (batches pending
video_ids). Each channel keeps Videos/.md folder/Remove actions.
Backend (jobs.py + api.py):
- POST /api/scrape/batch {video_ids} + POST /api/scrape/video/{id} (on-demand
.md for any video incl. pending) as SSE-tracked jobs
- GET /api/videos/{id}/markdown (file download)
- POST /api/tools/thumbnails + GET /api/thumbnails/{id} (local cache, offline)
- GET /api/clip/{id}?from=&to= (transcript segment)
- GET /api/stats (aggregate totals)
- POST /api/tools/audio (mp3 job)
Tools view rebuilt as full Knowledge Base grid surfacing every OPPORTUNITIES.md
feature as electable buttons (md download, re-render, export, search, analysis,
thumbnails, audio, clip, stats, cookies, channels, watch, open folders).
Floating job widget (SSE) for any running job. Thumbnails served via
/api/thumbnails/{id} everywhere (works offline once cached).
Launcher: robust Open-Browser helper (3 fallback methods) auto-opens
http://127.0.0.1:<port> on start + on idempotent re-open. data/ gitignored.
103 lines
4.1 KiB
PowerShell
103 lines
4.1 KiB
PowerShell
$ErrorActionPreference = 'Stop'
|
|
$root = Split-Path -Parent $PSScriptRoot
|
|
Set-Location -LiteralPath $root
|
|
|
|
# Robust browser opener — tries several Windows methods until one works.
|
|
function Open-Browser($url) {
|
|
$opened = $false
|
|
# Method 1: Start-Process with the URL (default protocol handler)
|
|
try { Start-Process -FilePath $url; $opened = $true } catch {}
|
|
if (-not $opened) {
|
|
# Method 2: explorer.exe with the URL (opens default browser)
|
|
try { & explorer.exe $url; $opened = $true } catch {}
|
|
}
|
|
if (-not $opened) {
|
|
# Method 3: cmd `start` builtin
|
|
try { & cmd.exe /c start "" $url; $opened = $true } catch {}
|
|
}
|
|
if ($opened) { Write-Host " [ok] abriendo navegador -> $url" -ForegroundColor DarkGray }
|
|
else { Write-Host " [warn] no se pudo abrir el navegador. Abre manualmente: $url" -ForegroundColor Yellow }
|
|
return $opened
|
|
}
|
|
|
|
Write-Host ""
|
|
Write-Host " [yt-scraper] iniciando servidor local..." -ForegroundColor Cyan
|
|
|
|
if (-not (Test-Path '.run')) { New-Item -ItemType Directory -Path '.run' | Out-Null }
|
|
|
|
# Already running?
|
|
if (Test-Path '.run\server.info') {
|
|
$firstLine = (Get-Content '.run\server.info' -TotalCount 1)
|
|
$existingPort = ($firstLine -split '\s+')[0]
|
|
try {
|
|
$r = Invoke-WebRequest -Uri "http://localhost:$existingPort/healthz" -UseBasicParsing -TimeoutSec 2
|
|
if ($r.Content -match 'ok') {
|
|
Write-Host " [ok] servidor ya activo en el puerto $existingPort" -ForegroundColor Green
|
|
Open-Browser "http://127.0.0.1:$existingPort"
|
|
exit 0
|
|
}
|
|
} catch {}
|
|
}
|
|
|
|
# Collect candidate free ports scanning from 8000.
|
|
$candidates = @()
|
|
foreach ($p in 8000..8100) {
|
|
try {
|
|
$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, $p)
|
|
$listener.Start(); $listener.Stop()
|
|
$candidates += $p
|
|
if ($candidates.Count -ge 8) { break }
|
|
} catch {}
|
|
}
|
|
if ($candidates.Count -eq 0) {
|
|
Write-Host " [error] no se encontro ningun puerto libre (8000-8100)" -ForegroundColor Red
|
|
exit 2
|
|
}
|
|
|
|
# Launch uvicorn DETACHED in its own persistent minimized console window, logging to .run/server.log.
|
|
# The `cmd /c` window is an independent console: it survives this launcher exiting.
|
|
$launched = $false
|
|
$proc = $null
|
|
$port = 0
|
|
foreach ($candidate in $candidates) {
|
|
# clear previous log
|
|
Remove-Item '.run\server.log' -Force -ErrorAction SilentlyContinue
|
|
$cmdLine = '/c python -m uvicorn yt_scraper.webapp.app:app --host 127.0.0.1 --port ' + $candidate + ' --log-level info > .run\server.log 2>&1'
|
|
$proc = Start-Process -FilePath 'cmd.exe' -ArgumentList $cmdLine -WorkingDirectory $root -WindowStyle Minimized -PassThru
|
|
Set-Content -Path '.run\server.info' -Value "$candidate $($proc.Id) $(Get-Date -Format o)" -Encoding ASCII
|
|
|
|
$ok = $false
|
|
for ($i = 0; $i -lt 40; $i++) {
|
|
Start-Sleep -Milliseconds 500
|
|
try {
|
|
$r = Invoke-WebRequest -Uri "http://localhost:$candidate/healthz" -UseBasicParsing -TimeoutSec 2
|
|
if ($r.Content -match 'ok') { $ok = $true; break }
|
|
} catch {}
|
|
}
|
|
if ($ok) { $port = $candidate; $launched = $true; break }
|
|
|
|
# failed on this port — show why from the log, then try the next candidate
|
|
Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue
|
|
Remove-Item '.run\server.info' -Force -ErrorAction SilentlyContinue
|
|
}
|
|
|
|
if (-not $launched) {
|
|
Write-Host " [error] no se pudo iniciar el servidor." -ForegroundColor Red
|
|
if (Test-Path '.run\server.log') {
|
|
Write-Host " --- ultimo log del servidor ---" -ForegroundColor Yellow
|
|
Get-Content '.run\server.log' -Tail 25 | ForEach-Object { Write-Host " $_" }
|
|
} else {
|
|
Write-Host " instala dependencias: pip install -e `".[web]`"" -ForegroundColor Yellow
|
|
}
|
|
exit 3
|
|
}
|
|
|
|
Open-Browser "http://127.0.0.1:$port"
|
|
Write-Host ""
|
|
Write-Host " [ok] servidor activo" -ForegroundColor Green
|
|
Write-Host " URL: http://localhost:$port"
|
|
Write-Host " PID: $($proc.Id) (ventana minimizada 'cmd' independiente)"
|
|
Write-Host " log: .run\server.log"
|
|
Write-Host " stop: stop-server.bat"
|
|
Write-Host ""
|