Commit Graph

478 Commits

Author SHA1 Message Date
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
a0bbb7cd4c Integrate WebSocket proxy with session manager to cleanup sessions on client disconnect 2026-02-03 19:10:02 -05:00
4bd478e85c Add HLS playlist (.m3u8, .ts) support to binary proxy handler 2026-02-03 19:04:24 -05:00
f7a88791e8 Fix GetImage endpoint to proxy images instead of redirecting 2026-02-03 19:02:42 -05:00
9f8b3d65fb Fix HttpClient.SendAsync call for image proxy 2026-02-03 18:59:58 -05:00
1a1f9e136f Fix image proxy to handle binary data instead of JSON parsing 2026-02-03 18:59:03 -05:00
48f69b766d Add manual track mapping feature
- Add 'Map to Local' button for external tracks in playlist viewer
- Search Jellyfin library to find local tracks
- Save manual mappings (Spotify ID → Jellyfin ID) in cache
- Manual mappings take priority over fuzzy matching
- Clear playlist cache when mapping is saved to force refresh
- UI shows which tracks are manually mapped in logs
2026-02-03 18:57:19 -05:00
d619881b8e Fix: Correct matching logic - Jellyfin tracks first, then fill gaps
CRITICAL FIX: Changed matching strategy completely
- Step 1: Match ALL Jellyfin tracks to Spotify positions (fuzzy 70%)
- Step 2: Build playlist in Spotify order using matched Jellyfin tracks
- Step 3: Fill remaining gaps with external tracks (cached or on-demand)
- Step 4: Add any unmatched Jellyfin tracks at the end

This ensures Jellyfin tracks are ALWAYS used when they match, preventing
external tracks from being used when local versions exist.
2026-02-03 18:45:07 -05:00
dccdb7b744 Fix: Add on-demand external track search when cache is empty
- Search external providers in real-time if no cached match exists
- Use fuzzy matching (60% threshold) for external tracks
- Ensures external tracks are always available even without pre-matching
- Local tracks still prioritized first (70% threshold)
2026-02-03 18:39:33 -05:00
f240423822 Fix: Prioritize LOCAL tracks in Spotify playlist injection - match by name only
- Remove Spotify ID/ISRC matching (Jellyfin plugin doesn't add these)
- Use ONLY fuzzy name matching (title + artist, 70% threshold)
- LOCAL tracks ALWAYS used first before external providers
- Include ALL tracks from Jellyfin playlist (even if not in Spotify)
- Prevent duplicate track usage with HashSet tracking
- AdminController also updated to match by name for Local/External badges
- Better logging with emojis for debugging
2026-02-03 18:36:33 -05:00
1492778b14 UI fixes: Match per playlist, Match All button, local/external labels, preserve tab on reload 2026-02-03 18:27:29 -05:00
08af650d6c Add fuzzy name matching as fallback for local tracks + better error logging
- Add fuzzy matching by title+artist as fallback (like Jellyfin Spotify Import plugin)
- Add clear error messages when JELLYFIN_USER_ID is not configured
- Add emoji logging for easier debugging (🔍 📌  )
- Check HTTP status code when fetching playlist items
- This should fix the issue where all tracks show [S] even when they exist locally
2026-02-03 18:23:39 -05:00
c44be48eb9 Fix: Add UserId parameter for Jellyfin playlist operations + UI improvements
- CRITICAL FIX: Add UserId parameter to all Jellyfin playlist item fetches (fixes 400 BadRequest errors)
- Fix GetPlaylists to correctly count local/missing tracks
- Fix GetSpotifyPlaylistTracksOrderedAsync to find local tracks (was serving external tracks for everything)
- Fix SpotifyTrackMatchingService to skip tracks already in Jellyfin
- Add detailed debug logging for track matching (LOCAL by ISRC/Spotify ID, EXTERNAL match, NO MATCH)
- Add 'Match Tracks' button for individual playlists (not all playlists)
- Add 'Match All Tracks' button for matching all playlists at once
- Add JELLYFIN_USER_ID to web UI configuration tab for easy setup
- Add /api/admin/playlists/match-all endpoint

This fixes the issue where local tracks weren't being used - the system was downloading
from SquidWTF even when files existed locally in Jellyfin.
2026-02-03 18:14:13 -05:00
b16d16c9c9 Fix: Register SpotifyTrackMatchingService as singleton for DI
- Register as singleton first, then as hosted service
- Allows AdminController to inject and trigger matching manually
- Added better logging for Jellyfin playlist fetch failures
- Logs when JellyfinId is missing or API calls fail
- Helps debug '0 local / 0 missing' issue
2026-02-03 17:51:55 -05:00
e51d569d79 Show actual local/missing track counts in Active Playlists
- Fetches current Jellyfin playlist track count
- Compares with Spotify playlist total to calculate missing tracks
- Shows: '25 local / 5 missing' instead of just track count
- Local = tracks currently in Jellyfin playlist
- Missing = Spotify tracks not yet in Jellyfin (need to be matched)
- Helps users see which playlists need track matching
2026-02-03 17:48:49 -05:00
363c9e6f1b Add Match Tracks button and Local/External column to Active Playlists
- Added 'Match Tracks' button to trigger matching for specific playlist
- Added Local/External column (shows '-' for now, will populate after matching)
- New endpoint: POST /api/admin/playlists/{name}/match
- Injects SpotifyTrackMatchingService into AdminController
- UI shows: Name | Spotify ID | Total | Local/External | Cache Age | Actions
- Allows users to manually trigger matching without waiting 30 minutes
2026-02-03 17:47:44 -05:00
f813fe9eeb Fix: Match local Jellyfin tracks by ISRC instead of Spotify ID
- Local Jellyfin tracks don't have Spotify IDs (only plugin-added tracks do)
- Now matches by ISRC first (most reliable), then falls back to Spotify ID
- Builds dictionaries for fast lookup: existingBySpotifyId and existingByIsrc
- Prioritizes local tracks over external matches
- Logs: 'X tracks (Y with Spotify IDs, Z with ISRCs)'
- This fixes all tracks showing [S] - now uses local files when available
2026-02-03 17:45:53 -05:00
ef0ee65160 Optimize UI: Update playlist status without refetching all playlists
- After linking/unlinking, update UI state directly instead of refetching
- Avoids 7+ second delay from fetching track stats for all playlists
- Added data-playlist-id attribute to table rows for easy lookup
- Only refreshes Active Playlists tab (fast operation)
- Much better UX when linking multiple playlists
2026-02-03 17:40:47 -05:00
b3bfa16b93 Fix RemovePlaylist to use new 4-field config format
- Include JellyfinId when serializing playlists after removal
- Ensures config format stays consistent: [Name,SpotifyId,JellyfinId,position]
- Fixes issue where removed playlists would stay in the list
2026-02-03 17:35:54 -05:00
aa9b5c874d Skip matching tracks already in Jellyfin playlist
- Fetch existing tracks from Jellyfin playlist before matching
- Extract Spotify IDs from ProviderIds to identify already-matched tracks
- Only search for tracks not already in Jellyfin
- Logs: 'Matching X/Y tracks (skipping Z already in Jellyfin)'
- Significantly reduces matching time for playlists with local content
- Example: If 20/50 tracks exist locally, only searches for 30 tracks
2026-02-03 17:29:28 -05:00
e3546425eb Optimize track matching with parallel batch processing
- Process tracks in batches of 11 (matches SquidWTF provider count)
- Each batch runs 11 parallel searches (one per provider)
- Wait 150ms between batches (not between individual tracks)
- This is ~11x faster: 50 tracks now takes ~1 second instead of ~7.5 seconds
- Round-robin in SquidWTF ensures each parallel request hits a different provider
- Maintains rate limiting while maximizing throughput
2026-02-03 17:20:05 -05:00
5646aa07ea Fix playlist injection: Store Jellyfin playlist ID in config
- Added JellyfinId field to SpotifyPlaylistConfig model
- Updated config format: [[Name,SpotifyId,JellyfinId,position],...]
- LinkPlaylist now stores both Jellyfin and Spotify playlist IDs
- IsSpotifyPlaylist() now checks by Jellyfin playlist ID (not Spotify ID)
- GetJellyfinPlaylists shows linked status by checking JellyfinId
- Updated Program.cs to parse new 4-field format
- Backward compatible: JellyfinId defaults to empty string for old configs

This fixes the issue where playlists weren't being recognized as configured
because the code was checking Jellyfin playlist IDs against Spotify IDs.
2026-02-03 17:18:08 -05:00
7cdf7e3806 Switch to Spotify GraphQL API to match Jellyfin plugin approach
- Replace REST API calls with GraphQL API (api-partner.spotify.com/pathfinder/v1/query)
- Use same persisted query approach as Jellyfin Spotify Import plugin
- GraphQL API is more reliable and has better rate limits
- Fetch playlists with operationName: fetchPlaylist
- Parse GraphQL response structure (playlistV2, itemV2, etc)
- This should eliminate TooManyRequests errors
2026-02-03 17:10:38 -05:00
fe9c1e17be Add rate limiting delays to prevent Spotify 429 errors
- Increase delay between playlist fetches from 1s to 3s
- Only delay between playlists, not after the last one
- Add debug logging for rate limit delays
- Spotify is very aggressive with rate limiting on their API
2026-02-03 17:05:34 -05:00
63324def62 Fix Jellyfin BadRequest errors when fetching playlist items
- Add UserId parameter to playlist items API call (required by Jellyfin)
- Auto-fetch first user if no user ID configured
- Simplify Fields parameter to only request Path
- This fixes the 400 BadRequest errors when loading Jellyfin playlists tab
2026-02-03 17:03:04 -05:00
ff72ae2395 Fix playlist linking to save all playlists and add restart banner
- Read playlists from .env file instead of stale in-memory config
- This fixes issue where linking multiple playlists only saved the last one
- Add prominent restart banner at top when config changes
- Update UI immediately after linking/unlinking playlists
- Banner shows 'Restart Now' button and dismissible option
- All config changes now show the restart banner instead of toast messages
- GetJellyfinPlaylists now reads from .env for accurate linked status
2026-02-03 17:00:23 -05:00
1a3134083b Fix remaining pragma warnings in debug console output 2026-02-03 16:55:32 -05:00
bd64f437cd Fix web UI infinite loop and improve cookie age tracking
- Add cookieDateInitialized flag to prevent infinite init-cookie-date calls
- Auto-initialize cookie date when cookie exists but date not tracked
- Improve local vs external track detection in Jellyfin playlists
- Support multiple Spotify playlist ID formats (ID, URI, URL)
- Fix pragma warnings in legacy playlist parsing code
- Simplify GetPlaylistTrackStats to check Path property for local tracks
2026-02-03 16:54:40 -05:00
5606706dc8 Fix web UI config persistence and cookie age tracking
- Add SPOTIFY_API_SESSION_COOKIE_SET_DATE to docker-compose.yml env mapping
- Mount .env file in container for web UI to update
- Add SessionCookieSetDate loading in Program.cs
- Improve .env update logic with better error handling and logging
- Auto-initialize cookie date when cookie exists but date not set
- Simplify local vs external track detection in Jellyfin playlists
- Enhanced Spotify playlist ID parsing (supports ID, URI, and URL formats)
- Better UI clarity: renamed tabs to 'Link Playlists' and 'Active Playlists'
2026-02-03 16:50:19 -05:00
79a9e4063d Remove unnecessary /me API call - not needed for sp_dc auth 2026-02-03 16:27:19 -05:00
c33c85455f Fix JS syntax error: escape quotes in onclick handlers for playlist names 2026-02-03 16:19:13 -05:00
5af2bb1113 Remove hardcoded playlists from appsettings - use web UI instead 2026-02-03 16:14:28 -05:00
2c1297ebec Fix playlist config: dedupe entries, use Spotify playlist ID for lookup 2026-02-03 16:12:25 -05:00
df7f11e769 Add local/external track columns to Jellyfin playlists, remove libraries filter 2026-02-03 16:08:49 -05:00