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.
8.4 KiB
8.4 KiB
REVIEW
Append-only review log. Each task section is appended or updated in place; prior task history is preserved.
T-001 — FastAPI backend (app.py) + inline HTML/JS frontend (requirements.txt)
Verdict: PASS_WITH_NOTES
Findings
- [minor]
app.py:147— On any failure insidedownload()(including yt-dlp errors),str(exc)is returned verbatim to the browser as themessagefield. yt-dlp exceptions can include local filesystem paths (the temp dir) or verbose internal detail. Not a required fix for this local/self-hosted tool, but consider mapping to a generic message (e.g. "The video could not be downloaded.") and logging the raw exception server-side instead, before this is exposed beyond local/dev use.- Required fix: No.
No blocker or major findings.
Verification
Steps performed:
- Re-read
.ai/PLAN.md(Phase 1 spec for T-001) and diffed it againstapp.py/requirements.txt. python -m py_compile app.py— succeeded.- Installed
fastapi,uvicorn,httpx,python-multipart,yt-dlpinto a scratch venv and exercised the app withstarlette.testclient.TestClient, mockingyt_dlp.YoutubeDLto avoid live network calls:GET /→ 200, body contains "Download MP3" and "Download Video".POST /downloadwith a garbage (non-http) URL → 400 JSON{"message": ...}.POST /downloadwith an empty URL → 400 JSON.POST /downloadmissing themodefield (triggersRequestValidationErrorhandler) → 400 JSON with.message.POST /downloadwith a valid URL, modemp3, mocked yt-dlp writing a fake file → 200, correct bytes streamed,Content-Disposition: attachment; filename="...", temp dir cleaned up viaBackgroundTask.POST /downloadwhere mocked yt-dlp raisesRuntimeError→ 500 JSON{"message": "..."}(no server crash), temp dir cleaned up on the error path.- Filename containing non-ASCII characters (e.g. "café mix 🎵.mp3") → correctly RFC 5987-encoded as
filename*=utf-8''...by Starlette; no crash or mojibake.
Findings from verification: All acceptance criteria for T-001 hold:
GET /returns 200 with HTML containing "Download MP3".POST /downloadwith a valid URL + mode streams a file.- Invalid URL returns 400 JSON with
.message.
Risks:
- No live network test against real YouTube was performed (would require external network access and a real video URL); mocked yt-dlp calls confirm the FastAPI plumbing (routing, form validation, error handling, file streaming, temp-dir cleanup) is correct, but do not confirm yt-dlp/ffmpeg behavior against a live video. This risk carries into the Docker-level validation planned for T-002 (
docker build+ smoke test), where a live download should be attempted at least once. - Large/long-running downloads have no timeout or size cap — acceptable for v1 per
ROADMAP.md(out of scope), but worth revisiting if this moves beyond local/dev use.
T-002 — Dockerfile + README.md (build/run docs)
Verdict: PASS
Findings
- [nit]
Dockerfile— the container runs as root (noUSERdirective). Not required for a local/self-hosted v1 tool perROADMAP.md, but worth adding a non-root user if this is ever exposed beyond a trusted local network.- Required fix: No.
No blocker, major, or minor findings.
Verification
Steps performed:
- Re-read
.ai/PLAN.md(Phase 2 spec for T-002) and diffed it againstDockerfile/README.md; matches the planned Dockerfile contents and documented build/run/usage/size-note requirements. docker build -t yt-dl-review .— exited 0.docker run --rm -d -p 8091:8080 yt-dl-review— container started;curl http://localhost:8091/returned HTTP 200 with both "Download MP3" and "Download Video" present in the body.POST /downloadwith an invalid URL against the running container → 400 JSON{"message": "..."}", confirming the containerized app behaves the same as the local dev checks from T-001;docker logsshowed clean request handling with no crash/traceback.- Live end-to-end test (real network, real YouTube video
https://www.youtube.com/watch?v=jNQXAC9IVRw, "Me at the zoo"):mode=mp3→ 200,content-type: audio/mpeg, valid ID3-tagged MP3 file returned (verified withfile),Content-Dispositionfilename correctly derived from video title.mode=video→ 200,content-type: video/mp4, valid ISO-Media MP4 file returned (verified withfile).- This closes the live-download risk flagged in the T-001 review — yt-dlp + ffmpeg inside the container work correctly end-to-end for both modes.
- Cleaned up: stopped test container, removed the
yt-dl-reviewtest image, removed downloaded test files.
Findings from verification: All acceptance criteria for T-002 hold:
docker build -t yt-dl .exits 0.docker run --rm -p 8080:8080 yt-dlstarts the server.curl http://localhost:8080/returns HTML with both download buttons.- (Bonus, beyond stated AC) A real MP3 and a real video download both succeed end-to-end through the container.
Risks:
- None outstanding. The live-download risk noted in the T-001 review has been verified and closed here.
T-003 — noplaylist option, sanitize_filename helper, title-based filenames, frontend Content-Disposition fix
Verdict: PASS
Findings
No blocker, major, minor, or nit findings.
Verification
Steps performed:
- Re-read
.ai/PLAN.md(Phase 1 spec for T-003) and diffed it againstapp.py/README.md; implementation matches the plan (noplaylist: Trueon both modes,sanitize_filenamehelper with the specified regex,extract_info(..., download=True)used to recovertitle/id,filename=display_namepassed toFileResponse, frontendContent-Disposition-basedlink.downloadfix). python3 -m py_compile app.py— succeeded.- Unit-checked
sanitize_filenamedirectly (scratch venv, no mocks):sanitize_filename("Song 🔥 Title ✨ (Live)", "abc123")→"Song Title (Live)".sanitize_filename("🔥🔥🔥", "abc123")→"abc123"(fallback to id).sanitize_filename("日本語 タイトル", "abc123")→"日本語 タイトル"(non-Latin preserved).- Verified Starlette's
FileResponseencodes such filenames viafilename*=utf-8''...(RFC 5987) when they contain non-ASCII/space/apostrophe characters, and confirmed the frontend regex (/filename\*?=(?:UTF-8''|")?([^";]+)/i) plusdecodeURIComponentcorrectly recovers the original title from that header format.
- Live end-to-end test, built and ran the actual Docker image (
docker build -t yt-dl-review .,docker run -p 8081:8080):GET /→ 200, contains "Download MP3".POST /downloadwith a playlist-context URL (watch?v=dQw4w9WgXcQ&list=RDdQw4w9WgXcQ),mode=mp3→ 200; container logs showDownloading just the video dQw4w9WgXcQ because of --no-playlist, confirming only the referenced video is fetched, never the rest of the playlist/mix; response is a single valid MP3 (fileconfirms ID3 v2.4 MPEG audio) withContent-Disposition: attachment; filename*=utf-8''Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster).mp3— title-based, not a UUID.mode=videoagainst a plain (non-playlist) URL → 200, valid MP4 (fileconfirms ISO Media MP4), filenameMe at the zoo.mp4derived from the video title.POST /downloadwith an invalid (non-http) URL → unchanged 400 JSON{"message": ...}behavior, confirming the existing validation path was not regressed.- Cleaned up: stopped/removed the test container and scratch venv, deleted downloaded test files.
- Reviewed
README.mdchanges; the new paragraph accurately describes the playlist-single-video behavior and the title-based/emoji-stripped filename rule, matching observed behavior.
Findings from verification: All acceptance criteria for T-003 hold:
- Pasting a playlist/
list=URL downloads only the referenced video (confirmed via yt-dlp's own--no-playlistlog line and a single file being produced). - Saved filename matches the video title with emoji/icons stripped (normal chars, including non-Latin, kept) instead of a UUID, with the correct extension.
python -m py_compile app.pypasses.
Risks:
- None outstanding.
sanitize_filenamestrips characters outside\w/whitespace/- _ . ( ) , '; this only affects theContent-Dispositionheader value used for the browser's save-as name, not the on-disk path used to read the file, so there is no path-traversal or injection concern from unsanitized titles.