Add download APIs, metadata, lyrics & ID3 embedding#246
Conversation
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.
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (24)
📝 WalkthroughWalkthroughThis PR refactors the media download system from a single unified endpoint ( ChangesMedia Download System Refactoring
Sequence DiagramsequenceDiagram
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
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly Related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Review rate limit: 0/1 reviews remaining, refill in 33 minutes and 9 seconds.Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (26)
.gitignoreapp/api/download/audio-library-ready/route.tsapp/api/download/audio/route.tsapp/api/download/video/route.tsapp/api/video-converter/route.tscomponents/custom/VideoSelect.tsxlib/api-guard.tslib/audio-convert.tslib/embed-id3.tslib/lyrics/index.tslib/lyrics/lrc-to-sylt.tslib/lyrics/lrclib.tslib/lyrics/subtitles.tslib/lyrics/types.tslib/lyrics/vtt-to-sylt.tslib/metadata/index.tslib/metadata/providers/itunes.tslib/metadata/providers/musicbrainz.tslib/metadata/types.tslib/route-utils.tslib/server-conf.tslib/song-matching.tslib/video-download.tslib/ytdlp-binary.tslib/ytdlp-info.tspackage.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}: Useimport { prisma } from "@/lib/prisma"for Prisma client access — never usenew PrismaClient(). Prisma 7 requires aPrismaLibSqldriver adapter; bare instantiation throws at runtime.
Never callprisma.$disconnect()on the shared singleton instance, as it breaks subsequent requests and cron runs.
Files:
lib/api-guard.tslib/ytdlp-binary.tslib/metadata/index.tslib/metadata/providers/musicbrainz.tslib/lyrics/vtt-to-sylt.tslib/lyrics/lrc-to-sylt.tslib/lyrics/types.tslib/metadata/providers/itunes.tslib/ytdlp-info.tslib/route-utils.tslib/embed-id3.tsapp/api/download/video/route.tslib/lyrics/lrclib.tslib/video-download.tslib/lyrics/subtitles.tslib/song-matching.tslib/audio-convert.tsapp/api/download/audio-library-ready/route.tsapp/api/download/audio/route.tslib/server-conf.tslib/metadata/types.tslib/lyrics/index.tscomponents/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 viainitializeConf().
Files:
lib/api-guard.tslib/ytdlp-binary.tslib/metadata/index.tslib/metadata/providers/musicbrainz.tslib/lyrics/vtt-to-sylt.tslib/lyrics/lrc-to-sylt.tslib/lyrics/types.tslib/metadata/providers/itunes.tslib/ytdlp-info.tslib/route-utils.tslib/embed-id3.tslib/lyrics/lrclib.tslib/video-download.tslib/lyrics/subtitles.tslib/song-matching.tslib/audio-convert.tslib/server-conf.tslib/metadata/types.tslib/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) viaselectYtDlpPath()for actual stream downloading; useyoutubei.jsvialib/innertube.tsfor metadata and format list retrieval.
Files:
app/api/download/video/route.tsapp/api/download/audio-library-ready/route.tsapp/api/download/audio/route.ts
app/api/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Store merged MP4s in
temp/cached/and index them in theFiletable by URL+quality to serve repeat requests from cache without re-downloading.
Files:
app/api/download/video/route.tsapp/api/download/audio-library-ready/route.tsapp/api/download/audio/route.ts
🔇 Additional comments (27)
package.json (1)
34-34: LGTM —node-id3pinned to the current latest.
node-id3latest published version is0.2.9, so the range^0.2.9correctly tracks it with no update noise.lib/lyrics/types.ts (1)
1-4: LGTM.Clean shared type;
timeStamp: numberin milliseconds aligns with thenode-id3synchronisedLyricscontract.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.tsuses first-wins perqualityLabel, so sorting descending byheightbefore 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 (unlikeserver-conf.ts's_initPromise).lib/lyrics/vtt-to-sylt.ts (1)
3-40: LGTM — correctly handles bothHH:MM:SS.mmmandMM:SS.mmmVTT 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, andfindVttUrl/vttToSyltare composed cleanly. Fetch timeout is covered in thelrclib.tscomment above.lib/metadata/types.ts (1)
1-16: LGTM — clean, minimal type contracts; allSongMetadatafields correctly optional for partial-enrichment scenarios.lib/metadata/providers/itunes.ts (1)
1-39: LGTM — correct use ofsatisfies SongMetadatafor compile-time contract enforcement, safe optional chaining ondata.results, and clean graceful-degradation viacatch {} → return null. Fetch timeout covered by thelrclib.tscomment.lib/metadata/index.ts (1)
1-25: LGTM — sequential provider fallback with per-provider isolation andresult.title || result.artistvalidity guard are all correct. The overall latency concern stems from the pointless delay inmusicbrainzProvider(addressed in themusicbrainz.tscomment).lib/embed-id3.ts (2)
1-9: LGTM!Clean interface definition. The
EmbedOptionsinterface properly mirrors the optional nature ofSongMetadatafields and lyrics components.
51-77: LGTM!The SYLT/USLT embedding logic is correct, with proper constants usage and logging. Error handling for
NodeID3.writeis appropriate.lib/lyrics/index.ts (3)
1-10: LGTM!Clean type definitions and re-exports. The
LyricsResultinterface appropriately models the synced/plain lyrics distinction.
15-20: Duration validation logic is correct.The guard on line 16 (
!durationSec) correctly handles bothundefinedand0cases, 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
_inFlightMap 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_REregex 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 aYoutubeDlwrapper instance (for higher-level integration), whilegetYtDlpBinaryPath()returns the path string to the binary. Sincelib/audio-convert.tsspawns the process directly viaspawn(getYtDlpBinaryPath(), ...), it requires the path string, makinggetYtDlpBinaryPath()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
embedId3Tagscall 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.
| temp | ||
| videos | ||
| video | ||
| /video |
There was a problem hiding this comment.
🧩 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.
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.
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
Chores