- Japanese tracks stored with original names in folder/filename/tags; romaji only in sort tags - findExistingTrack: case-insensitive, searches all album folders under artist, strips release suffix - --max-filesize 30M + 2x duration cap on all yt-dlp calls - iTunes collectionArtistName captured as album_artist; fixes compilation album splitting - album_artist tag uses mainArtist (not rawArtist) so all tracks in one album are consistent - No-duration cascade: steps 5-8 (title-only/no-filter) removed — fail cleanly like with-duration - Autofill iTunes: fills title, artist, albumArtist, discNum (was album/genre/year/track/total only) - Autofill MusicBrainz: fills albumArtist, discNum (was missing both) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
4.4 KiB
4.4 KiB
CLAUDE.md
Guidance for Claude Code when working in this repository.
What this is
NovaSonic — a self-hosted music streaming PWA (Vite + vanilla ES modules) and Android app (Jetpack Compose + Media3/ExoPlayer) for Navidrome / Subsonic servers, plus a Node.js/Express backend that downloads tracks via yt-dlp and proxies APIs.
Critical rules
- Never hardcode or mock frontend data. All library data comes from the Navidrome (Subsonic) API. No placeholder songs/albums/artists, ever.
- Don't commit or push unless asked. When asked, push to the Gitea remote.
- Two-part versioning
MAJOR.MINOR(e.g.0.8) — no patch component. Each release bumps the minor; features and fixes go in the same release. SeeCHANGELOG.md. - Keep the
CHANGELOG.mdPlatform Parity Matrix accurate when adding cross-platform features.
Layout
src/— web PWA (vanilla ES modules). State insrc/state.js(theSobject); Subsonic/iTunes API insrc/api.js; views insrc/views/, UI insrc/ui/.backend/novasonic-server.js— Express API (downloads, metadata edit/delete, events/ABS proxy).backend/lib/—ytdlp.js(download + cascade + ffmpeg tagging),downloader.js,metadata.js(iTunes lookup, file resolution helpers),config.js,jobs.js,romaji.js.android/— native Kotlin app (separate; backend-dependent web features like the metadata editor are not in the Android app).
Commands
npm run dev # Vite dev server (HMR)
npm run build # production build → dist/
npm run preview # preview the production build
Backend runs in Docker (see docker-compose.yml); on the deploy host it lives at
/opt/navidrome/music. Restart after backend changes: docker compose restart novasonic-backend
(this also re-pulls the latest yt-dlp).
Download pipeline (hard-won knowledge)
- Downloads are yt-dlp only (the old squid.wtf/Qobuz backend was removed).
- File naming:
{NN - }Artist - Title.extwith the release suffix stripped from the title (no- Single/- EP). The album folder keeps the full album name. Original names are used for folder, filename, and all main tags (artist/title/album/album_artist) — even for Japanese tracks. Romaji goes only into sort tags (TITLESORT / ARTISTSORT / etc.). - Search cascade (
ytdlp.js): YTM search first (web_musicplayer client, Songs filter param), then regular YouTube; each candidate filtered by duration (±10%, min 10s), title keyword, artist channel, and variant exclusions (remix/cover/sped/…). - "Mittelweg" rule (with AND without duration): when nothing matches in the filtered steps, relax only the duration constraint while still requiring the artist channel. If nothing matches, download nothing and fail cleanly with "Nicht eindeutig gefunden — bitte Direktlink im Importer nutzen". Never fall back to a blind title-only/no-filter grab — it pulled same-titled covers by other uploaders, and wrong Piano Echoes for Japanese tracks.
- Size cap:
--max-filesize 30Mon every yt-dlp call; Mittelweg steps also cap at 2× expected duration. Prevents saving a 28-min video (52 MB) when the wrong result slips through. - Duplicate detection (
metadata.js findExistingTrack): searches case-insensitively across all album folders under the matching artist folder; compares normalized artist+title ignoring track prefix and release/variant suffix. Handles DJ OKAWARI vs DJ Okawari and "- Single" drift. - Known limitation: yt-dlp's YTM/YouTube search can't surface very obscure Art Tracks by
ambiguously-named artists (e.g. "MINA") — the browser finds them, yt-dlp's API extraction
doesn't (confirmed across params/clients/deno). For those, the direct link in the importer
(
sourceUrlpath) is the reliable route. - File resolution for edit/delete: Navidrome's stored path may use a different mount root
or a stale stem.
metadata.jsrebases the path and falls back to recursive/stem matching (tolerant of release-suffix mismatch). A ghost Navidrome entry needs a full library rescan. - Album splitting: prevented by consistent
album_artisttag. All tracks in a download share the samemainArtistvalue (= iTunescollectionArtistNamewhen available, elsealbumArtistparam, else track artist). Never userawArtistforalbum_artist.
UI / references
When referencing files in chat, use clickable markdown links, not backticks:
[ytdlp.js](backend/lib/ytdlp.js).