- Single-worker mode for album batches (sequential downloads enable clean source reuse)
- Browse API integration to list files in a source's directory
- Failed source tracking per-batch to avoid retrying broken sources
- Graduated quality scoring for upload speed, queue length, and free slots
- Track number fallback fix (uses Spotify track number instead of hardcoded 1)
- Duplicate completion guard to prevent double-decrement of active worker count
- Dedicated logging for source reuse and post-processing diagnostics
When downloading an album/EP, perform a single album-level search on Soulseek to find a user with the complete album folder before falling back to per-track search. This improves album completion rates and ensures consistent quality across tracks.
Merge split album entries (e.g. Navidrome assigning different IDs to the same album) by grouping on title+year instead of album ID, so track counts are correctly combined across all entries. Also add a minimum title similarity floor (0.6) to prevent a perfect artist match from carrying unrelated albums over the confidence threshold.
Add a /callback Flask route (port 8008) that handles Spotify OAuth token exchange, mirroring the existing HTTPServer handler on port 8888. This allows users behind a reverse proxy to point their Spotify redirect_uri at the main app.
ListenBrainzManager opens its own raw sqlite3 connection, bypassing MusicDatabase initialization. If it runs before MusicDatabase creates the tables, all queries fail with "no such table: listenbrainz_playlists". Added _ensure_tables() to ListenBrainzManager init that runs CREATE TABLE IF NOT EXISTS for both ListenBrainz tables — a no-op when they already exist, but creates them if MusicDatabase hasn't run yet.
- Expose suffix, bitRate, and path fields on NavidromeTrack from the Subsonic API response
- Add fallback in insert_or_update_media_track() to populate file_path and bitrate for Navidrome tracks, fixing the Quality Scanner
returning 0 results
- Increase ListenBrainz playlist cache limit from 4 to 25 per type
- Add sub-tab grouping in the Recommendations tab (Weekly Jams, Weekly Exploration, Top Discoveries, etc.)
Two fixes for iTunes integration:
1. iTunes failed tracks now properly added to wishlist
- Root cause: iTunes tracks had no 'album' field (unlike Spotify)
- Fix: Added album information to each track in get_album_tracks()
- Tracks now include: album id, name, images, release_date
2. Remove ' - Single' suffix from iTunes album names
- Root cause: iTunes API includes ' - Single' in collectionName
- Fix: Added _clean_album_name() helper method
- Strips ' - Single' and ' - EP' suffixes from all album names
- Applied to all 6 locations where collectionName is used
Both Spotify and iTunes sources now work identically for wishlist
auto-processing when tracks fail or are cancelled.
Root cause: When album downloads completed, the frontend immediately closed
the modal and called /api/playlists/cleanup_batch, which deleted the batch
from memory. The wishlist processing thread (submitted to executor) would
then try to access the batch and fail silently because it was already deleted.
This explains why:
- Wishlist auto-processing worked (different timing/3-second delay)
- Manual "Add to Wishlist" button worked (different code path, before downloads)
- Album modal failed tracks didn't get added (race condition)
The fix prevents batch cleanup until wishlist processing completes:
Backend (web_server.py):
1. Mark wishlist_processing_started=True when submitting processing job
2. Mark wishlist_processing_complete=True when processing finishes
3. Block cleanup endpoint if processing in progress (return 202)
Frontend (script.js):
4. Handle 202 response and retry cleanup after 2-second delay
This eliminates the race condition while maintaining async processing and
ensuring all failed/cancelled tracks are properly added to the wishlist.
Two fixes for iTunes integration:
1. iTunes failed tracks now properly added to wishlist
- Root cause: iTunes tracks had no 'album' field (unlike Spotify)
- Fix: Added album information to each track in get_album_tracks()
- Tracks now include: album id, name, images, release_date
2. Remove ' - Single' suffix from iTunes album names
- Root cause: iTunes API includes ' - Single' in collectionName
- Fix: Added _clean_album_name() helper method
- Strips ' - Single' and ' - EP' suffixes from all album names
- Applied to all 6 locations where collectionName is used
Both Spotify and iTunes sources now work identically for wishlist
auto-processing when tracks fail or are cancelled.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Root cause: When album downloads completed, the frontend immediately closed
the modal and called /api/playlists/cleanup_batch, which deleted the batch
from memory. The wishlist processing thread (submitted to executor) would
then try to access the batch and fail silently because it was already deleted.
This explains why:
- Wishlist auto-processing worked (different timing/3-second delay)
- Manual "Add to Wishlist" button worked (different code path, before downloads)
- Album modal failed tracks didn't get added (race condition)
The fix prevents batch cleanup until wishlist processing completes:
Backend (web_server.py):
1. Mark wishlist_processing_started=True when submitting processing job
2. Mark wishlist_processing_complete=True when processing finishes
3. Block cleanup endpoint if processing in progress (return 202)
Frontend (script.js):
4. Handle 202 response and retry cleanup after 2-second delay
This eliminates the race condition while maintaining async processing and
ensuring all failed/cancelled tracks are properly added to the wishlist.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Issue: Tracks marked as failed or cancelled in the download missing tracks modal
were not being automatically added to the wishlist on completion, despite manual
"Add to Wishlist" button working correctly. Modal completed silently without
mentioning wishlist.
Root cause: Line 549 in web_server.py was calling _on_download_completed() with
wrong parameters - missing batch_id. This caused the completion handler to look
up wrong batch, return early, and never process failed tracks to wishlist.
The bug affected downloads that complete via monitor detection (YouTube, Soulseek)
since the monitor loop calls this function when it detects completed transfers.
Fix: Added missing batch_id parameter to _on_download_completed() call on line 549.
Changed:
_on_download_completed(task_id, success=True)
To:
_on_download_completed(batch_id, task_id, success=True)
Tested: Automatic wishlist addition now works correctly for failed/cancelled tracks.
Frontend: treat discover_album_ playlists as Album Downloads to ensure proper folder structure for Recent Releases.
Backend: inject explicit album context into Wishlist download tasks to force Artist/Album/ grouping instead of flat file handling.
Updated startMissingTracksProcess in script.js to recognize discover_album_ IDs as album downloads. This ensures that albums downloaded from the Discover page (e.g., Recent Releases) are correctly organized into "Artist/Album" folders instead of being treated as flat playlists.
Library page was using album data from discography listing while Artists page used track.album from API. With iTunes, these could have different track counts, causing different album_type classifications.
- Updated handleAddToWishlist to use track.album data like Artists page does
- Added album_type copying to owned releases in merge_discography_data
Apple Music added as fallback metadata source if Spotify is not available. Spotify is always preferred if it is available with its richer data responses. Will easily swap from Spotify to Apple Music on the fly if you are rate limited by Spotify or worse, temp banned.
Apple Music and Spotify will now each have their own discovery pool for the discover page. Both will always be updated on every watchlist scan so long as Spotify is authorized, otherwise only Apple Music data is pulled.
**Known issues:**
Any artist image pulled while Apple Music is the primary source will only pull album art for that artist since they do not provide artist images :( This can, very rarely, lead to cases where the album image that is pulled could have another another artist displayed if it's some collab single, EP or something. Seen it happen once with an indie artist so it's possible.
Looking for a great api / website to parse easily specifically for artist names and a huge db to pull from.
- Added global currentMusicSourceName variable to track active source
- Updated discovery modal to show "Apple Music" when iTunes is active
- Replaced hardcoded "Spotify" in modal titles, headers, and descriptions
- Discovery modals now automatically reflect the correct music source (Spotify/Apple Music)
- Display "Apple Music" instead of "Spotify" in UI when iTunes is active source
- Enhanced connection test messages to indicate Spotify config/auth status
- Fixed similar artists requiring Spotify re-scan when Spotify becomes available
- Fixed hero slider buttons failing for iTunes-only artists
- Updated activity feed items to show correct source name dynamically
When Spotify is enabled after populating similar artists with only iTunes IDs, the freshness check now detects missing Spotify IDs and triggers a refetch. This fixes the Discover page not showing data when switching from iTunes-only mode.
- Add similar artists fetching to web UI scan loop
- Add database migration for UNIQUE constraint on similar_artists table - Add source-agnostic /api/discover/album endpoint for iTunes support
- Fix NOT NULL constraint on discovery_recent_albums blocking iTunes albums
- Add fallback to watchlist artists when no similar artists exist
- Add /api/discover/refresh and /api/discover/diagnose endpoints
- Add retry logic with exponential backoff for iTunes API calls
- Ensure cache_discovery_recent_albums runs even when pool population skips
- Fix platform detection to include is_listenbrainz_playlist check when generating fix buttons
- Update openDiscoveryFixModal to check both listenbrainzPlaylistStates and youtubePlaylistStates
- Update searchDiscoveryFix to detect discovery_source and use appropriate search API
- Add /api/itunes/search_tracks endpoint for manual track search when using iTunes source
- Update selectDiscoveryFixTrack state lookup to include ListenBrainz
Fix button now works correctly for all platforms (YouTube, ListenBrainz, Tidal, Beatport) with both Spotify
and iTunes discovery sources.
- Update _search_spotify_for_tidal_track to accept use_spotify and itunes_client parameters for dual-source
support
- Update _run_tidal_discovery_worker to check is_spotify_authenticated() and fall back to iTunes when Spotify
unavailable
- Update _run_beatport_discovery_worker with same iTunes fallback pattern
- Add discovery_source field to state and results for frontend awareness
- Activity messages now indicate which provider (SPOTIFY/ITUNES) completed discovery
All four discovery modals (YouTube, ListenBrainz, Tidal, Beatport) now support the dual-source architecture:
Spotify preferred, iTunes fallback.
Implement dual-source architecture where iTunes serves as always-available
primary source and Spotify as preferred source when authenticated.
- Make watchlist scans provider-aware (manual and auto paths)
- Update discovery pool population to process both sources
- Update recent albums caching for both sources
- Create source-specific curated playlists (Fresh Tape, Archives)
- Add on-the-fly iTunes ID resolution for similar artists
- Add iTunes ID check to similar artists freshness validation
- Fix sqlite3.Row compatibility in personalized playlists
- Fix iTunes ISO 8601 date format parsing
- Update API endpoints to serve source-appropriate data
This ensures the app remains fully functional if Spotify becomes
unavailable (rate limits, auth issues, bans) by seamlessly falling
back to iTunes data that has been building in parallel.
Implements lazy loading of artist images in search results, artist pages, and similar artist bubbles to improve performance and user experience. Updates the iTunes client to prefer explicit album versions and deduplicate albums accordingly. Adds a new API endpoint to fetch artist images, and updates frontend logic to asynchronously fetch and display images where missing.