Skip to content

Add download APIs, metadata, lyrics & ID3 embedding#246

Merged
DestroyCom merged 4 commits into
masterfrom
feat/audio-library-ready
May 3, 2026
Merged

Add download APIs, metadata, lyrics & ID3 embedding#246
DestroyCom merged 4 commits into
masterfrom
feat/audio-library-ready

Conversation

@DestroyCom
Copy link
Copy Markdown
Owner

@DestroyCom DestroyCom commented May 3, 2026

Add new server endpoints for downloading/streaming media: /api/download/video, /api/download/audio and /api/download/audio-library-ready. Implement ffmpeg + yt-dlp pipelines (audio/video download, merging, MP3 conversion) and ID3 embedding with cover art. Add metadata providers (MusicBrainz, iTunes), lyrics extraction (lrclib, VTT, subtitles) and utilities for handling yt-dlp binary, route helpers, server config and song matching; include an API guard to restrict requests to same-origin. Remove the legacy video-converter route and update the VideoSelect component to use the new endpoints and UI tweaks. Also fix a small .gitignore entry and update package.json.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added "audio-library-ready" download option providing enhanced MP3 files with enriched metadata, cover art, and synchronized lyrics.
    • Improved metadata resolution from multiple sources for better song information during downloads.
  • Chores

    • Refactored API structure to better organize download endpoints and added dependencies to support enhanced file metadata handling.

Add new server endpoints for downloading/streaming media: /api/download/video, /api/download/audio and /api/download/audio-library-ready. Implement ffmpeg + yt-dlp pipelines (audio/video download, merging, MP3 conversion) and ID3 embedding with cover art. Add metadata providers (MusicBrainz, iTunes), lyrics extraction (lrclib, VTT, subtitles) and utilities for handling yt-dlp binary, route helpers, server config and song matching; include an API guard to restrict requests to same-origin. Remove the legacy video-converter route and update the VideoSelect component to use the new endpoints and UI tweaks. Also fix a small .gitignore entry and update package.json.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2026

Warning

Rate limit exceeded

@DestroyCom has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 33 minutes and 9 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 917862dd-a464-45c6-b2af-af3ac6195abe

📥 Commits

Reviewing files that changed from the base of the PR and between 34d4f70 and cac1fe4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (24)
  • CLAUDE.md
  • README.md
  • app/api/download/audio-library-ready/route.ts
  • app/api/download/audio/route.ts
  • app/api/download/video/route.ts
  • app/layout.tsx
  • components/custom/VideoSelect.tsx
  • lib/api-guard.ts
  • lib/audio-convert.ts
  • lib/embed-id3.ts
  • lib/lyrics/index.ts
  • lib/lyrics/lrc-to-sylt.ts
  • lib/lyrics/lrclib.ts
  • lib/lyrics/subtitles.ts
  • lib/metadata/providers/itunes.ts
  • lib/metadata/providers/musicbrainz.ts
  • lib/route-utils.ts
  • lib/server-conf.ts
  • lib/serverUtils.ts
  • lib/song-matching.ts
  • lib/video-download.ts
  • lib/ytdlp-binary.ts
  • lib/ytdlp-info.ts
  • package.json
📝 Walkthrough

Walkthrough

This PR refactors the media download system from a single unified endpoint (/api/video-converter) to three specialized routes (/api/download/audio, /api/download/audio-library-ready, /api/download/video) with enhanced metadata enrichment, lyrics fetching, and ID3 tagging capabilities. Supporting infrastructure for server configuration, binary resolution, request validation, and lyrics/metadata providers is introduced.

Changes

Media Download System Refactoring

Layer / File(s) Summary
Foundation & Request Guard
lib/api-guard.ts, lib/server-conf.ts, lib/route-utils.ts, lib/ytdlp-binary.ts, package.json
New request validation guard checks origin via sec-fetch-site/referer. Server config initialization caches ffmpegPath on first call. Route utilities provide temp directory paths, file cleanup, and content-disposition header builders. Binary resolver locates yt-dlp across known candidate paths. node-id3 dependency added.
Download & Stream Pipelines
lib/audio-convert.ts, lib/video-download.ts, lib/ytdlp-info.ts
Audio-convert streams yt-dlpffmpeg for MP3 with optional thumbnail cover metadata. Video-download spawns concurrent yt-dlp processes for audio and video streams with timeout enforcement. Video format selection now sorts by resolution descending before deduplication.
Metadata Enrichment
lib/metadata/types.ts, lib/metadata/index.ts, lib/metadata/providers/musicbrainz.ts, lib/metadata/providers/itunes.ts, lib/song-matching.ts
Metadata provider abstraction with MusicBrainz and iTunes implementations. Song matching derives canonical artist/title from yt-dlp data with "Artist - Title" parsing and YouTube suffix stripping as fallback. Metadata fetcher iterates providers sequentially.
Lyrics Retrieval
lib/lyrics/types.ts, lib/lyrics/index.ts, lib/lyrics/lrc-to-sylt.ts, lib/lyrics/lrclib.ts, lib/lyrics/subtitles.ts, lib/lyrics/vtt-to-sylt.ts
Core SyltEntry type defines lyric entry with millisecond timestamp. Lyrics orchestrator prioritizes manual YouTube subtitles → LRClib (with duration validation) → auto captions. Format converters parse LRC and VTT into SYLT. LRClib fetcher queries external API and selects best result by sync status.
ID3 Metadata Embedding
lib/embed-id3.ts
Embeds ID3v2 tags into MP3 via node-id3, including optional cover art download, synchronized lyrics (SYLT), and plain lyrics (USLT).
Audio Download Routes
app/api/download/audio/route.ts, app/api/download/audio-library-ready/route.ts
Both routes fetch Innertube metadata, download audio, and stream MP3. The basic route includes optional thumbnail cover. The library-ready route additionally fetches song metadata and lyrics, embeds full ID3 tags (including SYLT/USLT), and provides richer enrichment.
Video Download Route
app/api/download/video/route.ts
Validates video quality, fetches formats, downloads audio/video concurrently, merges via ffmpeg with stream copy, caches merged output in database, deduplicates in-flight requests by video:quality key, and streams MP4 response.
Removed Endpoint
app/api/video-converter/route.ts
Unified route handler removed (functionality distributed across three specialized routes).
Frontend Routing
components/custom/VideoSelect.tsx
Quality dropdown adds "audio-library-ready" option. Download handler branches fetch URL by quality: /api/download/audio, /api/download/audio-library-ready, or /api/download/video. Progress component visibility refactored.
Configuration
.gitignore
.gitignore pattern for video cache updated from unanchored video to root-anchored /video.

Sequence Diagram

sequenceDiagram
    participant Client
    participant AudioRoute as /api/download/<br/>audio-library-ready
    participant Innertube
    participant YtDlp
    participant Metadata as Metadata<br/>Provider
    participant Lyrics as Lyrics<br/>System
    participant FFmpeg
    participant ID3 as ID3<br/>Embedder
    participant Client2 as Response<br/>Stream

    Client->>AudioRoute: GET ?url=<YouTube URL>
    AudioRoute->>Innertube: getBasicInfo(videoId)
    AudioRoute->>YtDlp: getYtDlpFullInfo(url)
    Innertube-->>AudioRoute: title, artist, thumbnail
    YtDlp-->>AudioRoute: track, artist, duration, subtitles
    
    par Metadata & Lyrics
        AudioRoute->>Metadata: fetchSongMetadata(artist, title)
        Metadata-->>AudioRoute: enriched metadata
        AudioRoute->>Lyrics: fetchLyrics(query)
        Lyrics-->>AudioRoute: sylt[], plain text
    and Audio Download
        AudioRoute->>YtDlp: stream download
        YtDlp->>FFmpeg: audio stream
        FFmpeg->>FFmpeg: encode to MP3
    end
    
    AudioRoute->>ID3: embedId3Tags(mp3Path, {<br/>metadata, sylt, plainLyrics })
    ID3->>ID3: write tags + lyrics
    ID3-->>AudioRoute: tagged MP3 file
    AudioRoute->>Client2: stream MP3 response
    Client2-->>Client: download complete
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly Related PRs

  • Update and fix StroyGetter for 2026 #245: Continuation of the same YouTube/yt-dlp download stack migration; both PRs refactor the video-converter endpoint and introduce/enhance ytdlp-info, innertube, and binary resolution patterns.

Poem

🐰 Three routes split where one stood tall,
With lyrics, tags, and metadata's call,
YouTube → ffmpeg → MP3 with flair,
Library-ready, audio everywhere!
hops away with streaming delight

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and concisely summarizes the main changes: adding download APIs for audio/video, metadata fetching, lyrics extraction, and ID3 tag embedding.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/audio-library-ready

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 33 minutes and 9 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@socket-security
Copy link
Copy Markdown

socket-security Bot commented May 3, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addediso-639-3@​3.0.110010010080100
Addednode-id3@​0.2.99810010080100

View full report

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.gitignore:
- Line 42: The .gitignore change anchors "/video" which no longer ignores nested
directories like app/api/download/video/ and creates an inconsistency with the
unanchored "videos" entry; revert to an unanchored pattern (use "video" instead
of "/video") or explicitly add both patterns you intend (e.g., "video" to ignore
any nested folders and "/video" only if you truly want root-only) and ensure
app/api/download/video/ is explicitly listed or negated if it must remain
ignored/tracked; update the entries "video", "/video", and "videos" accordingly
so their intent (global vs root-only) is consistent.

In `@app/api/download/audio-library-ready/route.ts`:
- Line 1: Remove the top-level "use server" directive from the route handler
file; open the route.ts that defines the API route (the exported GET/handler
function) and delete the `"use server"` line so the file is a standard Next.js
Route Handler without the unnecessary directive.
- Around line 47-64: The current code uses an unnecessary async IIFE and nested
Promise.all to fetch metadata/lyrics and convert to MP3 in parallel; simplify by
removing the IIFE and directly run Promise.all with two promises: one
Promise.all for fetchSongMetadata and fetchLyrics (using fetchSongMetadata({
artist: match.artist, title: match.title }) and fetchLyrics({...})) and the
other downloadAndConvertToMp3(url, mp3Path, ffmpegPath), then destructure the
results into { meta, lyrics } and the MP3 result; update the variables
accordingly so fetches and conversion remain parallel but with a flatter
Promise.all structure.

In `@app/api/download/audio/route.ts`:
- Around line 53-77: The MP3 file may be left behind if
downloadAudioWithFfmpegTags throws because mp3Path is only cleaned when the
stream is created; wrap the download step in a try/catch/finally that (1)
ensures cleanFiles([mp3Path]) is called on error before rethrowing, (2) keeps
the existing thumb cleanup in the finally for hasThumb/thumbPath, and (3) only
creates fs.createReadStream(mp3Path) and attaches stream.on("close", ...) after
confirming the file exists; update the function handling in route.ts around
downloadAudioWithFfmpegTags, mp3Path, cleanFiles, stream, NextResponse and
buildContentDisposition accordingly so partial files are always removed on
failure.
- Line 1: Remove the redundant "use server" directive at the top of the audio
route file: delete the top-line string literal so the exported route handler
(the GET/exported handler function in route.ts) remains a normal server Route
Handler without the unnecessary directive; do not change the handler
implementation or exports, just remove the `"use server"` line.

In `@app/api/download/video/route.ts`:
- Line 1: Remove the `"use server"` directive from the Route Handler file — the
string literal "use server" at the top of the route.ts should be deleted because
route handlers are server-only by default; simply remove that directive and keep
the existing exported handler logic unchanged.

In `@components/custom/VideoSelect.tsx`:
- Around line 209-218: The API URL is built by concatenating videoUrl directly
into apiUrl (see the selectedQuality branches, apiUrl variable, and the
fetch(apiUrl, ...) call), which can break if videoUrl contains special
characters; update each branch to encode the URL parameter (use
encodeURIComponent(videoUrl)) when building the query string (and ensure the url
param in the `/api/download/video?url=...&quality=...` branch is encoded as
well) so the fetch receives a safe, properly escaped URL.

In `@lib/api-guard.ts`:
- Around line 1-23: The JSDoc for guardApiRequest incorrectly implies
Sec-Fetch-Site/Referer provide real protection; update the comment above export
function guardApiRequest(request: Request) to remove the claim that curl/direct
navigation cannot set Sec-Fetch-Site and instead state these headers are
trivially spoofable, not suitable for access control, and only act as a
best-effort client fingerprint (mention the specific headers "sec-fetch-site"
and "referer"). Also add a short guidance line in the same comment recommending
one of the real protections (use your auth layer via Authorization/cookie
validation or issue short-lived signed tokens/HMAC tied to the user session) so
callers know to implement proper access control for the download/ffmpeg routes;
do not change the function logic in this patch.

In `@lib/embed-id3.ts`:
- Around line 28-49: Add an AbortController-based timeout to the cover art fetch
in lib/embed-id3.ts: create an AbortController, start a timer (e.g. 5s) that
calls controller.abort(), pass controller.signal to fetch(metadata.coverUrl),
and clear the timer on success/failure; update the catch block to handle aborts
(log a timeout-specific message) and ensure tags.image is only set when fetch
completes before the timeout; reference the metadata.coverUrl fetch and the
tags.image / NodeID3.TagConstants.AttachedPicture.PictureType.FRONT_COVER logic
when making the change.

In `@lib/lyrics/lrclib.ts`:
- Around line 15-17: The fetch call that assigns const res in lrclib.ts (the
search request using `fetch(\`https://lrclib.net/api/search?${params}\`, ...)`)
must be given an AbortSignal with a sensible timeout: create a signal via
AbortSignal.timeout(<ms>) (e.g. 5000–10000 ms), pass it as the `signal` option
to fetch, and ensure any thrown AbortError is handled or allowed to propagate
per existing error handling; apply the same pattern to the other fetch calls
noted (lib/lyrics/subtitles.ts, lib/metadata/providers/itunes.ts,
lib/metadata/providers/musicbrainz.ts) so each fetch receives a timeout signal
rather than waiting on the OS connection timeout.

In `@lib/metadata/providers/musicbrainz.ts`:
- Around line 38-42: The 1-second await before setting metadata.coverUrl is
unnecessary because no HTTP request is made here; remove the unconditional delay
in the block that checks releaseId (where metadata.coverUrl =
`${CAA_BASE}/release/${releaseId}/front`) so you don't add latency to
musicbrainzProvider.search calls—if you later intend to perform a live Cover Art
Archive fetch, perform the fetch there and keep the 1s delay immediately before
that network call instead of where the URL is merely constructed.

In `@lib/route-utils.ts`:
- Around line 15-18: The current pipeline calls encodeURIComponent before
removing combining marks, so normalize("NFKD") output gets percent-encoded
(e.g., "%CC%81") and the regex /[̀-ͯ]/g never matches; update the logic around
the safe variable so you call .normalize("NFKD") then strip combining marks
using a Unicode-escaped range (e.g., /\u0300-\u036F/g) before calling
encodeURIComponent((title || ext)), and keep the rest of the replacement chain
intact so safe becomes an encoded, mark-free filename.

In `@lib/server-conf.ts`:
- Around line 6-12: getServerConf caches a rejected initialization promise
(_initPromise) when initializeConf(CONF) fails, preventing retries; change how
_initPromise is created so failures clear the cached promise: assign
_initPromise = initializeConf(CONF).then(() => { /* success */ }).catch(err => {
_initPromise = undefined; throw err; }); this ensures CONF.isInitialized remains
false but subsequent calls will retry initialization; reference symbols:
getServerConf, _initPromise, initializeConf, CONF.isInitialized.

In `@lib/video-download.ts`:
- Around line 60-72: The timeout handler currently kills audioProc/videoProc but
never rejects, and exit codes from the child processes are never checked,
allowing silent success and propagation of truncated files; in
downloadStreamsToFiles, change the logic so the timeout callback records a
timeout error and causes the awaiting Promise to reject (instead of just killing
processes), and after Promise.all(waitForClose(...)) resolve, inspect
audioProc.exitCode and videoProc.exitCode (and/or their exit events) and throw a
descriptive error if either is non-zero; reference the existing symbols
audioProc, videoProc, DOWNLOAD_TIMEOUT_MS, waitForClose and
downloadStreamsToFiles to implement: set a local rejected flag or reject
function when timeout fires, kill procs, and ensure Promise.all rejects on
timeout or non-zero exit codes so callers (e.g., mergeAudioVideo) never receive
partial files.
- Around line 40-55: The spawned yt-dlp child processes (audioProc and
videoProc) leave stderr as a pipe which can fill and block the process; after
the existing stdout null-check (the block that throws "Failed to get process
stdout"), call .resume() on both audioProc.stderr and videoProc.stderr to
drain/discard their stderr streams so yt-dlp cannot block on stderr writes;
locate the stderr handling immediately after the stdout null-check in the code
where audioProc and videoProc are created and ensure you safely guard against
null before calling .resume().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c57aaaad-b891-4fc0-9f67-4aaf996be286

📥 Commits

Reviewing files that changed from the base of the PR and between e882c17 and 34d4f70.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (26)
  • .gitignore
  • app/api/download/audio-library-ready/route.ts
  • app/api/download/audio/route.ts
  • app/api/download/video/route.ts
  • app/api/video-converter/route.ts
  • components/custom/VideoSelect.tsx
  • lib/api-guard.ts
  • lib/audio-convert.ts
  • lib/embed-id3.ts
  • lib/lyrics/index.ts
  • lib/lyrics/lrc-to-sylt.ts
  • lib/lyrics/lrclib.ts
  • lib/lyrics/subtitles.ts
  • lib/lyrics/types.ts
  • lib/lyrics/vtt-to-sylt.ts
  • lib/metadata/index.ts
  • lib/metadata/providers/itunes.ts
  • lib/metadata/providers/musicbrainz.ts
  • lib/metadata/types.ts
  • lib/route-utils.ts
  • lib/server-conf.ts
  • lib/song-matching.ts
  • lib/video-download.ts
  • lib/ytdlp-binary.ts
  • lib/ytdlp-info.ts
  • package.json
💤 Files with no reviewable changes (1)
  • app/api/video-converter/route.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use import { prisma } from "@/lib/prisma" for Prisma client access — never use new PrismaClient(). Prisma 7 requires a PrismaLibSql driver adapter; bare instantiation throws at runtime.
Never call prisma.$disconnect() on the shared singleton instance, as it breaks subsequent requests and cron runs.

Files:

  • lib/api-guard.ts
  • lib/ytdlp-binary.ts
  • lib/metadata/index.ts
  • lib/metadata/providers/musicbrainz.ts
  • lib/lyrics/vtt-to-sylt.ts
  • lib/lyrics/lrc-to-sylt.ts
  • lib/lyrics/types.ts
  • lib/metadata/providers/itunes.ts
  • lib/ytdlp-info.ts
  • lib/route-utils.ts
  • lib/embed-id3.ts
  • app/api/download/video/route.ts
  • lib/lyrics/lrclib.ts
  • lib/video-download.ts
  • lib/lyrics/subtitles.ts
  • lib/song-matching.ts
  • lib/audio-convert.ts
  • app/api/download/audio-library-ready/route.ts
  • app/api/download/audio/route.ts
  • lib/server-conf.ts
  • lib/metadata/types.ts
  • lib/lyrics/index.ts
  • components/custom/VideoSelect.tsx
lib/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Temp directory structure: Dev uses ./temp/{source,cached}, production uses /temp/stroygetter/{source,cached}. Initialize automatically on first request via initializeConf().

Files:

  • lib/api-guard.ts
  • lib/ytdlp-binary.ts
  • lib/metadata/index.ts
  • lib/metadata/providers/musicbrainz.ts
  • lib/lyrics/vtt-to-sylt.ts
  • lib/lyrics/lrc-to-sylt.ts
  • lib/lyrics/types.ts
  • lib/metadata/providers/itunes.ts
  • lib/ytdlp-info.ts
  • lib/route-utils.ts
  • lib/embed-id3.ts
  • lib/lyrics/lrclib.ts
  • lib/video-download.ts
  • lib/lyrics/subtitles.ts
  • lib/song-matching.ts
  • lib/audio-convert.ts
  • lib/server-conf.ts
  • lib/metadata/types.ts
  • lib/lyrics/index.ts
lib/ytdlp-info.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Format parsing for yt-dlp output should be handled in lib/ytdlp-info.ts.

Files:

  • lib/ytdlp-info.ts
app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use youtube-dl-exec (yt-dlp binary) via selectYtDlpPath() for actual stream downloading; use youtubei.js via lib/innertube.ts for metadata and format list retrieval.

Files:

  • app/api/download/video/route.ts
  • app/api/download/audio-library-ready/route.ts
  • app/api/download/audio/route.ts
app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Store merged MP4s in temp/cached/ and index them in the File table by URL+quality to serve repeat requests from cache without re-downloading.

Files:

  • app/api/download/video/route.ts
  • app/api/download/audio-library-ready/route.ts
  • app/api/download/audio/route.ts
🔇 Additional comments (27)
package.json (1)

34-34: LGTM — node-id3 pinned to the current latest.

node-id3 latest published version is 0.2.9, so the range ^0.2.9 correctly tracks it with no update noise.

lib/lyrics/types.ts (1)

1-4: LGTM.

Clean shared type; timeStamp: number in milliseconds aligns with the node-id3 synchronisedLyrics contract.

lib/ytdlp-info.ts (1)

25-42: LGTM — sort-before-dedup correctly picks the highest-resolution variant per quality label.

The Map in app/api/download/video/route.ts uses first-wins per qualityLabel, so sorting descending by height before deduplication ensures the best-quality stream is selected.

lib/lyrics/lrc-to-sylt.ts (1)

3-17: LGTM — correctly handles standard 2-digit centisecond and 3-digit millisecond LRC fractions.

lib/ytdlp-binary.ts (1)

6-17: LGTM — clean, synchronous binary resolver with a clear error on miss.

The two-candidate search (.next/server/bin/ for post-build copies, node_modules/youtube-dl-exec/bin/ as fallback) covers both the production and dev layouts. The module-level cache correctly re-scans on failure (unlike server-conf.ts's _initPromise).

lib/lyrics/vtt-to-sylt.ts (1)

3-40: LGTM — correctly handles both HH:MM:SS.mmm and MM:SS.mmm VTT timing formats, with clean deduplication for YouTube's word-level VTT.

lib/lyrics/subtitles.ts (1)

1-51: LGTM — language fallback logic (exact → base → regional-variant) is correct, and findVttUrl / vttToSylt are composed cleanly. Fetch timeout is covered in the lrclib.ts comment above.

lib/metadata/types.ts (1)

1-16: LGTM — clean, minimal type contracts; all SongMetadata fields correctly optional for partial-enrichment scenarios.

lib/metadata/providers/itunes.ts (1)

1-39: LGTM — correct use of satisfies SongMetadata for compile-time contract enforcement, safe optional chaining on data.results, and clean graceful-degradation via catch {} → return null. Fetch timeout covered by the lrclib.ts comment.

lib/metadata/index.ts (1)

1-25: LGTM — sequential provider fallback with per-provider isolation and result.title || result.artist validity guard are all correct. The overall latency concern stems from the pointless delay in musicbrainzProvider (addressed in the musicbrainz.ts comment).

lib/embed-id3.ts (2)

1-9: LGTM!

Clean interface definition. The EmbedOptions interface properly mirrors the optional nature of SongMetadata fields and lyrics components.


51-77: LGTM!

The SYLT/USLT embedding logic is correct, with proper constants usage and logging. Error handling for NodeID3.write is appropriate.

lib/lyrics/index.ts (3)

1-10: LGTM!

Clean type definitions and re-exports. The LyricsResult interface appropriately models the synced/plain lyrics distinction.


15-20: Duration validation logic is correct.

The guard on line 16 (!durationSec) correctly handles both undefined and 0 cases, preventing division by zero on line 18.


33-65: LGTM!

The three-stage fallback (manual subs → LRCLib → auto subs) is well-structured with appropriate duration validation where needed. Manual subs skip duration check as noted in the comment, which is correct since artist-uploaded subs are authoritative.

app/api/download/video/route.ts (2)

123-139: LGTM!

The cache resolution logic correctly checks both Prisma records and filesystem existence before attempting download. The cleanup on error is properly handled.


141-161: In-flight deduplication only works for single-instance deployments.

The _inFlight Map prevents duplicate work within a single process, but won't help if the app runs on multiple instances. This is acceptable for typical self-hosted usage but worth noting.

lib/song-matching.ts (3)

26-35: LGTM!

Correctly uses selectYtDlpPath() per coding guidelines. The options mirror those used elsewhere in the codebase.


37-53: Regex may over-strip artist names with parentheses.

The SEPARATOR_RE regex strips all trailing parenthetical content before extracting. Artists or songs with parentheses in their actual names (e.g., "Panic! At The Disco" or "Song Title (Acoustic)") may lose meaningful content.

However, given the three-step fallback design and that this targets YouTube titles (which commonly include "(Official Video)" etc.), the current behavior is reasonable.


55-80: LGTM!

The three-step matching strategy is well-designed: prefer yt-dlp metadata (reliable for official tracks) → parse title → use raw values. The function always returns a valid CanonicalMatch, making it safe for callers.

lib/audio-convert.ts (2)

67-91: LGTM!

The ffmpeg argument construction for optional thumbnail and metadata tags is well-structured. The ID3v2 version 3 usage for cover art is appropriate for broad compatibility.


2-2: No change needed—getYtDlpBinaryPath() is the correct function for this usage.

The distinction is that selectYtDlpPath() returns a YoutubeDl wrapper instance (for higher-level integration), while getYtDlpBinaryPath() returns the path string to the binary. Since lib/audio-convert.ts spawns the process directly via spawn(getYtDlpBinaryPath(), ...), it requires the path string, making getYtDlpBinaryPath() the appropriate choice.

			> Likely an incorrect or invalid review comment.
app/api/download/audio-library-ready/route.ts (2)

66-84: LGTM!

Good fallback logic for metadata and cover URL. The embedId3Tags call correctly passes optional SYLT and plain lyrics.


86-100: LGTM!

Proper stream handling with cleanup on close, and correct error handling with cleanup in the catch block.

components/custom/VideoSelect.tsx (3)

181-191: LGTM!

The new "Audio - Library Ready" option is properly added to the Select component with appropriate IDs and styling consistent with other options.


224-228: LGTM!

The extension logic correctly identifies both audio options as MP3. The blob download mechanism is standard and correct.

Also applies to: 230-236


265-267: LGTM!

Good refactor to only render the Progress component when actually downloading, avoiding unnecessary DOM elements.

Comment thread .gitignore
temp
videos
video
/video
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find any 'video' files or directories that are not at the repository root

# Search for directories named 'video' (excluding root level)
fd -t d '^video$' --max-depth 10 | grep -v '^video$' || echo "No nested 'video' directories found"

# Search for files named 'video' (any extension)
fd -t f '^video' --max-depth 10 || echo "No 'video' files found"

# Also check git status to see if any previously-ignored paths would now appear
git status --ignored | grep -i video || echo "No video-related paths in git status"

Repository: DestroyCom/StroyGetter

Length of output: 251


🏁 Script executed:

cat -n .gitignore | sed -n '35,50p'

Repository: DestroyCom/StroyGetter

Length of output: 288


Anchoring /video to root will expose the nested app/api/download/video/ directory.

The change from video to /video narrows the ignore pattern to the repository root only. The nested directory app/api/download/video/ (currently protected by the unanchored video pattern) will now become trackable and committable.

Additionally, line 41 ignores videos (unanchored), while line 42 now ignores /video (anchored to root). Confirm whether this inconsistency is intentional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.gitignore at line 42, The .gitignore change anchors "/video" which no
longer ignores nested directories like app/api/download/video/ and creates an
inconsistency with the unanchored "videos" entry; revert to an unanchored
pattern (use "video" instead of "/video") or explicitly add both patterns you
intend (e.g., "video" to ignore any nested folders and "/video" only if you
truly want root-only) and ensure app/api/download/video/ is explicitly listed or
negated if it must remain ignored/tracked; update the entries "video", "/video",
and "videos" accordingly so their intent (global vs root-only) is consistent.

Comment thread app/api/download/audio-library-ready/route.ts Outdated
Comment thread app/api/download/audio-library-ready/route.ts
Comment thread app/api/download/audio/route.ts Outdated
Comment thread app/api/download/audio/route.ts Outdated
Comment thread lib/metadata/providers/musicbrainz.ts
Comment thread lib/route-utils.ts Outdated
Comment thread lib/server-conf.ts
Comment thread lib/video-download.ts Outdated
Comment thread lib/video-download.ts Outdated
DestroyCom added 3 commits May 4, 2026 01:08
Propagate lyrics language from the lyrics fetch to the ID3 embedding step and default to English when unknown. Added a helper to convert BCP-47 language tags to ISO 639-2 three-letter codes (using iso-639-3) and include the language in LyricsResult. embedId3Tags now accepts a lyricsLanguage option and uses it for synchronised (SYLT) and unsynchronised (USLT) lyric frames. package.json updated to add the iso-639-3 dependency.
Introduce MAX_FILESIZE env var (default 8G) and pass it as --max-filesize to yt-dlp in audio-convert and video-download so stream downloads are capped. Update CLAUDE.md to document the MAX_FILESIZE setting. Also fix a Tailwind class in VideoSelect.tsx (change '!h-10' to 'h-10!' to enforce the intended height when downloading or on error).
Add better error handling, timeouts and cleanup across download and metadata flows. Key changes:

- Add AbortSignal timeouts to external fetches (itunes, musicbrainz, lrclib, subtitles, embed cover fetch) to avoid hanging requests. Update api-guard comment to clarify best-effort checks.
- Harden audio download route: ensure thumbnail is cleaned, wrap ffmpeg/tagging in try/catch/finally, stream MP3 to response, and return 500 on error. Use guardApiRequest import ordering fix.
- Improve video download flow: normalize spawn arg formatting, sanitize path joins, inline small formatting fixes and return unified error message for missing format.
- Refactor stream/download logic: downloadStreamsToFiles now pipes stdout, resumes stderr, enforces a download timeout with explicit rejection, waits for closures, and checks child exit codes.
- Audio conversion tweaks: standardized ffmpeg/yt-dlp arg formatting and safer spawn usage; audio tag embedding uses AbortSignal timeout for cover art and logs more cleanly.
- UI/client fixes: encodeURIComponent videoUrl before API calls, minor JSX whitespace/formatting and progress/display adjustments.
- Misc: safer initializeConf promise handling, improved debug formatting, content-disposition safe filename normalization, and minor code style/formatting cleanups (ytdlp-binary, ytdlp-info, song-matching, lrclib, subtitles).

These changes reduce hangs, improve resource cleanup on errors/timeouts, and make the download pipeline more robust.
@DestroyCom DestroyCom merged commit 6491492 into master May 3, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant