You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

11 KiB

ROADMAP

Goal: deliver a containerised YouTube downloader web app — one URL input, two download buttons (MP3 / Video), always best available quality.

Priority 1

Objective: Containerised YouTube downloader with a minimal browser UI.

  • User pastes a YouTube URL into a single input field.
  • Clicking Download MP3 triggers a server-side yt-dlp run that extracts audio at the highest available bitrate (converted to MP3 via ffmpeg) and streams the file back to the browser for download.
  • Clicking Download Video triggers a server-side yt-dlp run that fetches the best video+audio quality and streams the file back to the browser for download.
  • The entire app runs in a single Docker container (docker build / docker run -p 8080:8080 yt-dl) with no external dependencies beyond Docker.
  • No login, no database, no persistent storage required — stateless request/response.

Acceptance Criteria

  • docker build completes without errors.
  • docker run -p 8080:8080 yt-dl starts the server.
  • Pasting a valid YouTube URL and clicking Download MP3 delivers an .mp3 file to the browser.
  • Pasting a valid YouTube URL and clicking Download Video delivers a best-quality video file (.mp4 / .mkv) to the browser.
  • Invalid or empty URLs show a clear error message in the UI (no server crash).

Out of Scope

  • Playlist batch downloads (only the single video referenced by the pasted link is ever downloaded — see Priority 2).
  • User accounts / history.

Priority 2

Objective: single-video-only downloads from playlist links, plus live download progress in the UI.

  • If the pasted URL is a playlist link or a video link that also carries a playlist context (e.g. watch?v=...&list=...), only the one referenced video is downloaded — the rest of the playlist is ignored. Implemented via yt-dlp's noplaylist option.
  • Below the two download buttons, a progress bar area appears once a download starts, showing for the video currently being processed:
    • percentage complete
    • current download speed
    • downloaded size / total size
  • Progress transport: the server runs each download as a background job identified by a job_id and records live progress (from yt-dlp's progress_hooks) in server-side memory. The browser polls a GET /progress/{job_id} endpoint (~every 500ms) and updates the bar/text from the response.
  • File delivery: when a job reaches 100%, the page automatically fetches GET /download/{job_id}/file and triggers the browser's normal save behavior — no extra click required, preserving today's one-click UX.
  • Errors surfaced during the background job (invalid URL, yt-dlp failure) are reported through the same progress endpoint/UI status area used today, without a server crash.
  • Still single-container, no external dependencies beyond Docker, no persistent storage — job state is in-memory only and is cleaned up once the file has been delivered (or after a reasonable failure/timeout window).
  • The delivered filename is derived from the video's real title, not a random/opaque id (currently the browser sometimes ends up saving with a UUID-like name):
    • Take the video title as reported by yt-dlp.
    • Strip characters that aren't plain text — emoji, pictograms/icons, and other symbols outside normal letters/digits/punctuation — rather than replacing the whole title.
    • Keep letters (any language), digits, spaces, and common punctuation (- _ . ( ) , '); collapse repeated whitespace left behind by stripped characters and trim the ends.
    • If stripping leaves an empty name (e.g. an emoji-only title), fall back to the video id so a filename always exists.
    • Keep the correct extension for the mode (.mp3 for audio, .mp4/.mkv for video) after the cleaned title.

Acceptance Criteria (Priority 2)

  • Pasting a playlist URL (or a watch URL with a list= param) and clicking MP3 or Video downloads only that one video, never the rest of the playlist.
  • Starting a download shows a progress bar below the buttons that updates with percentage, speed, and downloaded/total size while the server is processing.
  • When the download finishes, the file is automatically delivered to the browser (save dialog) without an extra click.
  • The saved file's name matches the video's title (with emoji/icons/symbols removed, normal characters kept) instead of a UUID or other opaque id, and still ends in the correct extension.
  • If the download fails server-side, the UI shows a clear error in place of the progress bar (no server crash, no stuck spinner).

Out of Scope

  • Playlist batch downloads (downloading every video in a playlist in one action).
  • User accounts / history.
  • Persisting job/progress state across server restarts.

Priority 3

Objective: fix downloads failing due to yt-dlp's missing JavaScript runtime.

  • Bug: some videos fail with ERROR: [youtube] <id>: This video is not available, preceded by WARNING: [youtube] No supported JavaScript runtime could be found. Only deno is enabled by default.... yt-dlp falls back to an alternate player client (e.g. visionos) when it can't run JS-based signature/PO-token deciphering, and that fallback path incorrectly reports some videos as unavailable.
  • Root cause: the Docker image installs no JS runtime, so yt-dlp can't perform the JS-dependent extraction steps modern YouTube extraction increasingly requires.
  • Fix: install Deno (yt-dlp's documented lightweight JS runtime) in the Dockerfile, configure yt-dlp (via ydl_opts and/or a --js-runtimes equivalent) to use it, and allow retrieval of the official GitHub-hosted EJS challenge-solver scripts required by current yt-dlp releases, so normal extraction succeeds without falling back to a degraded client.
  • Confirm the WARNING: No supported JavaScript runtime could be found message no longer appears in server logs during a download.

Acceptance Criteria (Priority 3)

  • docker build still completes without errors after adding Deno.
  • Re-running a download of a video URL that previously failed with This video is not available (due to the missing JS runtime) succeeds and delivers the file.
  • Server logs no longer show No supported JavaScript runtime could be found during a normal MP3 or Video download.
  • Existing Priority 1/2 behavior (single-video-from-playlist, progress bar, filename sanitization) is unaffected.

Out of Scope

  • Fixing videos that are genuinely unavailable (age-restricted with no workaround, region-blocked, deleted, private) — this priority only addresses the missing-JS-runtime-induced false negative.
  • General yt-dlp version upgrade policy beyond what's needed to pair with the new JS runtime.

Priority 4

Objective: fix downloads failing due to YouTube's PO Token requirement and IP-based rate limiting on the web player client.

  • Bug: after Priority 3's fix, some downloads now fail differently: WARNING: [youtube] LV-...: Unable to download webpage: HTTP Error 429: Too Many Requests, followed by WARNING: [youtube] Unable to fetch GVS PO Token for web client: Missing required Visitor Data, ending in ERROR: [youtube] ...: This video is not available. The web client increasingly requires a Proof-of-Origin (PO) Token yt-dlp cannot generate on its own, and is also more exposed to YouTube's IP-based rate limiting.
  • Fix: configure yt-dlp's extractor_args (player_client) to prefer player clients that historically don't require a PO Token as strictly (e.g. android, ios, tv), falling back to web only if those fail, instead of relying on the web client first.
  • This is a best-effort mitigation, not a permanent fix: YouTube's anti-bot requirements change over time, so the chosen client list may need retuning later if YouTube tightens restrictions on the alternate clients too.

Acceptance Criteria (Priority 4)

  • Re-running a download of a video URL that previously failed with Missing required Visitor Data / This video is not available under the web client now succeeds using an alternate player client.
  • Server logs show the alternate player client(s) being used and no longer show Unable to fetch GVS PO Token for web client blocking the download.
  • Existing Priority 1/2/3 behavior (single-video-from-playlist, progress bar, filename sanitization, Deno JS runtime) is unaffected.
  • If all configured player clients fail for a given video, the existing error-handling path still surfaces a clear error in the UI with no server crash (no new failure mode introduced).

Out of Scope

  • Running a separate PO-Token-provider service/container.
  • Retry/backoff handling for transient 429s (may be revisited later if switching clients doesn't sufficiently resolve rate limiting).
  • Guaranteeing every video downloads successfully — YouTube's anti-bot measures are outside this app's control.

Priority 5

Objective: add a capped-720p video download option alongside the existing best-quality video download.

  • Add a third button, Download Video 720p, next to the existing Download MP3 and Download Video buttons.
  • Download Video keeps today's behavior unchanged: best available video+audio quality, no cap.
  • Download Video 720p downloads the best available video+audio quality capped at 720p — i.e. yt-dlp format selection equivalent to bestvideo[height<=720]+bestaudio/best[height<=720], merged the same way as today's video mode (mp4 container via merge_output_format).
  • If the video's best available quality is below 720p (e.g. only 480p exists), the 720p button downloads the best quality actually available — no error, no upscaling.
  • All existing behavior applies unchanged to the new mode: single-video-from-playlist (noplaylist), background job + live progress bar, sanitized title-based filename with the correct extension, and error handling on failure.

Acceptance Criteria (Priority 5)

  • The UI shows three buttons: Download MP3, Download Video, Download Video 720p.
  • Clicking Download Video 720p on a video that has streams above 720p delivers a file whose video stream is at most 720p (not the unrestricted best quality).
  • Clicking Download Video 720p on a video whose best quality is below 720p still succeeds and delivers that lower-quality file (no error).
  • The 720p mode shows the same live progress bar (percent/speed/downloaded-total size) and the same auto-delivery-on-completion behavior as the existing Video and MP3 modes.
  • The delivered filename follows the same sanitized-title convention as the existing modes, with the correct video extension.
  • Existing Download MP3 and Download Video behavior is unchanged.

Out of Scope

  • Additional quality tiers beyond 720p (e.g. 480p, 1080p buttons) — only the existing best-quality option and the new 720p-capped option are in scope.
  • A quality-selection dropdown or other UI beyond a third fixed button.