Promote the 0.10 changelog entry to 1.0; feature-complete across web and
Android. Android versionName was already 1.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Download albums/playlists to private device storage (Room + WorkManager,
HttpURLConnection streaming). New Offline tab lists groups with progress;
tapping a group opens a detail screen (cover-art rows, play/shuffle, per-song
delete from device). Downloaded tracks play from disk on every screen.
Album/playlist cards show a downloaded badge; download button resumes missing
songs. Song storage is dedup'd by ID and reference-counted on delete.
Fixes: Android 15 download crash (dropped banned DATA_SYNC foreground service);
offline detail flash-back (initial empty state no longer mistaken for deleted).
Docs: CHANGELOG 0.10 + parity matrix; condensed README Features.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Sidebar: replace ♫ emoji with new equalizer SVG icon (icon.svg)
- Mobile header: remove duplicate logo+name next to search bar
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Keep nginx /app/ alias for favicon resolution only.
CSS/JS stay at root-relative paths via location /.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- icon.svg / icon-192/512.png: blue→purple equalizer gradient design
- nginx.conf: add /app/ alias so reverse-proxy prefix resolves assets
- vite.config.js: base /app/ + %BASE_URL% in favicon links so path
resolves correctly whether app is served at / or /app/
- ic_launcher.xml: same equalizer design for Android app icon
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- styles.css: remove overflow:hidden from .player-info-wrap — it was
clipping the player-status-row and shifting the shuffle badge
- index.html: use absolute /icon.svg path for favicon; add PNG fallback
for browsers without SVG favicon support
- icon-192.png / icon-512.png: regenerated from new equalizer SVG design
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wrap player-info + star in .player-info-wrap (flex, align-items:center)
so the star is immediately to the right of the text block and centered
vertically with it, instead of floating to the far end of the 1fr column.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- icon.svg: replaced music-note with 5-bar equalizer waveform (gold
gradient on dark bg) — works well as favicon and app icon at all sizes
- index.html: add <link rel="icon"> so browsers pick up the favicon;
move #btn-star-current out of the title row and into player-track so
player-track's align-items:center centers it with the full info block
- styles.css: add flex:1 to .player-info base rule; remove stale mobile
overrides that assumed the star was inside player-info (position:absolute,
padding-right hack)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Artist name from Spotify/source is stored directly in folder, filename,
and tags without being replaced by the iTunes artistName. iTunes is used
only for supplemental metadata (genre, year, cover, duration, trackNum,
albumArtist/collectionArtistName). Sort tags still receive romaji via
romajiForStorage() in ytdlp.js.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Consistent with point 7: original (kanji) names in folder, filename,
and main tags — romaji only in ARTISTSORT/ALBUMARTISTSORT.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Japanese Art Tracks on YTM/YouTube are often romanized ("Souiunatsu"
for "そういう夏") — the Japanese keyword filter in --match-filter always
rejected the correct video. Channel + duration are sufficient quality
gates for JP tracks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Album words ("KAGUYA Music", "OST" etc.) appended to the search query
caused wrong or no results — "Kei Haneoka 確信 KAGUYA Music" would miss
tracks the plain "Kei Haneoka 確信" query finds immediately.
Artist + cleanTitle is sufficient for both JP and Western tracks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
Both iTunes/MusicBrainz autofill buttons currently fill only partial form
fields; they should populate title, artist, album-artist, album, genre, year,
track, disc, total, cover — backend endpoints must return those fields too.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Artists are stored ;-separated (feat./ft. normalized away at download), and
both lookups already use the first artist. The real gap is the strict match
failing for multi-artist tracks (no fallback) plus iTunes autofill not filling
the artist/album-artist fields.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Duplicate check matches exact computed path only, so iTunes/romaji/trackNum/
album drift between runs re-downloads existing tracks. The no-duration cascade
branch lacks Mittelweg protection and grabs wrong covers (Yorushika 車窓 →
Piano Echoes Shasou).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
iTunes/MusicBrainz autofill don't fill the Album-Artist field; multi-artist
lookup splitting is inconsistent across the two endpoints (neither handles
/, feat., ft.).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Duplicate detection (filename-only), missing download size cap (52MB wrong
video), and uncaptured iTunes album artist (collectionArtistName) affecting
Navidrome grouping/sorting.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Captures the rules and hard-won details from recent work: no mocked frontend
data, two-part versioning, yt-dlp-only download cascade + Mittelweg rule, file
naming, the YTM-search limitation for obscure tracks, and edit/delete file
resolution. Loaded automatically by Claude Code at session start.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Promote 0.7.1 → 0.8 with this session's work (Mittelweg cascade, web_music
client, clean filename, duration-filter + metadata-editor fixes, squid removal)
- Drop the patch component from all historical versions (0.7.0 → 0.7, etc.)
- Add Metadata editor and Song delete rows to the parity matrix (Web only)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When a track's duration is known but nothing matches the duration window, the
cascade no longer falls back to blind title-only/no-filter grabs (which pulled
same-titled videos by other uploaders, e.g. a "recognize" fan clip). Instead it
relaxes only the duration constraint while still REQUIRING the artist channel +
title. If nothing matches, the download produces no file and the job now fails
cleanly with "Nicht eindeutig gefunden — bitte Direktlink im Importer nutzen"
(previously it falsely reported success with no file).
Single, album and playlist paths all surface the not-found state.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-add the ¶ms= Songs-shelf filter (the documented way to song-search
YouTube Music) so results exclude videos/playlists/clips. Verified neutral
for the ambiguous-artist case (yt-dlp's search can't surface that track
regardless) but cleaner for normal tracks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The filename used the raw title, so "recognize - Single" leaked into the
filename ("01 - MINA - recognize - Single.mp3"). Now the cleaned title drives
both the filename and the embedded title tag, giving "01 - MINA - recognize.mp3"
(Track - Artist - Title). The album folder keeps the full album name.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
After the embedded-title change (recognize vs "recognize - Single"),
Navidrome's title-derived stem can drop the "- Single"/"- EP" suffix the
on-disk filename still carries, so _findFileByStem matched neither exact nor
suffix and every fallback failed -> "File not found on disk" on edit/delete.
Now also match on the structured prefix in both directions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The delete-song endpoint returned a bare "File not found on disk" with no
context, making path-resolution mismatches impossible to diagnose. Now
reports the Navidrome path, music root, and searched filename — matching the
detail the edit-metadata endpoint already provides.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The ¶ms= Songs-tab filter re-ranked YTM results toward a same-named
artist's discography and excluded the official track (qNffDItRUeo) entirely,
even logged-out. The default blended search (what the browser shows) ranks
the exact track first. Removing the param so yt-dlp gets the same result set.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"MINA - recognize" skewed YTM ranking toward a same-named artist's full
discography, so the actual track (qNffDItRUeo) never appeared in results.
"MINA recognize" — matching how users type in the YTM search box — ranks
the exact official track first.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The YTM search (getYtmVideoUrls) ran without the web_music client, so yt-dlp
parsed the search page as a generic YouTube search and returned unrelated
videos instead of the native Songs shelf — the actual artist track (e.g.
qNffDItRUeo for MINA - recognize) never appeared in results. Apply the same
player_client=web_music,default arg to the search step.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
yt-dlp's android_vr player client returns "This video is not available" for
many YouTube Music Art Track / topic uploads (e.g. MINA - recognize) that
play fine in the browser. Switch to player_client=web_music with default
fallback so these tracks become downloadable, fixing the cascade falling
through to wrong YouTube videos.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
searchTitle (e.g. "recognize") is now passed to embedMetadata instead of
the raw title ("recognize - Single"), so the ID3 title tag matches the
actual song name. Album tag stays unchanged ("recognize - Single").
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
[youtube:tab] in stderr fires for all YTM search page processing, not just
artist channel expansion, causing channelExpanded=true for every search and
returning [] — bypassing all YTM cascade steps entirely. Fix: only check
stdout for /browse/UC (actual channel URL in results). Remove ytmsearch5:
scheme which is not supported by this yt-dlp version. Revert pool to 10.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ytmsearch5 (yt-dlp native YTM search) added as step 2 — returns different
result ordering than the manual YTM URL, recovering songs that the URL
approach ranks too low. YTM pool expanded from 10 → 20 results to reduce
fallthrough to YouTube.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
spawnYtdlp now always resolves so the hasAudio() cascade continues when a
single YTM result is geo-blocked or unavailable. Error is still logged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
YTM search used `title` directly, so "recognize - Single" was searched on
YouTube Music instead of "recognize" — causing no YTM results and falling
through to the less reliable YouTube search cascade.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Matches spotdl behaviour; still covers YouTube intros/outros while
reducing false positives from similarly-lengthed wrong songs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Track path: always fetch iTunes when durationMs/trackNum missing (not just genre)
- Both paths: strip release suffixes from title before iTunes lookup
- Log "Album:" instead of "Album aus Spotify:" (also fires for iTunes global search)
- Log duration when fetched from iTunes so it's visible in job log
Ensures all importers have consistent yt-dlp search filters (duration filter
now always runs when iTunes finds the track).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- downloads.js: use durationMs (Spotify) before trackTimeMillis (iTunes)
- server: read durationMs from top-level body, not only from tracks[0]
- server: also grab durationMs + trackNum from iTunes in the Spotify path
Without durationMs the duration filter never ran, allowing a 58s fan video
to pass all text filters and be downloaded instead of the actual song.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Handles all Spotify trailing suffixes: Single, EP, Remaster, Deluxe Edition,
Bonus Tracks, B-Sides, bare years (2024), Official Music Single, etc.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- "recognize - Single" title → search uses "recognize" (strips "- Single/EP/Album/…")
- Adds Single/EP/Remaster/Deluxe/… to titleKws STOPWORDS so they never become
mandatory title-match filters (no YouTube video title contains "Single")
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Western tracks now use artist + title only (matches spotdl behaviour).
Album suffix was causing noise for short English titles like singles
where album name = title + "Single".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Album "recognize - Single" produced query "MINA - recognize recognize Single"
because "recognize" appeared in both title and album name. Now filters album
words already present in the title, plus generic words (Single, EP, Remaster…).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Deletes backend/lib/squid.js; strips all references from downloader.js,
config.js, novasonic-server.js, docker-compose.yml, .env.example, README,
and CHANGELOG. Default DOWNLOAD_BACKEND is now ytdlp.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>