Commit graph

300 commits

Author SHA1 Message Date
Broque Thomas
d9efcbdf99 feat: AcoustID audio verification, MusicBrainz enrichment UI, v1.5
Add optional post-download audio fingerprint verification using AcoustID.
  Downloads are verified against expected track/artist using fuzzy string
  matching on AcoustID results. Mismatched files are quarantined and
  automatically added to the wishlist for retry.

  - AcoustID verification with title/artist fuzzy matching (not MBID comparison)
  - Quarantine system with JSON metadata sidecars for failed verifications
  - fpcalc binary auto-download for Windows, macOS (universal), and Linux
  - MusicBrainz enrichment worker with live status UI and track badges
  - Settings page AcoustID section with real-fingerprint connection test
  - Source reuse for album downloads to keep tracks from same Soulseek user
  - Enhanced search queries for better track matching
  - Bug fixes: wishlist tracking, album splitting, regex & handling, log rotation
2026-02-05 16:33:07 -08:00
Broque Thomas
fa808e5bba include acoustID in settings page. foundation for verification flow 2026-02-03 16:57:53 -08:00
Broque Thomas
cee5590718 feat(ui): add MusicBrainz enrichment status UI with real-time monitoring
MusicBrainz library enrichment with real-time
status monitoring and manual control.
Features:
- Status icon button in dashboard header with glassmorphic design
- Animated loading spinner during active enrichment
- Hover tooltip showing:
  - Worker status (Running/Paused/Idle)
  - Currently processing item
  - Artist matching progress with percentage
- Click-to-toggle pause/resume functionality
- Auto-polling every 2 seconds for live updates
Backend Changes:
- Added GET /api/musicbrainz/status endpoint
- Added POST /api/musicbrainz/pause endpoint
- Added POST /api/musicbrainz/resume endpoint
- Worker tracks current_item for UI display
- get_stats() returns enhanced status data
Frontend Changes:
- New MusicBrainz button component with tooltip
- Premium CSS styling with animations
- JavaScript polling and state management
- Positioned tooltip below button with centered arrow
Files Modified:
- web_server.py: API endpoints and worker initialization
- core/musicbrainz_worker.py: current_item tracking
- webui/index.html: Button and tooltip structure
- webui/static/style.css: Complete styling (240 lines)
- webui/static/script.js: Polling and interaction logic (115 lines)
2026-02-03 15:20:04 -08:00
Broque Thomas
6d9ee9f9c1 fix issue where worker would retry the same host if failed. 2026-02-02 13:49:56 -08:00
Broque Thomas
2d866cf0dd Fix failed tracks not being added to wishlist. 2026-02-02 13:42:54 -08:00
Broque Thomas
863f04c5ae Fix album splitting in media servers for multi-source downloads
- Files downloaded from different soulseek sources carried conflicting MusicBrainz Release IDs and disc number tags that were never overwritten during metadata enhancement, causing Navidrome/Plex to split a single album into multiple entries even when folder structure and album name were identical
  - disc_number is now written to file metadata (sourced from Spotify/iTunes API), overwriting stale tags from soulseek sources
  - MusicBrainz IDs are now stripped during metadata enhancement so media servers group by album name + artist instead of mismatched release IDs
  - The verification worker now applies _resolve_album_group() for album name consistency with the stream processor path
  - Fixed source reuse retry logic: used_sources is no longer reset between retries, so a source that errors during transfer is skipped on retry and the system falls through to a normal search instead of retrying the same failing source 3 times
2026-02-02 10:00:56 -08:00
Broque Thomas
af7279a3ea add 'rejected' to the list of errored states from slskd for album level source. 2026-02-01 16:43:49 -08:00
Broque Thomas
01ac392307 Fix post-processing race condition between Stream Processor and Verification Worker
Added per-context-key threading lock in _post_process_matched_download to serialize access when both the Stream    Processor and Verification Worker attempt to process the same downloaded file. Previously, both threads would race to   move the source file via shutil.move, causing FileNotFoundError for the loser. The lock ensures the first thread    completes the move before the second enters, where existing protection checks detect the file was already transferred    and return early.
2026-02-01 16:15:22 -08:00
Broque Thomas
4581603005 app log rotation with capped file size 2026-02-01 08:38:38 -08:00
Broque Thomas
60fdf4c82c Fix issue on windows where source files were removed before transfer. 2026-02-01 08:35:05 -08:00
Broque Thomas
35e65b2106 When downloading albums/EPs from Soulseek, after the first track downloads successfully, browse the source's folder and reuse it for subsequent tracks. This keeps entire albums coming from one source instead of scattering across many users.
- 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
2026-02-01 00:42:39 -08:00
Broque Thomas
7d8bb2b88a Revert "Add album-level Soulseek search and fix unbounded log growth"
This reverts commit 258fd7a8ae.
2026-01-31 18:42:37 -08:00
Broque Thomas
bbac51412d Revert "album level search for wishlist albums/eps"
This reverts commit 28ca3f5ce5.
2026-01-31 18:42:17 -08:00
Broque Thomas
28ca3f5ce5 album level search for wishlist albums/eps 2026-01-31 11:21:35 -08:00
Broque Thomas
258fd7a8ae Add album-level Soulseek search and fix unbounded log growth
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.
2026-01-30 12:46:52 -08:00
Broque Thomas
578d29f774 Add Spotify OAuth callback route to Flask app for reverse proxy support
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.
2026-01-29 13:37:22 -08:00
Broque Thomas
681d4b3cd2 Fixed an issue where the wishlist wouldn't open if null value is detected 2026-01-27 17:04:31 -08:00
Broque Thomas
8248fab16e Fix iTunes wishlist and remove Single suffix
iTunes tracks now include album field for proper wishlist support.
  Strip " - Single" and " - EP" suffixes from iTunes album names.
2026-01-26 17:00:51 -08:00
Broque Thomas
375dcb8a19 Revert "Fix race condition preventing failed tracks from being added to wishlist"
This reverts commit c0c05c7b89.
2026-01-26 16:51:19 -08:00
Broque Thomas
c0c05c7b89 Fix race condition preventing failed tracks from being added to wishlist
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.
2026-01-26 16:44:31 -08:00
Broque Thomas
c728309a09 Fix automatic wishlist addition for failed/cancelled tracks in download modal
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.
2026-01-26 15:39:07 -08:00
Broque Thomas
087ead1a9a Fix Album Folder Organization for Recent Releases and Wishlist
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.
2026-01-26 12:35:26 -08:00
Broque Thomas
6096049c50 Fix album tracks added as singles from Library page with iTunes source
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
2026-01-25 17:44:17 -08:00
Broque Thomas
d7542b20c9 update version modal and readme 2026-01-25 10:33:32 -08:00
Broque Thomas
905e98016f Fix watchlist artist config and add image at insert when Itunes source. 2026-01-25 09:24:28 -08:00
Broque Thomas
59848acaf3 feat: dynamic source labels and enhanced connection testing
- 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
2026-01-25 00:29:33 -08:00
Broque Thomas
4f1dc2c15f force refetch similar artists when Spotify IDs missing
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.
2026-01-24 23:46:01 -08:00
Broque Thomas
3cb88669e3 Fix iTunes-only Discover page not loading data
- 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
2026-01-24 21:48:17 -08:00
Broque Thomas
b0e34c6942 Update web_server.py 2026-01-24 14:53:43 -08:00
Broque Thomas
580d28cfb9 Update web_server.py 2026-01-24 14:35:54 -08:00
Broque Thomas
2170ffa99e Fix discovery modal fix button for iTunes source and ListenBrainz playlists
- 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.
2026-01-24 14:25:27 -08:00
Broque Thomas
84e6b01cc6 Add iTunes fallback to Tidal and Beatport discovery workers
- 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.
2026-01-24 14:17:09 -08:00
Broque Thomas
4319d440ee Add iTunes as resilient primary source for discovery features
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.
2026-01-24 09:47:41 -08:00
Broque Thomas
1d14a8b987 Discover page itunes integration. Spotify and Itunes will have their own pool 2026-01-23 23:05:58 -08:00
Broque Thomas
1560726bbc rebuild discovery pool flow to allow multiprocessing of itunes and spotfiy. each getting their own pool. 2026-01-23 14:25:26 -08:00
Broque Thomas
fecd371b5e Add lazy loading for artist images across UI
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.
2026-01-23 07:38:03 -08:00
Broque Thomas
f12478ee70 Add iTunes fallback and improve artist/album handling
Adds iTunes fallback to SpotifyClient for search and metadata when Spotify is not authenticated. Updates album type logic to distinguish EPs, singles, and albums more accurately. Refactors watchlist database methods to support both Spotify and iTunes artist IDs. Improves deduplication and normalization of album names from iTunes. Updates web server and frontend to use new album type logic and support both ID types. Adds artist bubble snapshot example data.
2026-01-22 19:12:14 -08:00
Broque Thomas
f126cf7118 Add cross-provider support for watchlist artists
Introduces iTunes artist ID support to WatchlistArtist and database schema, enabling proactive backfilling of missing provider IDs (Spotify/iTunes) for watchlist artists. Updates WatchlistScanner to use MetadataService for provider-agnostic scanning and ID matching, and modifies web_server to support scans with either provider. Includes new database migration and update methods for iTunes and Spotify artist IDs.
2026-01-22 17:04:23 -08:00
Rafael Richard
3e733ab459 gracefully try to load configs from file into DB if the DB is fresh 2026-01-18 12:04:39 -05:00
Broque Thomas
f4bdb76c72 Reduce stuck flag timeout to 15 minutes
Changed stuck flag detection for wishlist and watchlist auto-processing from 2 hours to 15 minutes for faster recovery. Updated log messages and related logic to reflect the new timeout and display stuck duration in minutes. Also updated wishlist stats to use the improved stuck detection function.
2026-01-13 12:15:34 -08:00
Broque Thomas
fb56fbca5f Enhance wishlist auto-processing detection and UI feedback
Added backend support for reporting current auto-processing state and cycle in the wishlist stats API. Updated frontend to detect and handle auto-processing start, close modals, and notify users, as well as improved countdown timer updates and conflict handling when starting manual processing.
2026-01-13 10:00:17 -08:00
Broque Thomas
2ede3ddee7 Improve wishlist and watchlist auto-scheduling robustness
Refactored wishlist and watchlist auto-processing scheduling to use atomic timer updates and retry logic, preventing stuck timers and improving reliability. Removed excessive debug logging and improved error handling throughout related functions. Added checks to prevent concurrent wishlist processing and ensured timer state is managed consistently.
2026-01-13 09:42:07 -08:00
Broque Thomas
af5cc33c34 Deduplicate wishlist tracks during sanitization and filtering
Added logic to remove duplicate tracks based on their Spotify track ID during both the sanitization and category filtering steps in get_wishlist_tracks(). This ensures the frontend receives a deduplicated list and improves data consistency. A warning is printed if duplicates are found and removed during sanitization.
2026-01-11 17:50:10 -08:00
Broque Thomas
030757fe12 Improve wishlist deduplication and timer reset logic
Enhanced wishlist and watchlist processing to deduplicate tracks during both sanitization and filtering, preventing duplicate downloads and processing. Added explicit resets of next run timer variables to ensure accurate scheduling after each cycle. Updated countdown timer display in the UI to show '0s' when timer reaches zero instead of hiding it.
2026-01-11 15:46:47 -08:00
Broque Thomas
a8766828d9 Add content type filters for watchlist artists
Introduces new filters for live versions, remixes, acoustic versions, and compilation albums to the watchlist artist configuration. Updates the database schema, backend API, and web UI to support these options, allowing users to customize which content types are included for each artist in their watchlist.
2026-01-11 01:33:39 -08:00
Broque Thomas
d9a8162fb9 Fix context key consistency and add metadata write locks
Standardizes the use of extract_filename() for context keys to ensure consistent lookup and storage across download and post-processing flows. Increases fuzzy match thresholds in file search to reduce false positives. Adds per-file threading locks to prevent concurrent metadata writes, improving thread safety during metadata enhancement.
2026-01-09 23:14:15 -08:00
Broque Thomas
05aa731381 Sanitize context values in path template substitution
Introduced _sanitize_context_values to clean all string values in the context dictionary before applying path templates. This prevents unsafe characters (like '/') in metadata (e.g., artist names) from creating unintended path components during template substitution.
2026-01-09 08:20:11 -08:00
Broque Thomas
d4f1b3ed9a Add fallback for legacy config loading
Adds a check for the presence of 'load_config' in config_manager. If not found, uses a fallback method to support older settings.py files, improving compatibility with legacy Docker volumes.
2026-01-07 08:47:30 -08:00
Broque Thomas
c7f2f95450 Update to SoulSync v1.3 with YouTube engine and fixes
Bump version to 1.3 and update the version info modal to highlight the new YouTube download engine, Docker and system reliability fixes, and general improvements. Update the web UI to display v1.3 and apply minor formatting and accessibility improvements throughout index.html.
2026-01-06 22:53:21 -08:00
Broque Thomas
9aa2ac463a Add Docker path resolution for downloads
Updated _find_downloaded_file to resolve download paths for Docker environments, ensuring compatibility with host-mounted directories. Also updated README to clarify YouTube integration and sources.
2026-01-06 21:13:16 -08:00