Allow multiple users to share a single SoulSync instance with isolated personal data. Each profile gets its own watchlist, wishlist, discovery pool, similar artists, and bubble snapshots — while sharing the same music library, database, and service credentials.
- Netflix-style profile picker on startup when multiple profiles exist
- Optional PIN protection per profile; admin PIN required when >1 profiles
- Admin-only profile management (create, edit, rename, delete)
- Profile avatar images via URL with colored-initial fallback
- Zero-downtime SQLite migration — all existing data maps to auto-created
admin profile
- Single-user installs see no changes — profile system is invisible until
a second profile is created
- WebSocket count emitters scoped to profile rooms (watchlist/wishlist)
- Background scanners (watchlist, wishlist, discovery) iterate all profiles
Incremental database updates now detect when artists or albums have been removed from your media server (Plex, Jellyfin, or Navidrome) and automatically clean them up from SoulSync's database. Previously, deleted content would persist as ghost entries until you ran a full refresh. Removal counts are reported in the scan results. Includes safety checks to prevent accidental mass deletion if the server is unreachable or returns incomplete data.
Adds Tidal as a third download source alongside Soulseek and YouTube. Uses the tidalapi library with device-flow authentication to search and download tracks in configurable quality (Low/High/Lossless/HiRes) with automatic fallback. Integrates into the download orchestrator for all modes (Tidal Only, Hybrid with fallback chain), the transfer monitor, post-processing pipeline, and file finder. Frontend includes download settings with quality selector, device auth flow, and dynamic sidebar/dashboard labels that reflect the active download source. No breaking changes for existing users.
Filled all missing Hydrabase fallthrough gates across 6 endpoints (artist album tracks, iTunes album, discover album, Spotify track, both similar artists), added track_number/disc_number to Track dataclass, fixed get_album_tracks to send soul_id instead of text query, mapped soul_id field from Hydrabase responses across all search methods, updated 7 frontend call sites to pass album name/artist params, and fixed M3U export defaulting to enabled when users never turned it on.
Fix short artist names (e.g. "ano") getting buried by wildcard matches. Re-ranks Spotify and iTunes results so exact name matches appear first, without changing what's returned.
SoulSync was importing from all Navidrome libraries regardless of user access restrictions. Added a "Music Library" dropdown in Navidrome settings that lets users scope imports to a specific music folder. Uses the Subsonic musicFolderId parameter on artist, album, and search API calls. Selecting "All Libraries" reverts to the previous behavior.
AcoustID sometimes returns featuring info in square brackets like [W/ Barnes Blvd.] instead of parentheses. The normalizer only stripped parenthetical featuring tags, so these tracks failed verification and got quarantined despite being correct. Now strips [W/ ...], [with ...], [feat. ...], and [ft. ...] bracket patterns too.
Store album artists in the Spotify track data structure (handles both dict and object album forms) so downstream processing has access to album-level artist metadata. Also update missing-track processing to fall back to source_info['watchlist_artist_name'] when artist_name is absent, ensuring artist context is preserved for legacy/watchlist-sourced entries.
Raise the default Jellyfin API timeout for bulk operations from 30s to 120s to better handle slow servers and large database syncs. Updated core/jellyfin_client.py to set bulk_timeout to 120, webui/index.html to show 120s in the settings input, and webui/static/script.js to use 120 as the fallback when loading settings. Aligns UI and client behavior to reduce timeout errors during heavy syncs.
Add a configurable API timeout for Jellyfin bulk requests and improve fetch retry behavior. UI: adds an "API Timeout (seconds)" field (15–300s, default 30) in webui/index.html and persists it via webui/static/script.js (load/save). Client: jellyfin_client.py now reads api_timeout from config_manager, uses it as the bulk timeout and computes a sensible non-bulk timeout (max(5, bulk_timeout//6)). Fetch loops for tracks and albums were hardened: reducing batch size now resets consecutive failure counters, log messages were clarified, and the stopping/retry thresholds were adjusted to avoid premature aborts at minimum batch sizes.
Introduce a generic score_track_match(...) in core/matching_engine.py and make calculate_match_confidence(...) delegate to it. The new scorer is source-agnostic, consolidates artist/title/duration logic (core-title fast path, cleaned similarity, weighted 60/30/10 scoring) and improves artist matching.
In web_server.py add cache-validation (_validate_discovery_cache_artist) and a reusable _discovery_score_candidates(...) helper that calls the new scorer. Propagate per-match confidence through discovery flows (Tidal, YouTube, ListenBrainz, Beatport), increase Spotify/iTunes search limits, add an extended high-limit search strategy, tighten per-source thresholds, and save match confidence to the discovery cache. Overall this centralizes and standardizes matching logic and improves accuracy/validation for cached discovery results.
Expand track title normalization and refine album grouping logic.
- core/acoustid_verification.py: Broadened the parenthetical-suffix regex to strip year-based remasters and additional variants (e.g. "2025 Remaster", "single edit", "album edit") while still removing common extras like (Live), (Deluxe), (Radio Edit), and featuring tags.
- web_server.py: Restrict the smart album grouping to only run for singles/auto-detected albums; explicit album downloads now preserve the original Spotify album name to avoid mangling names (e.g. reworked/remastered vs deluxe). Added explicit logging for both smart grouping and skipped grouping paths. The verification post-processing worker now checks an is_album_download flag in context and skips re-grouping when true, with fallback logging on errors.
These changes prevent unintended renaming of explicit album downloads and improve normalization of common title suffixes.
- matching_engine.py: Add 'single edit' and 'album edit' tokens and clarify radio edit comment so edit/cut variants are recognized as different cuts rather than being silently normalized away.
- database/music_database.py: Fix SQL param ordering by appending server_source to params; add a pre-step to strip "(with ...)" / "[with ...]" only when used inside brackets (so titles like "Stay With Me" are preserved); stop removing edit/version tokens in the generic cleanup and document that radio/single/album edits are treated as distinct by the similarity scorer to avoid incorrect matches.
- web_server.py: Increase DB match confidence threshold from 0.70 to 0.80 and update the runtime check accordingly.
These changes prevent edit/cut variants from being conflated with original recordings, improve title normalization for "with" featuring syntax in brackets, and fix a params ordering bug and a too-low match threshold.
Use UUID nonces to correlate requests/responses and robustly handle interleaved stats/heartbeat messages from the Hydrabase server. Adds _extract_stats and _extract_results helpers, records last_peer_count and timestamp, and loops on recv() with timeouts to drain non-result messages until the matching response (or a results message without a nonce) arrives. Mirrors the same nonce/send-and-drain logic in HydrabaseWorker, adds necessary imports (time, uuid), and improves logging and timeout handling to avoid returning stale or misattributed data.
Improve Hydrabase response handling and add discography/album track helpers. core/hydrabase_client.py: extract peer counts from stats.connectedPeers, handle new "response" key and stats-only or unexpected response shapes (return empty instead of wrapping), and add search_discography() and get_album_tracks() to map Hydrabase results into Album/Track objects. web_server.py: avoid redundant Hydrabase round-trips by passing precomputed hydrabase_counts into the background comparison worker; prefer Hydrabase for artist discography and album track import when active (with Spotify fallback); and route album-context searches to Hydrabase when configured. These changes reduce duplicate network calls and improve robustness against varied Hydrabase payloads.
Add Hydrabase support as an optional/dev metadata source and comparison tool.
- Add core/hydrabase_client.py: synchronous Hydrabase WebSocket client that normalizes results to Track/Artist/Album types and exposes raw access.
- Update config/settings.py: add hydrabase settings (url, api_key, auto_connect) and getter.
- Update web_server.py: integrate HydrabaseClient, initialize client alongside the existing HydrabaseWorker, add auto-reconnect using saved config, persist credentials on connect/disconnect, add endpoints for status and stored comparisons, background comparison runner (Hydrabase vs Spotify vs iTunes), and adapt multiple search endpoints to optionally use Hydrabase as the primary metadata source with fallbacks.
- Update web UI (webui/index.html, webui/static/script.js, webui/static/style.css): add network stats and source comparison UI, pre-fill saved credentials, show peer count, load/display comparisons, update disconnect behavior to disable dev mode, and add Hydrabase badge styling.
Behavioral notes: when dev mode + Hydrabase are active, searches can be served from Hydrabase and comparisons to Spotify/iTunes are run in background; when Hydrabase fails the code falls back to Spotify/iTunes. Saved Hydrabase credentials are persisted for auto-reconnect; disconnect disables dev mode and auto_connect.
Files touched: config/settings.py, core/hydrabase_client.py, web_server.py, webui/index.html, webui/static/script.js, webui/static/style.css.
Increase Spotify client rate limit and reduce API contention during watchlist scans. Changes:
- core/spotify_client.py: Bumped MIN_API_INTERVAL from 0.2s to 0.35s (~171 calls/min) to stay safely under Spotify's ~180/min limit.
- web_server.py: In start_watchlist_scan and automatic scan flow, pause spotify_enrichment_worker and itunes_enrichment_worker before scanning (tracking with _enrichment_was_running/_itunes_enrichment_was_running) and resume them in finally blocks; added console prints for pause/resume. This prevents enrichment workers from contending for API quota during long scans.
- webui/static/script.js: Improved enrichment status tooltip logic to prioritize explicit currentType and then fall back to completion-based inference with explicit branches for artists, albums, and tracks for clearer progress text.
These changes aim to avoid API rate violations and make scan progress display more predictable.
When clearing the wishlist or changing the discovery lookback period, reset watchlist_artists.last_scan_timestamp to NULL so subsequent scans can re-discover older releases that were previously filtered by an earlier scan timestamp. clear_wishlist now deletes wishlist_tracks, updates last_scan_timestamp for all watchlist artists, and logs the number of tracks cleared and artists reset. set_discovery_lookback_period also resets last_scan_timestamp and reports how many artists were reset. Minor whitespace cleanups in watchlist_scanner and web_server included.
Add a release_date field to the Track dataclass for both iTunes and Spotify clients (iTunes: parsed from releaseDate, Spotify: from album.release_date). Propagate release_date into enhanced search results in web_server and into the client-side script so album objects include release_date when available. Also broaden playlistId matching in the missing-tracks process to include 'enhanced_search_track_'. Removed SQLite SHM/WAL files from the repo (cleanup of DB temporary files). These changes enable showing and using track release dates across the app.
Add duplicate-artist detection/merge and handle ratingKey (ID) migrations for artists and albums. Introduces MusicDatabase.merge_duplicate_artists that picks a canonical artist (most enrichment data), merges enrichment fields, migrates albums/tracks, and removes duplicates; DatabaseUpdateWorker now runs this merge during updates (even when no new content) and after orphan cleanup. insert_or_update_artist/album now detect same-name/title + server_source collisions (ratingKey changes), inserting a new record while preserving enrichment and migrating references, with safe deletion of old rows. Also deduplicate artist listing queries so results show a single canonical row per name+server_source while aggregating album/track counts across duplicates. Logging improved to report merge/migration outcomes.
Introduce deferred album repair scanning and robust tracklist resolution plus UI/presentation tweaks.
- core/repair_worker.py: Add lazy MusicBrainz and AudioDB client accessors, per-batch folder queues, and register_folder/process_batch to defer folder scans until a batch completes. Implement cascading tracklist resolution (_resolve_album_tracklist) using Spotify/iTunes IDs, Spotify track→album lookup, album search, MusicBrainz release lookup, and AudioDB→MusicBrainz fallback. Add helpers to read Spotify track IDs, MusicBrainz album IDs, album/artist tags from files, MB/AudioDB fetchers, placeholder-ID filtering, and rename associated .lrc files when renaming audio files. Cache/locking and background-threaded scanning included. Improves resilience to missing/placeholder IDs and avoids circular imports.
- web_server.py: Register album folders for repair in post-processing and trigger repair_worker.process_batch when batches complete (multiple completion paths) so scans run after downloads finish.
- webui/static/script.js: Reduce unnecessary background work by skipping many fetches when the tab is hidden, and refresh dashboard-specific data on visibility change to ensure UI updates after OAuth or tab switch.
- webui/static/style.css: Replace glassmorphic backdrop-filters with opaque dark gradients for sidebar and main content to improve GPU rendering and visual consistency.
Overall: Adds reliable post-download album repair scanning using multiple metadata sources and reduces unnecessary client polling and heavy CSS effects for better performance and robustness.
Add full-featured SpotifyWorker and iTunesWorker background workers to enrich artists, albums, and tracks with external metadata using batch cascading searches, fuzzy name matching, ID validation, and DB backfills. Update RepairWorker to re-read the transfer path from the database each scan, resolve host paths when running in Docker, and trigger immediate rescans when the transfer path changes; remove the static config_manager dependency. Also include supporting changes to the database layer and web UI/server (stats, controls, and styles) to integrate the new workers and reflect updated worker status.
Add _get_playlist_items_page to call the new playlists/{id}/items endpoint (Feb 2026 API migration) and fall back to spotipy.playlist_items (old /tracks) on 403/404. Update _get_playlist_tracks and web_server.get_playlist_tracks to use the new helper to avoid 403 errors for Development Mode apps while preserving compatibility with Extended Quota Mode.
Introduce a RepairWorker to scan the transfer folder and automatically detect/repair broken album track numbers (e.g. the "all tracks = 01" bug). The worker uses mutagen to read/write tags, fuzzy-matches titles against an album tracklist (Spotify/iTunes via a SpotifyClient), updates filenames and the tracks DB file_path when renamed, and caches album tracklists. It also adds DB schema support (repair_status, repair_last_checked, and an index).
Integrates the worker into the web server: initializes and starts the worker, and exposes /api/repair/status, /api/repair/pause and /api/repair/resume endpoints. Adds UI elements (button, tooltip), client-side JS to poll and control the worker, CSS for visuals/animations, and a new image asset (whisoul.png).
Add a new docs/api-response-shapes.md describing expected Spotify/iTunes dataclass and raw-dict response shapes and client behavior. Also update core/wishlist_service.py to include 'track_number' (default 1) and 'disc_number' (default 1) in each formatted track dict so consumers receive track ordering metadata.
Introduce a Hydrabase P2P mirror worker and integrate it into the web UI and server flows. Adds core/hydrabase_worker.py: a background thread with a capped queue (1000), enqueue API, rate limiting, basic stats (sent/dropped/errors), and logic to send JSON requests over a provided WebSocket (responses received and discarded). Integrates the worker into web_server.py (import, startup init, status/pause/resume endpoints, and enqueues queries from multiple search endpoints when dev mode is enabled). Adds UI elements, JavaScript polling/toggle logic, and CSS styling for a Hydrabase status button in webui (index.html, static/script.js, static/style.css) to display and control worker state.
Add Deezer as a third metadata enrichment source. Enriches tracks with BPM and explicit flags, albums with
record labels, explicit flags, and type classification (album/single/EP), and backfills artwork and genres across
all entities. Includes background worker with priority queue, rate-limited API client, database migration, server
endpoints, and UI button with purple-themed status tooltip.
Integrated TheAudioDB as a metadata enrichment source with a background worker that scans artists, albums, and tracks in priority order. Stores style, mood, and AudioDB IDs with automatic backfill of artwork and genres.
Includes artist ID cross-verification from album/track results to correct mismatches caused by same-name artists.
Integrated TheAudioDB as a metadata enrichment source. A background worker scans the library in priority order (artists → albums → tracks), matching entities via fuzzy name comparison and storing style, mood, and AudioDB IDs. Includes rate limiting, 30-day retry for not-found items, and a UI tooltip showing phase-based scan progress.
Add a UI button to disconnect Spotify and fall back to iTunes/Apple Music without restarting. Cache is_spotify_authenticated() with a 60s TTL to reduce redundant API calls (~46 call sites were each
triggering a live sp.current_user() call). Fix status endpoint calling the auth check twice per poll,
and ensure both OAuth callback handlers (port 8008 Flask route and port 8888 dedicated server)
invalidate the status cache so the UI updates immediately after authentication.
Spotify's @rate_limited decorator now retries on 429/5xx with exponential backoff (up to 5 retries) instead of sleeping once and raising. Watchlist scan delays scale dynamically based on lookback setting and artist count to prevent sustained API pressure. A circuit breaker pauses the scan after consecutive rate-limit failures.
Multi-disc albums (e.g., deluxe editions, double albums) now automatically organize
tracks into Disc 1/, Disc 2/ subfolders within the album folder. Detection uses the
disc_number field from Spotify's API — when an album has total_discs > 1, subfolders
are created. Single-disc albums are completely unaffected.
- Plumb disc_number through all download paths (enhanced, non-enhanced, download
missing modal, wishlist)
- Compute total_discs from album tracklist and store on album context
- Modify path builder to insert Disc N/ subfolder for multi-disc albums
- Preserve disc_number when tracks fail and get re-added to wishlist
- Preserve disc_number when adding tracks to wishlist from library page
- Add visual disc separators in Soulseek search result track lists
Summary: Navidrome incremental updates always found 0 new tracks because _get_recent_albums_navidrome() fetched all artists, sampled only the first 200, collected their albums, and sorted by created date — missing artists beyond the first 200 entirely. Replaced this with a single getAlbumList2?type=newest Subsonic API call that directly returns albums sorted by library addition date, matching how Jellyfin and Plex already use their native "recently added" endpoints.
Fixed issue where broken iTunes explicit album IDs were preferred over working clean versions during deduplication. Some iTunes explicit albums (e.g., "Mr. Morale & The Big Steppers" ID 1623854804) report track counts in metadata but return 0 tracks when queried.
Added validation in itunes_client.py get_artist_albums() to verify explicit albums actually have tracks before keeping them. If an explicit version has 0 tracks, it's skipped and the clean version is used instead.
This fixes:
- "No tracks found" error when clicking affected albums
- Incorrect track count mismatches (19/18) caused by broken API data
The validation only runs for explicit albums during deduplication, minimal performance impact.
Enrich downloaded audio files with external identifiers and improved genre metadata in a single post-processing write. During metadata enhancement, the app now looks up the MusicBrainz recording and artist MBIDs, retrieves the ISRC and MusicBrainz genres from a follow-up detail lookup, merges them with Spotify's artist-level genres (deduplicated, capped at 5), and embeds everything alongside the Spotify/iTunes track, artist, and album IDs. All MusicBrainz API calls are serialized through the existing global rate limiter, making concurrent download workers safe without needing to pause the background worker. Includes a database migration adding Spotify/iTunes ID columns to the library tables.
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
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)
- 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.
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.
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
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.
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.
Refactored artist scanning logic to use the active metadata provider (Spotify or iTunes) for fetching artist data, discography, and album tracks. Introduced helper methods to select the correct client and artist ID based on the provider, and updated image and similar artist handling accordingly. This enables watchlist scanning to work with iTunes when Spotify is not authenticated, improving flexibility and provider support.
Updated get_album, get_album_tracks, and get_artist methods to return data structures compatible with Spotify's API format. This includes normalizing image arrays, artist and album fields, and adding synthetic URIs and external URLs for better interoperability.
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.
Introduces core/itunes_client.py implementing an iTunes Search API client for music metadata, providing search and lookup for tracks, albums, and artists with rate limiting. Adds METADATA-FALLBACK-IMPLEMENTATION.md, a comprehensive guide comparing fallback strategies for music metadata, including anonymous Spotify access and iTunes, and outlines integration approaches for seamless user experience without requiring Spotify credentials.
Updates the method for obtaining the next cursor during track pagination to use the approach from PR #113, retrieving it from 'links.meta.nextCursor' instead of 'meta.nextCursor'. This ensures correct pagination behavior.
Eliminated unnecessary filtering of playlists by owner or collaboration status, as the Spotify API already returns all accessible playlists. This simplifies the code and ensures all relevant playlists are processed.
Rewrites playlist and track fetching to use Tidal's JSON:API endpoints with cursor-based pagination and batch track hydration. Adds robust rate limiting with retry logic, and introduces ISO-8601 duration parsing. This improves reliability, performance, and compatibility with Tidal's latest API structure.
Added a call to self.soulseek._setup_client() in DownloadOrchestrator to ensure the Soulseek client configuration is reloaded when the orchestrator is initialized. This helps keep the client settings in sync with the latest configuration.
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.
Introduces a reload_config method to SpotifyClient and refactors ConfigManager to support reloading configuration from a file. Updates web_server.py to use the new config loading mechanism, ensuring configuration is loaded into the existing singleton instance and SpotifyClient is properly re-initialized after settings changes.
Introduces an async method to search for a query and automatically download the best result based on quality preferences and source priority. The method handles empty results, filters by quality, and logs the download process.
Implemented clear_all_completed_downloads in YouTubeClient to remove completed, cancelled, errored, and aborted downloads from memory. Updated DownloadOrchestrator to call this method, ensuring both Soulseek and YouTube completed downloads are cleared.
Introduces a new priority 0 query that combines artist, album, and title for improved matching, especially for YouTube and hybrid download modes. This helps better match tracks where the album is significant, such as soundtracks, and only applies when the album is not a generic label like 'single' or 'greatest hits'.
Adjusts matching weights for YouTube sources to rely more on title and duration, adds a shutdown callback to the YouTube client to prevent new downloads during shutdown, and enhances post-processing to reliably resolve actual YouTube file paths. Improves error handling for file removal, ensures no new batch downloads start during shutdown, and refines download monitoring to trigger post-processing on completed YouTube downloads. Also increases YouTube download retries and improves logging for debugging.
Enhances streaming logic to better support YouTube as a download source, including improved filename handling, fuzzy file matching, and search query generation. Updates format checks in the frontend to skip them for YouTube (always MP3). Refactors backend to use a unified download status API for both Soulseek and YouTube, and improves service test messaging based on the active download mode.
Introduces a DownloadOrchestrator class to route downloads between Soulseek and YouTube based on user-configurable modes (Soulseek only, YouTube only, Hybrid with fallback). Updates web server and UI to support new download source settings, including hybrid mode options and YouTube confidence threshold. Refactors YouTube client for thread-safe download management and bot detection bypass. Ensures quality filtering is skipped for YouTube results and improves file matching and post-processing logic for YouTube downloads.
Updated the duration field in TrackResult to store duration in milliseconds instead of seconds, matching Spotify's expected format. This ensures consistency when integrating with services that use millisecond-based durations.
Implements an enhanced search endpoint in the backend that unifies Spotify and local database results, returning categorized artists, albums, and tracks. Updates the frontend with a new dropdown overlay for live search, debounced input, categorized result rendering, and direct integration with the main results area for album/track selection. Adds new CSS for the dropdown and result cards, and updates the Track dataclass to include image URLs for richer UI display.
Introduces 'search_timeout' and 'search_timeout_buffer' options to Soulseek settings in the config, backend, and web UI. The backend now uses these values to control search duration and polling, allowing users to fine-tune how long searches run and how long to wait for late results.
Enhanced the music matching engine to use stricter version handling, rebalance title/artist/duration weights, and raise confidence thresholds to reduce false positives. Updated string normalization to better handle separators and special characters. In the web UI, improved album ID sanitization and added a placeholder for missing album images in the wishlist view.
Refactored the sorting logic to handle cases where 'spotify_data' may be a JSON string or have varying structures. This ensures consistent sorting by artist and track name, even with malformed or unexpected data formats.
Introduces backend and frontend functionality to list and select Jellyfin music libraries. Adds API endpoints, updates the client logic, and provides a UI selector for users to choose their preferred Jellyfin music library.
Adds strict checks to ensure Soulseek track versions (live, remix, acoustic, instrumental) are only matched if the Spotify track title contains corresponding version indicators. This prevents mismatched versions from being accepted and applies a reduced penalty for matching non-original versions.
Redirect URIs for Spotify and Tidal OAuth are now configurable via the web UI and settings. Updated backend clients to use the configured redirect URI if provided, improving flexibility for deployments with custom callback URLs.
Wishlist tracks are now sorted by artist and track name for consistent display. The database query for wishlist tracks now orders by date_added instead of randomly. Added logic to remove completed tracks from the wishlist during failed track processing. Updated docker-compose.yml to mount the H: drive for transfer folders.
Added helper functions to resolve Windows paths for Docker containers and to extract filenames in a cross-platform manner. Updated usage throughout web_server.py and soulseek_client.py to ensure correct path mapping and filename handling. Modified docker-compose.yml to use named volumes and mount the E: drive for better Docker compatibility.
Updated Soulseek client, web server, and docker-compose.yml to better handle service URLs when running inside Docker containers. Localhost URLs are now resolved to host.docker.internal, and Docker-specific volume mounts and extra_hosts are configured for improved interoperability between container and host services.
Introduces web server routes and UI buttons for initiating Spotify and Tidal OAuth authentication flows, with dedicated callback servers for token exchange. Updates Docker ports for OAuth callbacks and refines PKCE handling for Tidal. Improves user experience by allowing authentication directly from the web UI.
Introduces Docker deployment files (.dockerignore, Dockerfile, docker-compose.yml, docker-setup.sh, requirements-webui.txt, and README-Docker.md) for SoulSync WebUI. Refactors core/database_update_worker.py and core/media_scan_manager.py to support headless operation without PyQt6, enabling signal/callback compatibility for both GUI and non-GUI environments. Removes logs/app.log file.
Introduces a global activity feed system with API endpoints for recent activities and toasts, and integrates activity tracking for key backend events (downloads, syncs, database updates, etc.). Updates the dashboard frontend to periodically fetch and display activity feed items and show toasts for recent actions. Improves error logging and reduces noise for expected 404s in Soulseek client. Adds related CSS for activity feed separators.
Introduces backend logic to automatically remove tracks from the wishlist if they already exist in the database after downloads or database updates. Adds a new API endpoint and frontend button for manual wishlist cleanup, allowing users to remove already-owned tracks from the wishlist. Enhances reliability and user experience by keeping the wishlist up-to-date.
Watchlist will allow the user to select artist to 'watch' and the app will automatically add new releases by those artists to the wishlist for automatic download.
included version information and modal
This is needed if users manually correct failed matches on plex when songs are scanned in. If a db sync is done after the scan but before the manual modification,