- Fix external track detection in progress bar (check for external provider names in ProviderIds)
- Add missing tracks section at bottom of Active Playlists tab
- Shows all unmatched tracks across all playlists
- Includes Map to Local and Map to External buttons for each missing track
- Auto-refreshes with other playlist data
- Strip decorators FIRST (feat, remaster, explicit, etc)
- Substring matching SECOND (cheap, high-precision)
- Levenshtein distance THIRD (expensive, fuzzy)
- Greedy assignment LAST (optimal global matching)
- Lower threshold to 40 (was 50-60) for max coverage
- Accept artist priority matches (artist 70+, title 30+)
- Handles cases like 'luther' → 'luther (feat. sza)'
- Handles cases like 'a' → 'a-blah' with same artist
- Prevents duplicate assignments across tracks
- Inject Spotify ID into ProviderIds for manually mapped local tracks
- Also add Spotify ID to fuzzy-matched local tracks
- Enables Spotify Lyrics API to work for local Jellyfin tracks
- Fallback to Spotify lyrics when local track has no embedded lyrics
- Allow mapping Spotify tracks to local Jellyfin tracks via JellyfinId
- Supports both local (Jellyfin) and external (provider) manual mappings
- Local mappings take priority over fuzzy matching
- Helps when automatic matching fails for tracks already in Jellyfin library
- Lower matching threshold from 60 to 50 for more lenient matching
- Add fallback to trust provider's top result when artist matches well (>=70)
- Helps match tracks with parentheses, brackets, and stylized titles like 'PiLlOwT4lK'
- Provider search already does fuzzy matching, so trust it when artist is correct
- Implemented semaphore-based rate limiter (10 requests per minute)
- Odesli API allows 10 requests per 60 seconds
- Rate limiter ensures 1 request per 6 seconds maximum
- Prevents API rate limit violations
- Cache still used first (30 day TTL) to minimize API calls
- Removed ALL artist deduplication from search results
- Show both local and external artists with same name (e.g., Taylor Swift + Taylor Swift [S])
- Users can now browse external artist albums not in local library
- Added placeholder image support for missing cover art
- Placeholder served for both Jellyfin and external providers when image is null
- Added logging to SquidWTF artist search to show actual URLs
- Removed temporary documentation files from repo
- TIDAL image URLs already correctly implemented with UUID splitting
Changes:
- SearchItems: No deduplication, sort by relevance score
- SearchHints: No deduplication, show all matches
- GetArtists: No deduplication, show all matches
- GetImage: Returns placeholder.png when image unavailable
- Added GetPlaceholderImageAsync() helper method
- Artists with same name now appear separately (local + external [S])
- Fixed deduplication to keep one local AND one external per name
- Added logging to SquidWTF artist search to show actual URLs
- External artists get [S] suffix to distinguish from local
- Allows users to browse external artist albums not in local library
- TIDAL image URLs already correctly implemented with UUID splitting
- External tracks now create Jellyfin sessions on playback start
- Sessions maintained via WebSocket connections to Jellyfin
- Session activity updated during progress reports
- Sessions auto-cleanup after 50s grace period when playback stops
- Clients playing external tracks now appear in Jellyfin dashboard
- Added comprehensive testing documentation
- Only attempt Spotify lyrics for tracks with valid Spotify IDs (22 chars, no 'local' or ':')
- Add Spotify IDs to external matched tracks in playlists for lyrics support
- Proactively fetch and cache lyrics when playback starts (background task)
- Fix pre-existing SubSonicController bug (missing _cache field)
- Lyrics now ready instantly when requested by client
- Lyrics prefetch now uses playlist items cache which has Jellyfin item IDs
- Directly queries /Audio/{itemId}/Lyrics endpoint (no search needed)
- Eliminates all 401 errors and 'no client headers' warnings
- Priority order: 1) Local Jellyfin lyrics, 2) Spotify lyrics API, 3) LRCLib
- Much more efficient - no fuzzy searching required
- Only searches by artist/title as fallback if item ID not available
- All 225 tests passing
- Added 5-minute file cache for playlist summary to speed up admin UI loads
- Added refresh parameter to force cache bypass
- Invalidate cache when playlists are refreshed or tracks are matched
- Fixed incorrect use of anyProviderIdEquals (Emby API) in Jellyfin
- Now searches Jellyfin by artist and title instead of provider ID
- Fixes 401 errors and 'no client headers' warnings in lyrics prefetch
- All 225 tests passing
- Use data attributes instead of inline onclick to avoid quote escaping issues
- Add event listeners after rendering the table
- Fixes issue where Remove button didn't work due to escaped quotes in onclick attribute
- Added DELETE /api/admin/mappings/tracks endpoint
- Removes mapping from JSON file and Redis cache
- Deletes file if it becomes empty after removal
- Added 'Remove' button to each mapping in web UI
- Enhanced confirm dialog explaining consequences for both local and external mappings
- Supports removing both Jellyfin (local) and external provider mappings
- Allows phasing out local mappings in favor of Spotify Import plugin
- Shows all manual mappings in Active Playlists tab
- Displays summary counts (total, jellyfin, external)
- Table shows playlist, Spotify ID, type, target, and creation date
- Color-coded badges for jellyfin vs external mappings
- Auto-refreshes every 30 seconds
- Helps review mappings before phasing out local ones
- GET /api/admin/mappings/tracks returns all manual mappings
- Shows both Jellyfin (local) and external provider mappings
- Groups by playlist and includes creation timestamps
- Returns counts for jellyfin vs external mappings
- External tracks from playlists now look up their Spotify ID from matched tracks cache
- Enables Spotify lyrics API to work for SquidWTF/Deezer/Qobuz tracks
- Searches through all playlist matched tracks to find the Spotify ID
- Falls back to LRCLIB if no Spotify ID found or lyrics unavailable
- Add GET /api/admin/lyrics/spotify/test endpoint
- Accepts trackId query parameter (Spotify track ID)
- Returns lyrics in both JSON and LRC format
- Useful for testing Spotify lyrics API integration
- Handle nullable duration in LRCLib API responses
- Validate input parameters before making LRCLib requests
- Change SquidWTF artist warning to debug level (expected behavior)
- Prevent JSON deserialization errors when duration is null
- Prevent 400 Bad Request errors from empty track names
- Remove duplicate _httpClient field (use inherited one)
- Replace ValidationResult.Warning with ValidationResult.Failure
- Use PARTIAL status for partial failures
- Revert album endpoint back to ?id= (correct parameter)
- Update SquidWTF validator to test '22' by Taylor Swift
- Create LyricsStartupValidator testing all lyrics services:
* LRCLib API
* Spotify Lyrics Sidecar (docker container)
* Spotify API configuration
- Test song: '22' by Taylor Swift (Spotify ID: 3yII7UwgLF6K5zW3xad3MP)
- Register lyrics validator in startup orchestrator
- Change album endpoint from ?id= to ?f= to match API spec
- Album search parsing is correct (data.albums.items)
- Album detail parsing is correct (data with items array)
- Add TryGetProperty check for artist field in albums response
- Log response keys when artist data not found for debugging
- Improves error handling when API returns albums without artist field
- Add spotify-lyrics-api sidecar container to docker-compose
- Replace direct Spotify API lyrics code with sidecar API calls
- Update SpotifyLyricsService to use sidecar exclusively
- Add LyricsApiUrl setting to SpotifyApiSettings
- Update prefetch to try Spotify lyrics first, then LRCLib
- Remove unused direct API authentication and parsing code
- Check for embedded lyrics in local Jellyfin tracks before fetching from LRCLib
- Remove previously cached LRCLib lyrics when local lyrics are found
- Prevents unnecessary API calls and respects user's embedded lyrics
- Tracks with local lyrics are counted as 'cached' in prefetch stats
- Add complete lyrics ID mapping system with Redis cache, file persistence, and cache warming
- Manual lyrics mappings checked FIRST before automatic search in LrclibService
- Add lyrics status badge to track view (blue badge shows when lyrics are cached)
- Enhance search links to show 'Search: Track Title - Artist Name'
- Fix Active Playlists tab to read from .env file directly (shows all 18 playlists now)
- Add Map Lyrics ID button to every track with modal for entering lrclib.net IDs
- Add POST /api/admin/lyrics/map and GET /api/admin/lyrics/mappings endpoints
- Lyrics mappings stored in /app/cache/lyrics_mappings.json with no expiration
- Cache warming loads lyrics mappings on startup
- All mappings follow same pattern as track mappings (Redis + file + warming)