Commit Graph

473 Commits

Author SHA1 Message Date
6ccc6a4a0d debug: add logging to verify Spotify IDs in cached playlist items
Some checks failed
CI / build-and-test (push) Has been cancelled
2026-02-06 14:15:49 -05:00
c54503f486 fix: Spotify lyrics validation and proactive prefetching
- 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
2026-02-06 13:04:40 -05:00
fbac81df64 feat: add 1-hour cache for playlist cover images 2026-02-06 12:31:56 -05:00
3a433e276c refactor: reorganize apis folder into steering and api-calls 2026-02-06 12:20:54 -05:00
0c14f4a760 chore: explicitly ignore documentation files in apis folder 2026-02-06 12:16:39 -05:00
28c4f8f5df Remove local Jellyfin manual mapping, keep only external mappings 2026-02-06 12:05:26 -05:00
a3830c54c4 Use Jellyfin item IDs for lyrics check instead of searching
- 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
2026-02-06 11:53:35 -05:00
4226ead53a Add file-based caching for admin UI and fix Jellyfin API usage
- 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
2026-02-06 11:48:01 -05:00
2155c4a9d5 Fix delete button for manual track mappings
- 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
2026-02-06 11:42:01 -05:00
a56b2c3ea3 Add delete button for manual track mappings
- 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
2026-02-06 11:36:51 -05:00
810247ba8c Add manual track mappings display to web UI
- 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
2026-02-06 11:18:48 -05:00
96814aa91b Add endpoint to view all manual track mappings
- 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
2026-02-06 11:16:09 -05:00
d52c0fc938 Add Spotify ID lookup for external tracks to enable Spotify lyrics
- 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
2026-02-06 11:14:55 -05:00
64eff088fa Remove incorrect healthcheck from spotify-lyrics service 2026-02-06 11:02:00 -05:00
ff6dfede87 Change Spotify lyrics API external port to 8365 2026-02-06 10:54:43 -05:00
d8696e254f Expose Spotify lyrics API on port 8080 for testing 2026-02-06 10:52:44 -05:00
261f20f378 Add Spotify lyrics test endpoint
- 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
2026-02-06 10:51:16 -05:00
ad5fea7d8e Fix LRCLib and SquidWTF error handling
Some checks failed
CI / build-and-test (push) Has been cancelled
- 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
2026-02-06 02:07:45 -05:00
8a3abdcbf7 Fix LyricsStartupValidator build errors
- Remove duplicate _httpClient field (use inherited one)
- Replace ValidationResult.Warning with ValidationResult.Failure
- Use PARTIAL status for partial failures
2026-02-06 01:54:32 -05:00
f103dac6c8 Add comprehensive lyrics startup validation with '22' test
- 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
2026-02-06 01:48:12 -05:00
7abc26c069 Fix album detail endpoint to use correct parameter
- 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)
2026-02-06 01:45:10 -05:00
a2e9021100 Fix artist detail parsing to handle missing artist property
- 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
2026-02-06 01:39:43 -05:00
5f22fb0a3b Integrate Spotify lyrics sidecar service
- 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
2026-02-06 01:24:49 -05:00
a3d1d81810 Add Spotify lyrics sidecar service and integrate with prefetch
- Add spotify-lyrics-api container to docker-compose
- Update SpotifyLyricsService to use sidecar API
- Prefetch now tries Spotify lyrics first (using track ID), then LRCLib
- Add SPOTIFY_LYRICS_API_URL setting
- Sidecar handles sp_dc cookie authentication automatically
2026-02-06 01:21:30 -05:00
2dd7020a61 Add logging to Spotify lyrics search for better debugging
Show when Spotify search is skipped, fails, or finds no matches
2026-02-06 00:59:23 -05:00
e36e685bee Fix lyrics cache key mismatch between prefetch and lookup
Use same artist format (comma-separated) in prefetch as LrclibService
to ensure cached lyrics are found during playback
2026-02-06 00:58:13 -05:00
7ff6dbbe7a Prioritize local Jellyfin lyrics over LRCLib in prefetch
Some checks failed
CI / build-and-test (push) Has been cancelled
- 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
2026-02-05 15:09:59 -05:00
e0dbd1d4fd feat: Add lyrics ID mapping system, fix playlist display, enhance track view
- 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)
2026-02-05 14:58:57 -05:00
328a6a0eea Add lyrics completion bar per playlist showing percentage of tracks with cached lyrics
Some checks failed
CI / build-and-test (push) Has been cancelled
2026-02-05 12:44:11 -05:00
9abb53de1a Fix search to use SquidWTF HiFi API with round-robin base URLs, capitalize provider names in UI, and widen tracks modal to 90% 2026-02-05 12:35:33 -05:00
349fb740a2 Fix scrobbling: track playing item in session and send proper PlaybackStopped data on cleanup 2026-02-05 11:56:26 -05:00
b604d61039 Adjust modal size to 75% width and 65% height, call PlaybackStopped when cleaning up sessions 2026-02-05 11:53:35 -05:00
3b8d83b43e Add lyrics prefetch endpoint and UI button: prefetch lyrics for individual playlists with progress feedback 2026-02-05 11:45:36 -05:00
8555b67a38 Fix external track streaming: normalize provider names to lowercase (squidwtf, deezer, qobuz) 2026-02-05 11:40:45 -05:00
629e95ac30 Improve logging: clarify search vs manual mappings, show manual mapping counts in final log 2026-02-05 11:38:26 -05:00
2153a24c86 Make modal wider (800px) and taller (90vh) to fit buttons side by side 2026-02-05 11:35:09 -05:00
1ddb3954f3 Add missing using statement for IMusicMetadataService 2026-02-05 11:27:22 -05:00
3319c9b21b Fix external mapping: add Map to External button for external tracks, fetch metadata from provider, set searchQuery for missing tracks 2026-02-05 11:23:01 -05:00
8966fb1fa2 Add lyrics prefetching for injected playlists with file cache
New LyricsPrefetchService automatically fetches lyrics for all tracks in
Spotify injected playlists. Lyrics are cached in Redis and persisted to
disk for fast loading on startup.

Features:
- Prefetches lyrics for all playlist tracks on startup (after 3min delay)
- Daily refresh to catch new tracks
- File cache at /app/cache/lyrics for persistence
- Cache warming on startup loads lyrics from disk into Redis
- Rate limited to 2 requests/second to be respectful to LRCLIB API
- Logs fetched/cached/missing counts per playlist

Benefits:
- Instant lyrics availability for playlist tracks
- Survives container restarts
- Reduces API calls during playback
- Better user experience with pre-loaded lyrics
2026-02-05 11:15:42 -05:00
3b24ef3e78 Fix: Fetch full metadata for manual external mappings
Manual external mappings now fetch complete track metadata from the
provider (SquidWTF) instead of using minimal Spotify metadata. This
ensures proper IDs, artist IDs, album IDs, cover art, and all metadata
needed for playback.

Also fixed admin UI to properly detect manual external mappings so
tracks show as 'External' instead of 'Missing'.

Changes:
- Fetch full Song metadata using GetSongAsync when manual mapping exists
- Fallback to minimal metadata if fetch fails
- Admin controller now checks isManualMapping flag to set correct status
- Tracks with manual external mappings now show proper provider badge
2026-02-05 11:13:26 -05:00
dbeb060d52 Fix: Manual external mappings now work correctly for playback
Two critical fixes:
1. External songs from manual mappings now get proper IDs (ext-{provider}-song-{id})
   - Previously had no ID, causing 'dummy' errors in Jellyfin
   - Now follows same format as auto-matched external tracks

2. Admin UI now correctly shows manual external mappings as available
   - Previously showed as 'Missing' even after mapping
   - Now properly detects manual external mappings and shows provider badge

This fixes the 400 Bad Request errors when trying to play manually mapped tracks.
2026-02-05 11:12:15 -05:00
2155a287a5 Add manual mapping indicators and search button for missing tracks
- Manual mappings now show a blue 'Manual' badge next to the track status
- Added search button (🔍) for missing tracks to help find them
- Backend now returns isManualMapping, manualMappingType, and manualMappingId
- Frontend displays manual mapping indicators for both local and external tracks
- Missing tracks now show a search link to help locate them on SquidWTF
2026-02-05 10:20:31 -05:00
cb57b406c1 Fix: Manual external mappings now properly included in playlist cache
The bug was in PreBuildPlaylistItemsCacheAsync - when a manual external
mapping was found, it was added to matchedTracks but the code used
'continue' to skip to the next track WITHOUT adding it to finalItems.

This meant external manual mappings were never included in the playlist
cache that gets served to clients.

The fix converts the external song to Jellyfin item format and adds it
to finalItems before continuing, ensuring manual external mappings are
properly included in the pre-built playlist cache.
2026-02-05 10:07:57 -05:00
e91833ebbb Fix variable name conflict and change cache logs to DEBUG level
- Fixed CS0136 error: renamed 'doc' to 'extDoc' in AdminController to avoid variable name conflict
- Changed all Redis cache logs (HIT/MISS/SET) to DEBUG level instead of suppressing
- This allows cache logs to be visible in docker logs but not as noisy at INFO level
2026-02-05 09:59:28 -05:00
2e1577eb5a Fix external mapping deserialization and suppress cache MISS logs
- Fixed RuntimeBinderException when processing external mappings by replacing dynamic with JsonDocument parsing
- Suppressed cache MISS logs for manual/external mappings (they're expected to be missing most of the time)
- Only log manual/external mapping HITs at INFO level, other cache operations at DEBUG level
- Applied fix to SpotifyTrackMatchingService (2 locations) and AdminController (2 locations)
2026-02-05 09:57:07 -05:00
7cb722c396 Fix HasValue method to handle JsonElement properly
- Changed parameter type from dynamic? to object? to avoid runtime binding issues
- Added check for JsonValueKind.Undefined in addition to Null
- Fixes crash when checking external mappings that return JsonElement
- Applied fix to both AdminController and SpotifyTrackMatchingService
2026-02-05 09:40:39 -05:00
9dcaddb2db Make manual mappings permanent and persist to file
- Manual mappings now have NO expiration (permanent in Redis)
- Save manual mappings to /app/cache/mappings/*.json files
- Load manual mappings on startup via CacheWarmingService
- Manual mappings are first-order and survive restarts/cache clears
- User decisions are now truly permanent
2026-02-05 09:33:37 -05:00
5766cf9f62 Delete file caches when manual mappings are created
- When mapping a track to local or external, delete both Redis and file caches
- This forces the matched tracks cache to rebuild with the new mapping
- Ensures manual mappings are immediately reflected in playlists
2026-02-05 09:31:07 -05:00
a12d5ea3c9 Fix excessive track matching and reduce HTTP logging noise
- Added 5-minute cooldown between matching runs to prevent spam
- Improved cache checking to skip unnecessary matching
- Persist matched tracks cache to file for faster restarts
- Cache warming service now loads matched tracks on startup
- Suppress verbose HTTP client logs (LogicalHandler/ClientHandler)
- Only run matching when cache is missing or manual mappings added
2026-02-05 09:30:00 -05:00
25bbf45cbb Fix memory leak in ActiveDownloads dictionary
- Changed ActiveDownloads from Dictionary to ConcurrentDictionary for thread safety
- Added automatic cleanup of completed downloads after 5 minutes
- Added automatic cleanup of failed downloads after 2 minutes
- This fixes the 929MB -> 10MB memory issue where downloads were never removed from tracking
2026-02-05 09:19:32 -05:00