Code 101 means --max-downloads was reached — the file was downloaded
successfully. Treating it as error caused the ffmpeg/metadata step to
be skipped, leaving files with no tags.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
--playlist-items 1-5 --max-downloads 1 on all filtered steps so yt-dlp
skips non-matching results (wrong artist, sped up, wrong duration) and
lands on the first result that actually passes all active filters.
Unfiltered last-resort fallbacks keep --playlist-items 1.
YouTube fallback uses ytsearch5: to match the 5-result window.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Playlist track search: use 'Artist Title' query + songCount=10 (was title-only + 5)
fixes songs like 'Dead' by Geoxor being missed due to common-word collision
- Missing tracks now show 'Artist – Title' instead of just title
- renderJobs: update existing cards instead of full innerHTML wipe every 1.5s
eliminates slideUp animation re-triggering on every poll tick
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Snapshot dir before each yt-dlp call so hasAudio() only detects
files added by the current attempt, not pre-existing files from
parallel jobs downloading to the same album directory.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Duration: ±15% (min 15s) instead of -40%/+50%
- Variant filter: reject sped/speedup/slowed/reverb/nightcore/karaoke/
parody/remix/cover/acoustic/instrumental unless original title has keyword
- Cascade: YTM+YT tried at each level (dur+artist+variant → no artist → no dur → no filter)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Strips special chars from artist name (☆NoAki☆ → NoAki) and adds
uploader *= check so wrong-artist results are rejected early.
Falls back to duration-only, then no-filter if artist filter yields nothing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previous order let wrong YTM results through on the no-filter fallback.
New order: YTM+filter → YT+filter → YTM no filter → YT no filter.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ytmsearch1: is not a valid yt-dlp scheme — use full music.youtube.com URL with --playlist-items 1.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
e.target in dragstart is always the draggable item element, never the
handle child — so closest('.pl-drag-handle') always returned null and
every drag was cancelled. Now mousedown on handle sets fromHandle=true
and item.draggable=true; dragstart checks the bool instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
draggable=true must be set before any mouse event (not on mousedown).
Firefox requires setData() for drag to work. Removed duplicate dragend
listener that was resetting draggable=false and conflicting with the first.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
official audio consistently breaks remixes and lesser-known artists.
Duration filter is the primary quality check.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drag handle appears on hover, moves items in DOM, syncs order to
songs array, and saves to Navidrome via createPlaylist 800ms after
last drop (debounced to avoid rapid API calls).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
closePlModal() resets S.plModalSongs=[] before ids was computed,
so no songs were ever added to the playlist.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remix titles (remix/edit/flip/vip/etc.) are specific enough without suffix.
Normal tracks get 'official audio' to prefer audio over music videos.
Original official-audio issue was only for remixes — now caught by regex.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adding 'audio' shifts results for lesser-known artists (Kanro, etc.).
Plain 'Artist - Title' matches what users find manually on YouTube.
Duration filter is the reliable quality check against wrong songs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remixes already have '(Remix)' etc. in title so no suffix needed.
Normal tracks get 'audio' to filter piano covers and tutorials.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
'official audio' biased towards artist's main channel (wrong for remixes).
No suffix caused piano covers to rank top. 'audio' filters covers/tutorials
without over-specifying to official channels.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
For remixes and edits, 'official audio' biases YouTube search towards the
artist's official channel (wrong song) instead of the specific version.
Duration filter is now the quality check — the plain query is more accurate.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
--max-downloads 1 exits with code 101 which varies by yt-dlp version — caused
ffmpeg to never run, leaving untagged files. Use ytsearch1: + --match-filter instead:
filters top result by duration, exits 0 in all cases, falls back cleanly if no match.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
--playlist-items 1 in baseArgs caused filter to only check result #1 (filter useless).
--max-downloads 1 exits with code 101 which we treated as an error — ffmpeg never ran,
leaving untagged files (→ Unknown Artist/Album in Navidrome). Now code 101 resolves
successfully and filtered search uses commonArgs without --playlist-items.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When iTunes/Spotify provides a track duration, search 5 YouTube results
and keep only those within 60–150% of the expected length (filters remixes,
prayer videos, etc.). Falls back to ytsearch1: if no match is found.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace apk yt-dlp with pip3 install to get current version with ytmsearch support
- Split mainArtist on comma in downloadTrack so multi-artist tracks
always land in the primary artist folder (e.g. "Artist1, Artist2" → Artist1/)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Switch to YouTube Music search (ytmsearch3) as primary source with
fallback to ytsearch5 if no result found
- Add duration filter (±30s/+45s) and live-stream exclusion via
--match-filter when iTunes trackTimeMillis is available
- Send trackTimeMillis from frontend in download payload
- Pass durationMs through downloadTrack and downloadAlbumTracks
- Use first artist only (split on comma) for folder path on multi-artist tracks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
new Image() onload fires when bytes are downloaded but GPU decode of the
blurred layer hasn't happened yet on mobile. Double rAF defers the opacity
swap until after the browser has actually painted the new background.
will-change:opacity keeps the layers on dedicated compositor layers so
the blur doesn't need to be recomputed on every opacity transition.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Renders immediately with available data, then enriches with complete
metadata (genre, bitrate, path, plays, etc.) from getSong response.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_syncNpDirect is called 2-3x per skip from different call sites;
the crossfade now no-ops when the cover URL hasn't changed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two-layer opacity crossfade replaces the broken background-image transition.
New image loads on the hidden layer, then both opacity values swap smoothly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- renderQueue: in-place active-class update when queue unchanged — no DOM rebuild, no image reload
- updatePlayerUI: update img.src directly instead of replacing innerHTML
- _syncNpDirect: same img.src update for now-playing cover
- navigate('home'): always reloads home data so new albums appear without restart
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Collects tracks not matched in Navidrome search during playlist creation
and saves them as 'missing' in the notification — expandable list in the
bell panel labeled 'nicht in Navidrome gefunden'.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Bell icon in sidebar and mobile header with unread badge
- On download complete/error: saves notification to localStorage (survives reload)
- Playlist/album downloads: parses job log to list which tracks failed
- Panel toggles open with animation, click-outside closes it
- Each notification dismissable with X; "clear all" button
- Notifications capped at 100 entries in localStorage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- itunesProxy: drop 3-arg form (url, options, cb) — use 2-arg (url, cb) like lookupItunesTrackMeta
- _iGet: if proxy returns {results:[]} trust it (iTunes said no results); only fall back to direct Apple if proxy gave no response at all
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_iGet now tries backend proxy first (if backendUrl set), falls back to direct
Apple API URL so iTunes search/lookup/charts work regardless of backend config.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Apple now blocks direct browser requests from custom origins. Added backend proxy routes:
GET /api/itunes/search — iTunes search
GET /api/itunes/lookup — iTunes lookup by ID
GET /api/itunes/charts — Apple RSS most-played charts (albums/songs)
All itunesSearch/Lookup/TopCharts/TopSongs functions in api.js now route through backend.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- itunesSearch: use r.text() + JSON.parse so empty body (rate-limit) returns [] instead of throwing
- reduce Spotify artwork batch size 8→4 to avoid hammering iTunes and affecting global search
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CSS: target #player-artist-name span:hover and #np-artist-name span:hover so only the hovered artist name highlights, not all at once
- change artist separator from / to • in song items, player bar, now playing overlay, album header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _loadSpotifyTrackArtwork: rewritten to call itunesSearch() directly from browser (same as global search) instead of backend endpoint — same quality, works for all regions
- enriches track objects with collectionName, releaseDate, trackNumber, trackExplicitness so downloads get full metadata too
- 8-concurrent batch to avoid hammering iTunes
- adds onerror fallback to makeGlobalTrackItem img so broken URLs show 🎵 placeholder
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- player bar: fix onclick broken by JSON.stringify producing double-quoted strings inside double-quoted HTML attribute; use single-quoted JS args instead
- now playing overlay: np-artist-name now splits artists and each is individually clickable (closes NP then navigates to artist)
- remove wrapper onclick from both np-artist-name and player-artist-name in HTML
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- track-artwork endpoint: split artist on ,/•/ before iTunes search (same as download flow)
- track-artwork endpoint: fall back to existingArt when iTunes returns nothing
- frontend: render any artwork already in Spotify data immediately, don't wait for iTunes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- components.js: expand artist split regex to handle both ' / ' and '•' separators
- views.js: enable artist+album links in playlist song items (were noArtistLink:true)
- views.js: enable artist links in album detail song items
- views.js: album header artist is now split and renders as clickable links
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- sw.js: ns-v38 → ns-v39 forces PWA cache clear so previous fixes take effect
- components: split s.artist by ' / ' in makeSongItem — each part becomes its
own clickable link; noArtistLink turns all parts into plain text
- views: s_goArtist searches Navidrome by name when no artistId is available
(needed for secondary artists that only have the primary artistId in the song)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- nowplaying: add cancellation token to _loadNpLyrics to prevent stale async
calls overwriting newer results (synced→plain race condition)
- nowplaying: replace scrollIntoView with explicit container.scrollTo to prevent
page-level scroll on desktop (topbar shifting / gap at edge)
- backend: remove results[0] fallback in lookupItunesTrackMeta — only use
exact title+artist match to avoid embedding wrong artwork
- components/views: makeSongItem accepts noArtistLink/noAlbumLink opts;
album-detail disables both, playlist-detail disables artist link
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- overflow-x:hidden prevents horizontal scroll indicator from
scale(1.2) transform on active line
- scrollbar-width:none + ::-webkit-scrollbar hide vertical scrollbar
- mask-image gradient fades lyrics at top/bottom edges so text
doesn't clip abruptly against the container border
- Active line scale reduced from 1.2 to 1.08 (less overflow risk)
- padding-top removed from lyrics-view (mask handles visual spacing)
- lyrics-plain gets padding-top:24px for better visual breathing room
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Retry LRCLIB without album_name when exact match fails (404) —
album name mismatch was the most common cause of falling back to plain
- Capture plainLyrics from LRCLIB and prefer it over Navidrome plain
(LRCLIB plain text is usually higher quality)
- Use only first artist for LRCLIB lookup (multi-artist strings like
'Artist A / Artist B' cause 404)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
navidromeArtist: replace ', ' with ' / ' so Navidrome splits
multi-artist strings (e.g. 'Artist A, Artist B' → 'Artist A / Artist B')
with existing Tags.artist.Split = ["/"] config.
Playlist tracks: remove playlist cover URL as defaultArt in _spTrackItem
so that songs where iTunes lookup fails get no embedded artwork instead
of the wrong playlist cover art.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _restoreLastPlayed: set audio.src + call updateMediaSession so play
button works immediately and notification shows artwork on app open
- visibilitychange: re-register all MediaSession handlers after screen
unlock, fold/flip, or background return (fixes vanishing notification)
- Stall watchdog (3s interval): detects frozen audio in background and
calls play() to recover; also catches missed 'ended' events so
auto-advance works reliably when screen is off
- stalled/error listeners: attempt audio reload on network hiccup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Navidrome splits artist tags on '/' (Tags.artist.Split = ["/"]).
Using ' / ' between multiple artists (both from array format and from
Spotify's comma-separated subtitle) lets Navidrome register them as
separate artists without any config change.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Multi-artist strings like "Kløn, Renzyx" from Spotify's subtitle field
confuse the iTunes search API. Use only the first artist (split on comma)
as the search term, same as the playlist path already does.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Spotify's flat embed format provides no album, year or per-track artwork.
Now calls lookupItunesTrackMeta for every playlist track before downloading:
- Uses first artist of "Artist1, Artist2" subtitle for iTunes search query
(better match), keeps full string as the MP3 tag
- iTunes album/year/trackNum/artwork override the empty Spotify fields
- Falls back to Spotify data if iTunes returns nothing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds POST /api/spotify/track-artwork endpoint that takes an array of
{idx, trackName, artistName} and returns iTunes artwork URLs in batches
of 8 concurrent requests.
Frontend calls this after rendering the track list, then patches each
.gtrack-art element with the correct album cover as results arrive.
Also updates _spotifyResolved.tracks so downloads use the right artwork.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Spotify's playlist embed uses {title, subtitle, audioPreview} per track
instead of {name, artists, album}. Fixes:
- _spTrackItem: reads title as trackName, subtitle as artistName fallback,
audioPreview.url as previewUrl, isExplicit as boolean
- Route: reads entity.authors[0].name for playlist owner, entity.subtitle
for track artist fallback, entity.title fallback for entity.name
- Playlist tracks get playlist cover art as default artwork (no per-track art)
- Removes temporary debug logging
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _spTrackItem now unwraps GraphQL-style {data:{__typename,...}} wrappers
- _spAllArtists joins all featured artists (e.g. "Kløn, Renzyx")
- _spArtwork adds albumOfTrack path used by newer Spotify embed
- fetchSpotifyEmbed: added entity.content.items / entity.data fallback paths
- Track download now uses Spotify metadata directly (album, art, year,
trackNum) when provided — avoids iTunes returning wrong album for
multi-artist tracks
- Playlist trackList also tries entity.content.items and entity.tracks.items
- Frontend: redesigned preview card with green top accent, bigger art,
playlist checkbox styled as pill with Spotify color on check, album
button shows actual album name
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes the Spotify Web API token approach (blocked on non-Premium
accounts and from datacenter IPs). Now fetches open.spotify.com/embed
pages and parses __NEXT_DATA__ JSON — works without any auth from any IP.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- parseSpotifyUrl now handles intl-de/, intl-en/ and other locale prefixes
- getSpotifyToken uses full browser headers + sp_t cookie for the public endpoint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace Client Credentials flow with Spotify's public web-player token endpoint
(open.spotify.com/get_access_token) which works without any account or Premium.
Remove Spotify credential fields from UI entirely.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New 'Spotify Importer' page under Entdecken (sidebar + mobile nav)
- Backend: POST /api/spotify/resolve — resolves track/album/playlist URLs via Spotify API
- Backend: new 'playlist' download type accepting pre-resolved track list (no iTunes re-fetch)
- Frontend: paste Spotify link → preview with cover, type badge, track list
- Track: single download or full album via iTunes fallback
- Album: download all tracks with Spotify metadata
- Playlist: download all tracks + optional 'Als Navidrome-Playlist speichern' checkbox
- Navidrome playlist creation: searches library by title/artist after download, creates playlist
- Spotify Client ID + Secret stored in app settings (setup screen + settings modal)
- iTunes metadata tags kept for all downloaded files (existing pipeline unchanged)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Parse [offset:N] metadata from LRC files (was silently ignored, caused ~1s drift)
- Apply 200ms lookahead so active line highlights slightly before it's sung
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents other lines from shifting when active line grows.
Adds subtle glow effect on active line for karaoke feel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Hero height 220px mobile → 320px desktop (min-width: 769px)
- Larger artist name (46px) and more padding on desktop
- Overlay gradient now also darkens the right edge instead of fading to transparent
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 'Alle X Songs' button now navigates to a dedicated page instead of expanding inline
- Partial/not-downloaded albums now open an iTunes album detail page with track list,
preview buttons and download option
- Added page-artist-all-songs and page-itunes-album-detail to index.html
- Replaced _expandArtistAllSongs with loadArtistAllSongs + loadItunesAlbumDetail
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- loadArtistDetail now works for both Navidrome artists and iTunes-only
artists (id=null + itunesArtistId). loadItunesArtistResults from global
search now navigates to the same artist-detail page.
- Album sections: "In Library" (fully downloaded), "Unvollständig" (partial
— e.g. 2/12 tracks), "Noch nicht heruntergeladen" (iTunes-only). Partial
albums show X/Y badge and a direct download button on hover.
- iTunes albums now fetched via lookup endpoint (up to 200 albums) instead
of search (max 20), fixing the missing-albums problem.
- "Alle X Songs in Library" expand button fetches all songs across albums
and renders them grouped by album with play/context-menu support.
- Back button uses _navGoBack() for context-aware navigation.
- CSS: .section-count, .album-partial-badge, .card-dl-btn (hover reveal)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Songs from new releases (Apple RSS feed) have no collectionName, causing
the album tag to be omitted from embedded metadata -> Navidrome shows
[Unknown Album]. Backend now calls iTunes search to fill in album, year,
trackNum, totalTracks, discNum, genre before downloading.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename "Warteschlange" -> "Queue", tighten tab padding/letter-spacing
so all 3 tabs fit on narrow screens
- _loadNpLyrics now tries Navidrome getLyricsBySongId first (works once
.lrc sidecar is rescanned), then falls back to fetching synced LRC
directly from LRCLIB and parsing [mm:ss.xx] timestamps in the frontend
- Plain text shown as last resort if no synced lyrics found
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a Lyrics tab to the NP overlay using Navidrome's getLyricsBySongId
endpoint. Synced lyrics auto-scroll and highlight the current line as
the track plays; tapping a line seeks to that position. Falls back to
plain text for unsynced lyrics.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Backend:
- fetchLyrics() queries LRCLIB for plain + synced lyrics
- Synced lyrics saved as .lrc file (Navidrome reads these)
- Plain lyrics embedded via ffmpeg -metadata lyrics=
- ITUNESADVISORY=1 written for explicit tracks
- explicit flag passed through album/track download chain
- fetchItunesTracks + searchItunesAlbumTracks use country=us&explicit=Yes
Frontend:
- explicit:true sent in download payload from iTunes track data
- "E" badge shown on explicit tracks in global search track list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
iTunes search endpoint always returns cleaned versions.
Added itunesLookupArtistSongs() using /lookup?explicit=Yes which returns
the actual explicit tracks. Used in artist profile (global search) and
artist detail page (library). Album track lookup also gets explicit=Yes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
country=de silently filtered explicit content from iTunes results.
Switched to country=us&explicit=Yes so all songs appear regardless of explicit flag.
Removed wrong explicit-filtering code from views.js.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>