jblast94/aebn-vod-downloader-v2
aebn-vod-downloader v2 Python VOD downloader with a local web UI. v2 keeps the proven native segment engine and rebuilds the service layer around it: pluggable subtitle backends, an optional yt-dlp engine, hardened job lifecycle, and defaults sized for a 32-core / 64 GB node on a high-speed private tailnet. What changed from web-ui (v0.10.1) RunPod is no longer the default or a hard dependency. Subtitle generation is now a pluggable, selectable service: openai… See the full description on the dataset page: https://huggingface.co/datasets/jblast94/aebn-vod-downloader-v2.
aebn-vod-downloader v2
Python VOD downloader with a local web UI. v2 keeps the proven native segment engine and rebuilds the service layer around it: pluggable subtitle backends, an optional yt-dlp engine, hardened job lifecycle, and defaults sized for a 32-core / 64 GB node on a high-speed private tailnet.
What changed from web-ui (v0.10.1)
- RunPod is no longer the default or a hard dependency. Subtitle generation is now a pluggable, selectable service:
openai(Whisper API),gemini(Google Gemini transcription),runpod(serverless Faster-Whisper, optional), orlocal(faster-whisper on your own CPU).nonedisables subtitles entirely; nothing limits the downloader when a backend is unconfigured. - Subtitle mode is selectable:
off|post(trigger after download) |parallel(auto-starts in a background worker the moment a download finishes, never blocking the download slot). - Stale jobs don't linger: on startup, jobs left
queued/runningby a previous run are markedcancelled("Interrupted by restart") and their old logs are cleared. Logs are collapsed by default and fetched incrementally. - Internal player works:
/media/...implements HTTP Range streaming so seek/scrub works on large files;.srttracks attach automatically. - Enterprise defaults: 4 concurrent download jobs, 16 download threads, 2 concurrent subtitle workers (override via env).
- yt-dlp engine: select
ytdlpper job for generic/URL fallback downloads with concurrent fragments; output paths are captured and playable too. - Reliability: bounded
ThreadPoolExecutors replace the old semaphore + unbounded threads, DB write amplification removed, healthcheck endpoint added.
Quickstart (Docker)
cp .env.example .env # edit keys/volumes
docker compose up -d --build
open http://localhost:8787Quickstart (host, no Docker)
Requires Python 3.10+, uv, and ffmpeg in PATH.
uv sync
uv run aebndl-web # binds 127.0.0.1:8787 by default
AEBNDL_HOST=0.0.0.0 uv run aebndl-webCLI (native engine only): uv run aebndl "<url>" -r 720
Configuration (env)
local backend needs the extra: uv sync --extra local-whisper.
Network: tailnet + egress
- The UI binds to
127.0.0.1by default. On the tailnet node either setAEBNDL_HOST=0.0.0.0andAEBNDL_BIND_IP=<tailscale-ip>in compose (port published only on the tailnet interface), or keep loopback and expose withtailscale serve --bg http://127.0.0.1:8787. - Egress allowlist (outbound 443):
*.aebn.com/*.aebn.net(metadata + segment CDNs — segment hosts come from the DASH manifest, so allow the CDN domain block),api.openai.com,generativelanguage.googleapis.com,api.runpod.ai(only if used),pypi.org/*.pypi.org(install only). - Inbound: none required; anything that can reach the bound interface on 8787 can use the app — keep it on loopback or the tailnet only.
API (summary)
GET /api/config— effective settings + which backends are usableGET /api/jobs·POST /api/jobs·POST /api/jobs/{id}/cancelDELETE /api/jobs/{id}?delete_files=·DELETE /api/jobs(clear finished)POST /api/jobs/{id}/outputs/{index}/subtitles— generate.srtfor one outputGET /api/jobs/{id}/logs?after=N— incremental log fetchGET /media/{job_id}/{index}— Range-streaming playbackGET /subtitle-files/{job_id}/{index}— serve generated.srtPOST /api/info— metadata preview before downloadGET /healthz— health probe for compose/orchestrators
