When Navidrome has a Japanese path in its DB but the file was saved with
a romaji filename by the download pipeline, the existing fallback chain
compared the Japanese title stem against romaji filenames and always
failed. Added _findFileByStemOrRomaji which converts the stem via
romajiForStorage before the final recursive search, covering all three
endpoints: edit-metadata, delete-song, song-sorttags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Call reconcileNativeState() on visibilitychange→visible so the player UI
updates immediately when the user opens the app after a background auto-advance,
before deferred evaluateJavascript callbacks have had a chance to fire
- After deleteSong() succeeds, refresh the active playlist view (grid or detail)
with a 2s delay to let Navidrome's background scan complete
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- /api/delete-song endpoint: resolves path via same fallback chain as
edit-metadata, validates file is inside music root + is an audio file,
deletes it, then prunes the album folder if empty and the artist folder
if that too becomes empty — no recursive rm, depth strictly limited to
two levels above the file so no accidental mass-deletes
- Safety: path.resolve() guard ensures no path-traversal outside music root;
rmdirSync (non-recursive) fails if dir is non-empty as extra safety net
- Frontend: delete button (red outline, trash icon) at bottom of edit mode
with confirmation dialog showing title + folder-cleanup warning
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three improvements:
- openNowPlaying(): reads NSBridge.getState() (always-fresh @Volatile snapshot)
via ID search to reconcile S.qIdx before render; fixes NP showing old song
when deferred evaluateJavascript callbacks haven't fired yet after backgrounding
- onPositionUpdate(): periodic check (~every 2s) detects track mismatch via
snapshot ID; catches missed/delayed onTrackChanged when NP is open
- updatePlayerUI(): pre-warms next song's 1024px cover so auto-advance NP
swap is instant from browser cache instead of a 1-second load delay
- styles: add backface-visibility:hidden + isolation:isolate to .np-overlay
to reduce GPU composite flicker on Android WebView on manual skip
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Added a play history stack (_playHist) that records S.qIdx before each
forward advance (manual next, auto-advance via onTrackChanged). prevSong
now pops from this stack instead of blindly doing S.qIdx--, so pressing
back during shuffle always returns to the song that was actually playing
before, regardless of where shuffle jumped. History clears on new queue.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two fixes:
1. Remove snapshot read from openNowPlaying() — the MediaController snapshot
can show the old song at the old index (both still valid entries in the
queue), so the ID check does not detect staleness; after the onTrackChanged
fix S.qIdx is always authoritative so there is no need to read the snapshot
2. Call syncNowPlaying() explicitly in onTrackChanged so the NP overlay is
refreshed immediately when the song changes while it is already open
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
openNowPlaying() was blindly trusting the MediaController snapshot index,
which can lag one track behind ExoPlayer on auto-advance. This overwrote
the already-correct S.qIdx that onTrackChanged had just set, causing the
NP overlay to render the previous song while the mini-player showed the
right one. Now only accept the snapshot index when the song ID at that
position matches — stale snapshots are silently ignored.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace saturated blur filter with actual dominant-color extraction:
fetch cover as blob (avoids canvas CORS taint), draw on 24x24 canvas,
find most vivid pixel, darken to ~28% for a moody solid background.
Blurred bg layers hidden when mode is active. Transitions smoothly
between songs via CSS transition on background.
Renamed setting label to "Einfarbiger Player-Hintergrund".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Now Playing: add "Dynamische Hintergrundfarbe" toggle in Theme settings;
when on, np-bg uses higher saturation/brightness so the cover color really
shows through the blurred background
- Settings modal: extend to bottom nav on mobile (was leaving ~74px dead space
for the player bar which the overlay already covers at z-index 200)
- Android: fix onTrackChanged always staying on the first song — the snapshot
from MediaController can lag behind ExoPlayer on auto-advance, causing the
stale currentIndex to overwrite the correct p.index; now only advance to
a higher live index (for rapid-skip catch-up) and use p.trackId directly
instead of the potentially-stale snapshot id for the queue-mismatch guard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Handles "File not found" for songs with non-standard folder/artist names
(e.g. artist separator • or year-only album folders) by doing a deep
recursive title-stem search as a last resort after rebase and dir scan.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/api/itunes/lookup was returning 400 for any request without artist+title,
including id-based lookups (?id=...&entity=album) used by the artist page.
Use next() to fall through to the /:endpoint proxy for those requests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When iTunes enrichment returns a jingle/preview as the first result (e.g.
trackTimeMillis=9000), the yt-dlp duration filter becomes impossible
(duration > 30 & duration < 24), all filtered steps fail, and the final
unfiltered fallback downloads whatever YouTube returns first — often a
short clip.
Fix 1: spotify.js — only copy iTunes trackTimeMillis to durationMs when
it is > 30 seconds (genuine song, not a jingle or preview).
Fix 2: ytdlp.js — only enter the duration-filtered search branch when
durationMs > 30 000ms, so a corrupted short value silently falls back to
the unfiltered search path which still finds the correct full song.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a file is renamed or moved to a new artist/album folder during
metadata edit, Navidrome assigns a new song ID, breaking all playlist
entries that referenced the old ID.
Before the edit: collect which playlists contain the song.
After rename + rescan: poll Navidrome until the new song appears,
then update each affected playlist (add new ID, remove old index).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When Navidrome has a stale index (e.g. file was renamed from
'01 - Idea 10.mp3' to '01 - Gibran Alcocer - Idea 10.mp3'),
_findFileInDir now falls back to the original file's directory
and matches by track-number prefix ('01 - ') so the renamed file
is found even without a Navidrome rescan.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Spotify enrichment only copies album/trackNum/duration from iTunes.
Now also fetch totalTracks, discNum, discTotal from iTunes for:
- alreadyEnriched playlist tracks
- single track downloads where album came from Spotify
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1. JP genre mapping table in metadata.js — when only JP iTunes has the song
(niche Japanese artists), map localized JP genre names (アニメ, ポップ, etc.)
to English equivalents so files never get Japanese characters in genre tag.
2. Spotify playlist import (alreadyEnriched path) — was skipping iTunes lookup
entirely, leaving genre empty. Now does a lightweight genre-only lookup.
3. Single track download with Spotify album — was skipping iTunes when album
already known from Spotify. Now also fetches genre from iTunes when missing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When Navidrome and the backend run in separate containers, Navidrome returns
absolute paths rooted at its own mount (e.g. /opt/navidrome/music) which
don't exist in the backend container (mounted at /music).
Add three-stage fallback:
1. _rebasePath: strip leading path components and try under CONFIG.musicPath
2. _findFileRecursive: exact filename search under CONFIG.musicPath
3. _findFileInDir: fuzzy search in album dir by track-number prefix or title stem
(handles files renamed to include artist in the filename)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JP store returns localized genres (e.g. イージーリスニング instead of Easy Listening).
Fetch JP and US in parallel, use JP match for title/album/artwork but always
take genre from US result so genre is always in English/Western characters.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevent false Japanese matches by never using JP title-only fallback.
Strategy: JP strict → US strict → US title-only so niche Western tracks
still resolve but same-English-title Japanese songs don't pollute results.
Also fix JS error in autofillItunes: ie-total → ie-totaltracks.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Change overlay from position:absolute to position:fixed so it isn't
clipped by main-content's overflow-y:auto
- Set overlay top dynamically from header.offsetHeight so it always
anchors exactly below the sticky header
- Responsive max-height: accounts for player bar + bottom nav on mobile
- Hide logo text on ≤359px screens to keep search pill wide enough
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the hidden toggle-based mobile search bar with a permanently
visible pill-shaped search input in the header row, next to the logo.
- Search is always visible — no button click required
- Typing shows a floating overlay with library results (artists, albums,
songs) without navigating away from the current page
- Clearing the input or navigating to any page closes the overlay silently
- The ✕ clear button appears only while text is present
- navigate() now calls closeHeaderSearch() so results disappear cleanly
when tapping any result or bottom nav tab
- Old toggleMobileSearch / closeMobileSearch kept as no-op stubs for safety
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Artist grid and detail header now show romaji names when toggle is on.
Builds a Map<artistId, artistRomaji> from the existing song romaji index
so no extra API call is needed.
- Apple Music autofill button added to edit modal next to MusicBrainz.
Reuses the existing lookupItunesTrackMeta backend function via a new
GET /api/itunes/lookup endpoint — fills album, year, genre, track#,
and cover art.
- Romaji index is invalidated after every successful download so newly
added songs appear with correct sort tags without requiring a page reload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When kuromoji marks a katakana token as reading '*' (unknown word — common
for band names like ヨルシカ), the code fell back to the surface_form which
is still katakana. _hiraToRomaji can't handle katakana so it passed through
unchanged, causing sort tags to show ヨルシカ instead of Yorushika.
Fix: if the surface_form contains katakana and reading is *, convert it
mechanically via _kataToHira + _hiraToRomaji — katakana-to-romaji is always
unambiguous and needs no dictionary.
Also add a Japanese-char guard in the frontend when applying romaji index
values, so stale cache entries can never overwrite correct values with Japanese.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sort tag fields were not reset when opening a new song's edit modal,
so values from the previously opened song persisted until the async
romaji index fetch completed (or indefinitely if it found no entry).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
File sort tags containing Japanese characters are incorrect (sort tags
should be romaji/ASCII). Ignore them and keep the romaji index value
instead so the edit modal always shows the correct romaji pre-fill.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
romajiLookup returned null when the index wasn't loaded (only loaded when
showRomaji is ON). Now fetchRomajiIndex is called on-demand in the edit modal
so sort tags pre-fill from the index works even when the toggle is OFF.
Actual file tags from /api/song-sorttags still overwrite as the final step.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- backend: new GET /api/song-sorttags reads TITLESORT/ARTISTSORT/ALBUMARTISTSORT/
ALBUMSORT directly from the file via ffprobe
- ui.js: pre-fill sort fields from romaji index, then overwrite with real file
tags from the backend (shows actual values even if manually set)
- index.html: remove redundant hint texts in settings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- views.js: skip ASCII word-match when artist name is non-ASCII so Japanese
artists (e.g. ヨルシカ) resolve correctly to an iTunes artist ID
- index.html + styles.css: collapsible Sort-Tags section in edit modal
- ui.js: pre-fill sort fields from romaji index; include in save body
- novasonic-server.js: accept manual titleSort/artistSort/albumArtistSort/albumSort
overrides; fall back to auto-computed romaji when not provided
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
On page reload the saved cfg restored showRomaji=true but _romajiMap was
never fetched, so all display helpers fell back to Japanese text. Await
fetchRomajiIndex before the first navigate when the setting is on.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
albumQuerySuffix and related consts (escRe, JP_CHARS, variantFilter, titleKws,
artistChannelPart) were declared after the ytmQuery/query lines that reference them,
causing ReferenceError on every download. Moves the block to before its first use.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Skip channel filter when artistFirstWord is non-ASCII (Japanese kanji/kana):
YouTube channels are named in romaji/English, not kanji — the filter was
rejecting all valid results and causing 6+ extra cascade steps
- Add albumQuerySuffix: when title is Japanese (no keyword match) OR has only
one romaji keyword, include the album's first two English words in the search
query so YTM ranks the official OST/album version above fan covers
Fixes: 照井順政 channel filter rejecting all YTM results
Fixes: "Souiunatsu" matching covers with romaji subtitle instead of official OST
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Downloads now store original Japanese in TITLE/ARTIST/ALBUM tags
so Apple Music, iTunes, YTM etc. all see the authentic original text
- Sort tags (TITLESORT/ARTISTSORT/ALBUMSORT) written with romaji for
apps that use them for sorting/display
- iTunes-resolved English artist name used for ARTISTSORT and folder names
when available (more accurate than kuromoji for known artists)
- rawArtist parameter added to downloadTrack so YTM search query uses
the original Japanese channel name (fixes wrong song downloads)
- spotify.js: artistName preserved as original; itunesArtistName stored
separately so the Spotify importer shows Japanese titles
- romaji index now includes titleRomaji/artistRomaji/albumRomaji fields
- Frontend: showRomaji setting (Settings > Bibliothek) — when on, player
bar, now-playing, queue, and song lists all show romaji via index lookup
- refreshDisplayMode() updates all live UI when the toggle is saved
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Clean Japanese punctuation (、。・) in romajiForStorage so it doesn't pass through
- Add POST /api/romaji-convert endpoint for batch title conversion
- Add romajiConvert() to api.js
- Add third fallback in _createNdPlaylist: convert Japanese title to romaji and
search Navidrome with it (for songs already downloaded with romaji tags)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- spotify.js: stop overwriting trackName with iTunes English title —
yt-dlp must search with original Japanese title to find the right song
(was downloading English covers e.g. Ghost In A Flower instead of 幽霊花)
- metadata.js: add title-only fallback in lookupItunesTrackMeta so tracks
are found even when kuromoji gives wrong artist romanization (e.g.
Terui Jun Sei vs iTunes Yoshimasa Terui); also return itunesArtistName
- novasonic-server.js: use itunesArtistName from iTunes lookup as the
stored artist when available (single-track and playlist download paths)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ytdlp.js + novasonic-server.js: apply romajiForStorage to album tag
and folder/rename paths (was only applied to artist/title before)
- ui.js: _createNdPlaylist falls back to romaji index for Japanese titles
since Navidrome search3 doesn't index Japanese text properly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
iTunes ranks by popularity, so searching "WRLD Tunes" returned Juice WRLD
first. Now fetches 5 results and picks the first where all query words
appear in the iTunes artist name, preventing false-positive matches.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Search iTunes with original Japanese names (accurate) rather than
pre-converting with kuromoji (inaccurate for uncommon names/readings).
iTunes returns the correct English artist name and title directly.
Also run iTunes enrichment for single-track Spotify imports.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add romajiForStorage() to romaji.js — smart word spacing + Title Case
- ytdlp.js: apply to folder names, filenames, and embedded tags
- novasonic-server.js: apply in edit-metadata endpoint and Spotify resolve
- spotify.js: update track artist/name from iTunes canonical English names
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Space as fallback separator on save when no explicit separator present
(Artist1 Artist2 → Artist1;Artist2)
- & and / added to explicit separator set everywhere
- navidromeArtist (download path) also handles /
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_fmtArtists and _normalizeArtists now split on & and / in addition to
• · ; , — so "Arriez & King Dif" typed in the field normalizes correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
"Arriez & King Dif" was used as-is for the folder; now splits on , ; &
so only the first artist is used: "Arriez".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
getSong often omits albumArtist; getAlbum always returns album.artist.
Enrich the song object before showing the edit form.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Album name no longer shown inline next to the artist — navigate via
right-click > Zum Album instead, available everywhere.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Normalize •, ·, ;, , separators on save (both edit and download paths)
- Fix ndSong scope bug (was inaccessible outside try block in edit-metadata)
- Apply _normalizeArtists to artist/albumArtist before ffmpeg tags and file rename
- Storage format: Artist A;Artist B (no space, Navidrome/MusicBrainz style)
- Display format: Artist A; Artist B (space, via _fmtArtists in frontend)
- Show separator hint below label in metadata editor fields
- Remove hover effect on player bar song title
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Naming convention (download + edit-metadata):
Before: {01 - }Title.ext
After: {01 - }Artist1, Artist2 - Title.ext
- ytdlp.js: builds fileBase with artist prefix; findExistingTrack checks
both new and old format so existing files aren't re-downloaded
- metadata.js: findExistingTrack accepts optional safeArtist param
- novasonic-server.js: file rename on title edit now includes artist
(falls back to Navidrome's stored artist if not provided in body)
Song list subtitle: hide album name when album === title (singles where
the album is named after the track appear to duplicate the title)
Player bar: removed .player-title:hover underline/colour effect
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the user opens the edit modal before the getSong async call returns,
the form fields are populated from the initial song object (from the song
list) which may omit albumArtist and other fields that only getSong
returns. Now, when getSong resolves and edit mode is already visible, all
form fields are updated to the authoritative Navidrome data.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After saving metadata with ffmpeg:
1. Title change → rename the mp3 in place to "{01 - }?{sanitized title}.ext"
(same convention as the downloader uses)
2. Album-artist or album change → compute new {artistFirstPart}/{album}
directory, move the file there (create dirs as needed)
3. Old album dir is deleted if empty after the move; old artist dir
is deleted too if it also becomes empty
When only the album name changes (no artist change) the old in-place
folder-rename behaviour is preserved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
iTunes: split artist by [\s,;/] not just space so "VXLLAIN, iGRES"
yields first word "vxllain" (no trailing comma) and matches correctly.
MusicBrainz: extract first artist from the full credit string (split on
,;&•) and use `artistname` field — MB stores credits like "VXLLAIN feat.
iGRES & ENXK" which never matched the comma-joined full string.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Info modal view now shows Album-Künstler row from Navidrome albumArtist
- Song Importer detects youtube.com / youtu.be URLs (red badge)
- New /api/youtube/resolve endpoint: yt-dlp --dump-json for single
videos and playlists (up to 50 entries), enriched with iTunes lookup
for proper album/year/genre/cover on single tracks
- New src/youtube.js renders preview card and delegates download to
executeSpotifyDownload (which already handles sourceUrl → direct yt-dlp)
- Importer hint updated to list YouTube as a supported source
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds an editable Album-Künstler input to the info modal. Populated from
Navidrome's albumArtist field on open; saved as album_artist tag via
ffmpeg. Explicit value takes precedence; falls back to deriving from
artist (first semicolon segment) when artist is changed without an
explicit albumArtist.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JSON-LD could set track_name but return empty byArtist, making the OG
block (guarded by `if not track_name`) never run — leaving artist blank.
Now OG runs whenever either name or artist is missing, and only fills
fields that are still empty so JSON-LD name isn't overwritten.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The backdrop click handler was too easy to trigger accidentally,
especially when editing fields. Close button handles dismissal.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes LRCLIB lookup, .lrc sidecar file writing, and lyrics/uslt
metadata embedding from all downloads.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SC resolve: strip yt-dlp placeholder albums (starting with '['), use track title as album for singles
- edit-metadata: if Navidrome path stale, search music root recursively for file by name
- Both single and playlist SC track handling updated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JSON-LD byArtist gives the correct artist list. As secondary fallback,
OG desc format is 'Artist · Song · Year' not 'Song · Artist · Album',
so use desc_parts[0] not desc_parts[1] for artist.
Also handle both attribute orderings in meta tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SC tracks: use artworkUrl/album/genre fields (not iTunes artworkUrl100/collectionName/primaryGenreName)
- Playlist handler: same SC field fallbacks for artwork, album, genre, year
- ytdlp.js: convert & to , in album_artist tag so Navidrome albumartist.Split works
- Importer: clear both previews on any URL input change, not just on clear
- edit-metadata: rename album folder on disk when album name changes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Missing imports caused Object.assign(window,...) to throw before navigate
was ever assigned, breaking all onclick nav buttons.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove top-level ui.js import from soundcloud.js (broke views.js→soundcloud.js→ui.js→views.js cycle)
- Use window.executeSpotifyDownload() instead to avoid the circular dep
- Merge Spotify + SoundCloud into one unified URL input that auto-detects
the service and colors the badge/button accordingly
- resolveImporterLink() routes to the correct handler based on URL
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New src/soundcloud.js: resolves SoundCloud URLs via yt-dlp --dump-single-json,
renders track/playlist preview with per-track download buttons
- Backend POST /api/soundcloud/resolve: extracts metadata (title, artist, genre,
artwork, duration) directly from SoundCloud — no iTunes lookup needed
- ytdlp.js: sourceUrl param — if set, downloads directly from URL instead of
ytsearch cascade (used for SoundCloud tracks)
- Spotify importer page renamed to Song Importer, now contains both
Spotify and SoundCloud sections in one view
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-map 0:v? copies the embedded cover stream from the original file when
no new artwork is supplied, preventing it from being silently stripped.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Info modal gets an edit pencil button that switches to edit form
- Editable fields: title, artist, album, genre, year, track#, disc#, total tracks
- Cover art: upload your own image or auto-fetch from Cover Art Archive via MusicBrainz
- MusicBrainz button queries /api/musicbrainz/lookup and autofills all fields
- Save POSTs to /api/edit-metadata which rewrites tags via ffmpeg and triggers Navidrome rescan
- Backend: GET /api/musicbrainz/lookup + POST /api/edit-metadata endpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
navidromeArtist() now writes ; instead of / so Navidrome splits
correctly with the updated Tags.Artist.Split config.
Also handles & (e.g. "Artist A & Artist B" → "Artist A;Artist B").
Add fix-artist-separators.sh — one-time Debian script to update
existing library files from " / " to ";" in artist tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract parseArtists() helper — uses Navidrome's resolved artists array
when it has multiple entries, otherwise splits artist string on ';' only.
Removes old split on / that broke Au/Ra, HUNTR/X etc everywhere.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Manual split on / broke artist names like HUNTR/X and Au/Ra.
Now uses s.artists (OpenSubsonic array with correct IDs per artist)
and falls back to s.artist as a single unsplit link.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Page loads instantly with song-count order, then silently re-sorts
once the backend aggregates play counts across all songs. Result cached
20 min server-side to avoid repeated full-library fetches.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shuffle now shuffles the queue array once (current song first, rest
randomised behind it) instead of picking a random index on every skip.
Toggling shuffle off restores the original queue order. Removed the
'once' repeat state added in the previous commit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Scrobble timer: 12s → 15s
- Play count moved after action buttons, shown as "23×" (distinct from duration)
- Song star/menu buttons always visible (removed opacity:0 hover-only rule)
- Artist all-songs page: added star + 3-dots to every row (same as top songs)
- Hero card: picks from full recent+frequent pool (was capped at first 5 = all frequent)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Scrobbling:
- Replace end-of-track scrobble with 12s timer approach
- Android: schedule on onTrackChanged, cancel on pause
- Web: schedule in playCurrent, cancel in togglePlay pause + onEnded
- Skip/new track resets the timer automatically (new song ID)
Artist top songs:
- Add star button and 3-dots context menu to every top song row
- Topsong grid expanded to 5 columns to fit action buttons
Settings — play count toggle:
- New "Bibliothek" section with toggle switch
- When on: shows play count in album/playlist song rows
- Artist page always shows play count regardless of toggle
- Persisted in ns_cfg localStorage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Scrobble to Navidrome on auto-advance (Android) and track end (web)
so type=recent and type=frequent are accurate for the hero card
- Genre section: pill chips → grid cards with colored left bars
- Top 15 genres shown by default (sorted by song count), expand toggle
- Genre card: click → genre detail page with full song list
- Genre card: hover play button → shuffle-play without navigating
- Genre detail page reuses album-detail layout (back btn, stats, song list)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>