Commit Graph

511 Commits

Author SHA1 Message Date
07844cc9c5 Add GC hints to prevent memory leaks from large byte arrays 2026-02-04 22:50:35 -05:00
1601b96800 Add memory monitoring endpoint 2026-02-04 22:45:11 -05:00
7db66067f4 Complete mark-for-deletion system and memory optimization 2026-02-04 22:41:08 -05:00
f44d8652b4 Improve favorite/unfavorite logic - copy from cache, avoid re-downloads 2026-02-04 22:34:11 -05:00
8fad6d8c4e Fix manual mapping detection in Active Playlists tab
Some checks failed
CI / build-and-test (push) Has been cancelled
2026-02-04 19:35:34 -05:00
d11b656b23 Add loading state to save mapping button and timeout handling 2026-02-04 19:24:02 -05:00
cf1428d678 Fix manual mapping race condition and add log gitignore 2026-02-04 19:17:48 -05:00
030937b196 Add error handling and better logging for playlist cache deserialization 2026-02-04 19:10:04 -05:00
f77281fd3d Fix GetJellyfinTrack: add UserId and verify Audio type for URL-based mapping 2026-02-04 19:04:12 -05:00
791a8b3fdb Fix Jellyfin search: add UserId and verify Audio type 2026-02-04 19:03:21 -05:00
7311bbc04a Add debug logging to GetPlaylists cache reading 2026-02-04 18:59:00 -05:00
696a2d56f2 Fix manual mappings: preserve on rematch + fix local/external count detection 2026-02-04 18:53:09 -05:00
5680b9c7c9 Fix GetPlaylists to use pre-built cache with manual mappings for accurate counts 2026-02-04 18:49:12 -05:00
1d31784ff8 Fix manual mapping: add immediate playlist rebuild and manual mapping priority in cache builder 2026-02-04 18:38:25 -05:00
10e58eced9 fix: add authentication to playlist cache pre-building
- PreBuildPlaylistItemsCacheAsync was failing with HTTP 401
- Background services don't have client headers for authentication
- Now manually creates X-Emby-Authorization header with API key
- Fixes 'Failed to fetch Jellyfin playlist items: HTTP 401' warning
- Playlist items cache now builds successfully after track matching

All 225 tests pass.
2026-02-04 18:23:11 -05:00
0937fcf163 fix: accurate playlist counting and three-color progress bars
- Fix playlist counting logic to use fuzzy matching (same as track view)
- Count local tracks by matching Jellyfin tracks to Spotify tracks
- Count external matched tracks from cache
- Count missing tracks (not found locally or externally)
- Progress bars now show three colors:
  * Green: Local tracks in Jellyfin
  * Orange: External matched tracks (SquidWTF/Deezer/Qobuz)
  * Grey: Missing tracks (not found anywhere)
- Add 'Missing' badge to tracks that couldn't be found
- Missing tracks can still be manually mapped
- Fixes incorrect counts like '28 matched • 1 missing' showing 29 external tracks

All 225 tests pass.
2026-02-04 17:49:10 -05:00
506f39d606 feat: instant UI update after manual track mapping
- Backend now returns mapped track details after saving
- Frontend updates track in-place without requiring page refresh
- Track status changes from External to Local immediately
- Map button is removed after successful mapping
- Playlist counts refresh in background
- Improved UX: no more 'refresh the playlist' message

All 225 tests pass.
2026-02-04 17:44:57 -05:00
7bb7c6a40e fix: manual mapping UI and [S] tag consistency
- Fix manual mapping track selection visual feedback (use accent color + background)
- Clear all playlist caches after manual mapping (matched, ordered, items)
- Strip [S] suffix from titles/artists/albums when searching for lyrics
- Add [S] suffix to artist and album names when song has [S] for consistency
- Ensures external tracks are clearly marked across all metadata fields

All 225 tests pass.
2026-02-04 17:31:56 -05:00
3403f7a8c9 fix: remove orphaned code causing JavaScript syntax error
Removed duplicate/orphaned lines after searchProvider() function that were
causing 'expected expression, got }' syntax error in admin UI.
2026-02-04 17:06:24 -05:00
3e5c57766b feat: pre-build playlist cache and make matching interval configurable
- Pre-build playlist items cache during track matching for instant serving
- Add PreBuildPlaylistItemsCacheAsync() to SpotifyTrackMatchingService
- Combines local Jellyfin tracks + external matched tracks in correct Spotify order
- Saves to both Redis and file cache for persistence across restarts
- Change matching interval from hardcoded 30 minutes to configurable (default: 24 hours)
- Add SPOTIFY_IMPORT_MATCHING_INTERVAL_HOURS environment variable
- Set to 0 to only run once on startup (manual trigger still works)
- Add endpoint usage files to .gitignore
- Update documentation in README and .env.example

Rationale: Spotify playlists like Discover Weekly update once per week,
so running every 24 hours is more than sufficient. Pre-building the cache
eliminates slow 'on the fly' playlist building.

All 225 tests pass.
2026-02-04 17:03:50 -05:00
24c6219189 Fix external track counting by checking matched tracks cache
- External tracks are injected on-the-fly, not stored in Jellyfin DB
- Check spotify:matched:ordered cache to get accurate external count
- Calculate external tracks as: total matched - local tracks
- This will properly show the two-color progress bar (green local + orange external)
- All 225 tests passing
2026-02-04 16:54:56 -05:00
ea21d5aa77 Add clickable search links and enhanced debug logging
- Made search query clickable - opens provider-specific search
- Added searchProvider() function with URLs for Deezer, Qobuz, SquidWTF
- Enhanced console logging to debug progress bar data
- Logs raw playlist data and calculated percentages
- Search link opens in new tab
2026-02-04 16:51:46 -05:00
ee84770397 Improve progress bar visibility and add debug logging
- Increased progress bar height from 8px to 12px for better visibility
- Changed colors to more vibrant shades (green #10b981, orange #f59e0b)
- Added console debug logging for playlist stats
- Shows local (green) and external (orange) track percentages side-by-side
2026-02-04 16:50:20 -05:00
7ccb660299 Add startup cache warming service
- Proactively loads all file caches into Redis on container startup
- Warms genre cache (30-day expiration)
- Warms playlist items cache (24-hour expiration)
- Logs warming progress and duration
- Ensures fast access immediately after restart
- Cleans up expired genre cache files automatically
- All 225 tests passing
2026-02-04 16:46:27 -05:00
0793c4614b Add file-based caching for MusicBrainz genres
- Dual-layer caching: Redis (fast) + file system (persistent)
- File cache survives container restarts
- 30-day cache expiration for both layers
- Negative result caching to avoid repeated failed lookups
- Safe file names using base64 encoding
- Automatic cache restoration to Redis on startup
- Cache directory: /app/cache/genres
2026-02-04 16:44:35 -05:00
bf02dc5a57 Add MusicBrainz genre enrichment and improve track counting
- Fixed external track detection (check for provider prefix in ID)
- Added genre support to MusicBrainz service (inc=genres+tags)
- Created GenreEnrichmentService for async genre lookup with caching
- Show provider name and search query for external tracks in admin UI
- Display search query that will be used for external track streaming
- Aggregate playlist genres from track genres
- All 225 tests passing
2026-02-04 16:43:17 -05:00
7938871556 Release 1.0.0 - Production ready
- Fixed AdminController export/import .env endpoints (moved from ConfigUpdateRequest class)
- Added ArtistId and AlbumId to integration test fixtures
- All 225 tests passing
- Version set to 1.0.0 (semantic versioning)
- MusicBrainz service ready for future ISRC-based matching (1.1.0)
- Import/export handles full .env configuration with timestamped backups
2026-02-04 16:33:58 -05:00
39f6893741 Add MusicBrainz API integration for metadata enrichment
- Added MusicBrainzSettings model with username/password authentication
- Created MusicBrainzService with ISRC lookup and recording search
- Implements proper rate limiting (1 req/sec) per MusicBrainz rules
- Added meaningful User-Agent header as required
- Registered service in Program.cs with configuration
- Added MusicBrainz section to appsettings.json
- Credentials stored in .env (MUSICBRAINZ_USERNAME/PASSWORD)

Next: Add to admin UI and implement import/export for .env
2026-02-04 16:23:16 -05:00
cd4fd702fc Match Jellyfin response structure exactly based on real API responses
Verified against real Jellyfin responses for tracks, albums, artists, and playlists:
- Reordered fields to match Jellyfin's exact field order
- Added missing fields: PremiereDate, HasLyrics, Container, ETag, etc.
- Fixed MediaType to 'Unknown' for albums/artists (not null)
- Fixed UserData.Key format to match Jellyfin patterns
- Added ParentLogoItemId, ParentBackdropItemId for proper hierarchy
- Fixed Genres/GenreItems to always be arrays (never null)
- Added complete MediaStream structure with all Jellyfin fields
- Playlists now have MediaType='Audio' to match real playlists
- All responses now perfectly mimic real Jellyfin structure
2026-02-04 16:17:45 -05:00
038c3a9614 Fix playlist count caching and make external tracks perfectly mimic Jellyfin responses
- Fixed UpdateSpotifyPlaylistCounts to properly handle file cache without skipping items
- Added Genres and GenreItems fields to all tracks (empty array if no genre)
- Added complete MediaStreams with audio codec info for external tracks
- Added missing MediaSource fields: IgnoreDts, IgnoreIndex, GenPtsInput, HasSegments
- Ensured Artists array never contains null values
- All external tracks now have proper genre arrays to match Jellyfin structure
2026-02-04 16:12:41 -05:00
6e966f9e0d Fix nullability warnings in SpotifyTrackMatchingService 2026-02-04 16:10:16 -05:00
b778b3d31e Fix MediaSources null array fields and add logging for artist albums
- Added MediaStreams, MediaAttachments, Formats as empty arrays instead of null
- Added RunTimeTicks field to MediaSources
- Added detailed logging to GetExternalChildItems to debug artist album issues
- This should fix 'type Null is not a subtype of type List<dynamic>' error
2026-02-04 16:04:04 -05:00
526a079368 Fix compilation errors and nullability warnings
- Fixed LrclibService.GetLyricsAsync call to use empty string and 0 for duration
- Fixed nullability warnings in SpotifyTrackMatchingService by explicitly casting to nullable tuple
2026-02-04 15:40:52 -05:00
7a7b884af2 Update playlist progress bar to show stacked blue/yellow segments
- Blue segment shows local tracks percentage
- Yellow segment shows external matched tracks percentage
- Bar fills to 100% when all tracks are matched
- Added tooltips showing track counts on hover
2026-02-04 15:37:07 -05:00
6ab5e44112 Fix apostrophe normalization syntax error - use Unicode escape sequences 2026-02-04 15:33:59 -05:00
7c92515723 Fix null boolean error and playlist count showing 0 after restart
- Added all required boolean fields to MediaSources (IsRemote, IsInfiniteStream, RequiresOpening, etc)
- UpdateSpotifyPlaylistCounts now loads from file cache if Redis is empty
- This fixes 'type Null is not a subtype of type bool' error in Finamp
- Playlist counts now show correctly even after container restart
2026-02-04 15:32:18 -05:00
8091d30602 Add parallel provider racing for searches and lyrics pre-fetching
- Created ParallelMetadataService to race all providers and return fastest result
- Search now uses parallel service when available for lower latency
- Pre-fetch LRCLib lyrics for top 3 search results in background
- FuzzyMatcher already handles apostrophe normalization (applied everywhere)
2026-02-04 15:29:56 -05:00
e7ff330625 Add logging for server-to-client WebSocket messages to debug remote control
Some checks failed
CI / build-and-test (push) Has been cancelled
2026-02-04 11:30:12 -05:00
aadda9b873 Fix apostrophe matching, add URL input for track mapping, improve search caching
Some checks failed
CI / build-and-test (push) Has been cancelled
- Enhanced FuzzyMatcher to normalize apostrophes (', ', ', etc) for better matching
- Added Redis-only caching for search results (15 min TTL)
- Added pattern-based cache deletion for search and image keys
- Added URL input field in Map to Local modal to paste Jellyfin track URLs
- Added /api/admin/jellyfin/track/{id} endpoint to fetch track details by ID
- Fixed duplicate cache key declaration in GetSpotifyPlaylistTracksOrderedAsync
- Updated cache clearing to include new spotify:playlist:items:* keys
2026-02-04 01:44:56 -05:00
8a84237f13 Fix MediaSources field appending to query string
- Properly parse and modify Fields parameter instead of appending to end
- Fixes 400 BadRequest errors from malformed URLs
- Now correctly adds MediaSources to Fields parameter value
2026-02-03 23:56:40 -05:00
e3a118e578 Fix bitrate for injected playlists by preserving raw Jellyfin items
CRITICAL FIX: Don't convert Jellyfin items to Song objects and back!

- Get raw Jellyfin playlist items with MediaSources field
- Reorder them according to Spotify positions
- Inject external tracks where needed
- Return raw items preserving ALL Jellyfin metadata (bitrate, etc)

This ensures local tracks in Spotify playlists show correct bitrate
just like regular Jellyfin playlists.
2026-02-03 23:41:29 -05:00
e17eee9bf3 Fix Map to Local button using data attributes instead of inline onclick
- Avoid JavaScript escaping issues with inline onclick handlers
- Use data attributes to store track info
- Add event listeners after DOM is created
- Prevents syntax errors with special characters in track names
2026-02-03 23:24:19 -05:00
4229924f61 Fix bitrate showing 0K for favorited songs and browse views
Some checks failed
CI / build-and-test (push) Has been cancelled
- Ensure MediaSources field is included when proxying browse requests
- Applies to /Users/{userId}/Items (favorites, recently played, etc)
- Jellyfin doesn't include MediaSources by default, must be requested
- Now bitrate info shows correctly in all browse contexts
2026-02-03 20:04:10 -05:00
a2a48f6ed9 Fix Map to Local button when artists array is empty
- Safely handle empty or undefined artists array
- Prevents 'expected expression, got }' JavaScript error
- Also fixed artists display to handle undefined arrays
2026-02-03 19:55:30 -05:00
c7785b6488 Fix playlist track count to show actual available tracks
- Changed ChildCount to reflect tracks actually in Jellyfin (local + external matched)
- Previously was incorrectly adding missing tracks to the count
- Now clients see the correct number of playable tracks
- Uses spotify:matched:ordered cache to count external matched tracks
2026-02-03 19:53:22 -05:00
af03a53af5 Enhanced playlist statistics in admin dashboard
Backend changes:
- Distinguish between local tracks (in Jellyfin library) and external tracks (downloaded)
- Track external matched vs external missing counts
- Calculate completion percentage for each playlist

Frontend changes:
- Show detailed breakdown: X local • Y matched • Z missing
- Display completion percentage with progress bar
- Color-coded stats (green=local, blue=matched, yellow=missing)
- Updated table headers for clarity
2026-02-03 19:49:32 -05:00
c1c2212b53 Fix undefined artists array causing syntax error 2026-02-03 19:46:48 -05:00
17560f0d34 Fix Map to Local button by properly escaping JavaScript parameters
- Use JSON.stringify instead of escapeHtml for onclick parameters
- Prevents JavaScript syntax errors when track names contain quotes
- Fixes button doing nothing when clicked
2026-02-03 19:46:31 -05:00
6ab314f603 Enhanced cache clearing to include all Spotify playlist Redis keys
- Added clearing of spotify:matched:* and spotify:matched:ordered:* keys
- This ensures bitrate metadata fix takes effect after cache clear
- Returns count of cleared Redis keys in response
2026-02-03 19:41:16 -05:00
64ac09becf Preserve MediaSources metadata for local tracks in playlists to show bitrate 2026-02-03 19:35:39 -05:00