# 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.