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

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

  1. [minor] app.py:147 — On any failure inside download() (including yt-dlp errors), str(exc) is returned verbatim to the browser as the message field. 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 against app.py / requirements.txt.
  • python -m py_compile app.py — succeeded.
  • Installed fastapi, uvicorn, httpx, python-multipart, yt-dlp into a scratch venv and exercised the app with starlette.testclient.TestClient, mocking yt_dlp.YoutubeDL to avoid live network calls:
    • GET / → 200, body contains "Download MP3" and "Download Video".
    • POST /download with a garbage (non-http) URL → 400 JSON {"message": ...}.
    • POST /download with an empty URL → 400 JSON.
    • POST /download missing the mode field (triggers RequestValidationError handler) → 400 JSON with .message.
    • POST /download with a valid URL, mode mp3, mocked yt-dlp writing a fake file → 200, correct bytes streamed, Content-Disposition: attachment; filename="...", temp dir cleaned up via BackgroundTask.
    • POST /download where mocked yt-dlp raises RuntimeError → 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 /download with 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

  1. [nit] Dockerfile — the container runs as root (no USER directive). Not required for a local/self-hosted v1 tool per ROADMAP.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 against Dockerfile / 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 /download with 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 logs showed 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 with file), Content-Disposition filename correctly derived from video title.
    • mode=video → 200, content-type: video/mp4, valid ISO-Media MP4 file returned (verified with file).
    • 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-review test 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-dl starts 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 against app.py / README.md; implementation matches the plan (noplaylist: True on both modes, sanitize_filename helper with the specified regex, extract_info(..., download=True) used to recover title/id, filename=display_name passed to FileResponse, frontend Content-Disposition-based link.download fix).
  • python3 -m py_compile app.py — succeeded.
  • Unit-checked sanitize_filename directly (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 FileResponse encodes such filenames via filename*=utf-8''... (RFC 5987) when they contain non-ASCII/space/apostrophe characters, and confirmed the frontend regex (/filename\*?=(?:UTF-8''|")?([^";]+)/i) plus decodeURIComponent correctly 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 /download with a playlist-context URL (watch?v=dQw4w9WgXcQ&list=RDdQw4w9WgXcQ), mode=mp3 → 200; container logs show Downloading 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 (file confirms ID3 v2.4 MPEG audio) with Content-Disposition: attachment; filename*=utf-8''Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster).mp3 — title-based, not a UUID.
    • mode=video against a plain (non-playlist) URL → 200, valid MP4 (file confirms ISO Media MP4), filename Me at the zoo.mp4 derived from the video title.
    • POST /download with 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.md changes; 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-playlist log 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.py passes.

Risks:

  • None outstanding. sanitize_filename strips characters outside \w/whitespace/- _ . ( ) , '; this only affects the Content-Disposition header 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.