Add album.artists to the album data when adding wishlist tracks and change the missing-tracks processing to prefer album-level artists for constructing artist context. The code now uses spotify_data.get('album') or {} and picks artist_ctx from album.artists first, falls back to source_info.artist_name (with safe JSON parsing if source_info is a string), then to track-level artists, and finally to a generic Unknown Artist. This ensures tracks from compilations or albums where the album artist differs from track artists are grouped consistently.
Ensure Jellyfin music libraries are loaded after users by chaining loadJellyfinUsers() with .then(loadJellyfinMusicLibraries()) at three call sites (settings load, server toggle, and connection test) to avoid race conditions. Also add database write-ahead log and shared-memory files (database/music_library.db-wal and database/music_library.db-shm).
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.
Introduce a global watchlist override feature and UI to control release/content filters across all watchlist artists. Backend: add /api/watchlist/global-config (GET/POST) for reading/updating global settings, validation to require at least one release type when override is enabled, and _apply_watchlist_global_overrides() to apply settings to WatchlistArtist objects. Scanners (manual and automatic) now call _apply_watchlist_global_overrides() and perform additional checks (_should_include_release, _should_include_track) to skip releases/tracks according to config. Frontend: add a Global Watchlist Settings modal, controls (release types, content filters, include-all shortcut), save/validation logic, banners/notices when global override is active, and integration into the watchlist modal and per-artist config. Styles: add supporting CSS for the modal and banners. Small cleanup/whitespace adjustments included.
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.
Add support for cycling hero/featured similar artists by introducing a last_featured timestamp and using it to prefer least-recently-featured artists. Changes include:
- DB migration: add _add_similar_artists_last_featured_column and call it during migrations to add a last_featured TIMESTAMP column (non-fatal on error).
- Query changes: get_top_similar_artists now excludes watchlist artists via a LEFT JOIN and wa.id IS NULL and orders results by last_featured (nulls first), then by last_featured asc, occurrence_count desc, and similarity_rank asc. The query aliasing was also added for clarity.
- New helper: mark_artists_featured updates similar_artists.last_featured = CURRENT_TIMESTAMP for shown artists.
- Web server: increase fetch limit to 50, remove random shuffle and instead take the top 10 (already ordered by cycling logic), and call mark_artists_featured to rotate featured artists.
These changes aim to provide deterministic, least-recently-shown cycling of hero artists while keeping watchlist artists out of recommendations.
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.
Introduce an in-memory, locked cache (_mb_release_cache and _mb_release_cache_lock) for MusicBrainz release lookups to ensure concurrent post-processing threads use a consistent release MBID and avoid splitting albums in players. Use the track artist (first artist in multi-artist fields) for artist MBID matching. Remove usage of AudioDB's per-track strMusicBrainzAlbumID as a fallback for MUSICBRAINZ_RELEASE_ID and add a comment explaining why (AudioDB links tracks to original albums which can differ per track and split albums). Includes safeguards around match_release calls and stores empty results in the cache to avoid repeated failed lookups.
Prefer an album's stored track count when owned_tracks meets or exceeds it (avoids marking a complete standard edition as incomplete when an external source reports a larger deluxe count). Add more robust edition-detection regexes and generic catch-alls (parenthesized/bracketed text containing "edition") in music_database.py and web_server.py, and include silver/gold/platinum edition variants. Also tidy related regex handling and comments to improve matching for varied edition naming (e.g. "MMXI Special Edition").
Add robust checks and stabilization before treating transfers as finished. The patch verifies bytesTransferred vs expected size in multiple places (monitor, API polling, YouTube handling, and batch status building) to avoid acting on premature "Completed/Succeeded" states. It adds per-poll file-claiming to prevent two contexts from grabbing the same file, introduces file-stability loops and retries when locating/moving files, replaces a blind 1s wait with repeated size checks, and fsyncs destination files after copy. Also introduces an _incomplete_warned flag to reduce log spam and ensures post-processing is triggered reliably once files are truly stable.
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.
Introduce a new Retag tool to track and re-tag previously downloaded albums/singles. Changes include:
- Database: add migration hook and create retag_groups and retag_tracks tables, indexes, and many helper methods (add/find/update/delete groups & tracks, stats, trimming).
- Backend (web_server): capture completed album/single downloads into the retag tables, implement retag execution logic (_execute_retag) to fetch album metadata, match tracks, update tags, move files, download cover art, and update DB. Add thread-safe globals, executor, and REST endpoints for stats, listing groups, group tracks, album search, execute, status, and delete.
- Frontend (webui): add Retag Tool card, modals, search UI, JS to list groups/tracks, search albums, start retag operations, poll status, and update UI; include help content. Add CSS for modals and components.
The migration is invoked during DB init to ensure existing installations create the new tables. The tool caps stored groups (default 100) and avoids duplicate track entries.
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 per-track selection checkboxes, select-all control and a selection count to download modals (missing/YouTube/Tidal/artist-album). Implement JS helpers (toggleAllTrackSelections, updateTrackSelectionCount) to manage checkbox state, row dimming, button disabling, and to filter/stamp selected tracks with _original_index before sending to the backend. Update start/add-to-wishlist flows to use only selectedTracks and disable controls once analysis starts. Backend _run_full_missing_tracks_process now reads _original_index to preserve original table indices in analysis results. CSS updates (mobile.css and style.css) add styling for checkbox columns, responsive hiding logic for headers/columns, selection visuals (.track-deselected), and small layout/width tweaks.
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.
Set m3u_export.enabled default to false and update the UI so the M3U auto-save checkbox is unchecked unless explicitly enabled. Changes: config/settings.py flips the default to false, webui/index.html removes the checked attribute from the checkbox, and webui/static/script.js adjusts the logic to only check the box when settings.m3u_export.enabled === true. This prevents automatic M3U exports for users who don't explicitly opt in.
Introduce M3U export feature with UI control and server-side saving. Adds a new m3u_export config option (enabled flag) and a checkbox in the settings UI. The web endpoint /api/save-playlist-m3u now checks the m3u_export setting (unless force=true), builds the target folder using a new _compute_m3u_folder() helper (leveraging existing template logic with sensible fallbacks), sanitizes filenames, and writes .m3u files into the computed folder. Frontend JS loads/saves the new setting, supplies album/artist metadata when auto-saving, and both autoSave and manual export now POST M3U data to the server (manual export uses force=true). Also changed browser download filename extension to .m3u and added minor logging/response behavior.
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.
Prevent accidental misconfiguration of Docker container-internal paths
(Slskd Download Dir, Matched Transfer Dir, Import Staging Dir) by making
them read-only by default.
Repurposes the non-functional Browse button into a per-field Unlock/Lock
toggle. Adds a warning blurb below the Download Settings header so users
understand these are container paths, not host paths.
Introduce an optional "Blasphemy Mode" that deletes the original FLAC after a verified MP3 copy is created.
- config: add lossy_copy.delete_original (default: false).
- webui/index.html & static script: add checkbox and warning in settings UI and persist the setting.
- web_server.py: make _create_lossy_copy return the MP3 path when it deletes the FLAC (otherwise None); validate the MP3 using mutagen before removing the FLAC; rename associated .lrc files if present; update post-processing to use the final processed path in logs and wishlist checks and to consider .mp3 variants when FLAC may have been removed.
Behavior is off by default and includes safety checks and logging to avoid accidental deletion of originals.
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.
Tighten fuzzy matching in post-processing when no exact context is found: acquire matched_context_lock for thread-safe access and limit candidate keys to those starting with the current username prefix ("{task_username}::") while still matching the file basename. This prevents cross-album/username metadata contamination during mass downloads (e.g., multiple albums with identical track basenames) and reduces erroneous fuzzy matches.
Use MusicBrainz IDs returned by AudioDB as fallbacks when MB lookup is missing. The patch maps strMusicBrainzID/strMusicBrainzAlbumID/strMusicBrainzArtistID to MUSICBRAINZ_RECORDING_ID, MUSICBRAINZ_RELEASE_ID, and MUSICBRAINZ_ARTIST_ID respectively (only if those tags are not already present), sets recording_mbid and artist_mbid where applicable, and adds informational prints for debugging.
Request recording 'releases' from MusicBrainz and, when available, capture the first release MBID into id_tags['MUSICBRAINZ_RELEASE_ID']. Persist this release (album) MBID across tag formats by adding TXXX ('MusicBrainz Album Id'), a generic tag 'MUSICBRAINZ_ALBUMID', and an iTunes MP4 freeform key. This allows tracks to be associated with their MusicBrainz release/album.
Stop recomputing expected file paths during verification and instead use the exact path computed/moved during post-processing (context['_final_processed_path']). If that context value is missing, assume success to avoid false failures and mark the task completed.
Also store the computed final path into context in _post_process_matched_download so verification uses the exact same path. Add logic to handle cases where the source vanished but a variant (quality-tagged) file exists in the destination — treat that as success, set the final path, download art and generate LRC.
In the post-processing worker: skip verification when the task is already completed or marked as stream_processed, check for stream_processed status while searching, increase file search retries from 3 to 5 with longer sleeps, and re-check stream_processed before marking a task failed. Improve log messages and error text to reduce false negatives caused by independent recomputation or concurrent stream processing.
Add a recovery step during wishlist processing that scans in-memory download_tasks (under tasks_lock) for tasks marked 'failed' or 'not_found' but not present in permanently_failed_tracks, and appends normalized track entries (including retry_count, failure_reason, spotify_track, and cached candidates). This prevents tasks that were force-marked failed by stuck-detection logic from being silently skipped in wishlist sync. Also fix post-processing context lookup by rebuilding the context key with _make_context_key(task_username, task_filename) so it matches how context keys are stored.
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 an "Active Downloads" section to the dashboard and wire up client-side plumbing to populate and update it. Adds escapeForInlineJs to safely embed values into inline JS attributes and replaces several inline onclick usages (search/genre/listenbrainz/artist buttons) to prevent quoting issues. Implements updateDashboardDownloads, createDashboardDiscoverBubble, and integrates dashboard updates into artist/search/discover flows (including register/discover download persistence and monitor hooks). Adds dashboard-specific CSS for discover/artist bubbles and minor style fixes (artist image sizing, keyframe formatting) plus a mobile CSS tweak for artist images.
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.
Introduce a developer-only Hydrabase testing UI and backend WebSocket integration. Adds a simple dev-mode toggle (password 'hydratest') and new API endpoints (/api/dev-mode, /api/hydrabase/connect, /api/hydrabase/disconnect, /api/hydrabase/status, /api/hydrabase/send) that use websocket-client to connect/send raw JSON to a Hydrabase instance. Frontend changes include a Hydrabase nav/page, payload editors, response panel, dev-mode UI in Settings, associated JS handlers, CSS styling, and an icon asset. Also add websocket-client to requirements.
Introduce $artistletter and $disc template variables across config, UI, and backend to support artist-first-letter tokens and multi-disc albums. Update web_server.py to include disc_number in template context, prefer user-controlled $disc in templates, and create configurable disc subfolders using a new file_organization.disc_label setting. Update example and active config, web UI to expose the new variable and disc label selector, and script.js to validate, load, and save the new settings and substitutions.
Introduce a configurable "lossy_copy" feature that creates an MP3 copy alongside downloaded FLAC files. Adds default config (example and runtime) and UI controls for enabling the feature and selecting an MP3 bitrate. Implements _create_lossy_copy in web_server.py which checks the FLAC extension, respects the configured bitrate (default 320 kbps), locates ffmpeg (including a local tools/ffmpeg fallback), performs conversion, and attempts to update the QUALITY tag via mutagen. The feature is invoked after post-processing/moving downloads. Logs and graceful failures (missing ffmpeg, timeouts, tag errors) are included.
Introduce a new $quality template variable that is only substituted into filenames to avoid splitting album folders when tracks of mixed qualities are present. Updates include:
- web_server.py: populate template contexts with 'quality' (from context['_audio_quality']), strip $quality from folder components, substitute it only in the filename, and clean up empty brackets/parentheses/dashes when the variable is empty.
- config/config.example.json and config/config.json: document the new variable in the file_organization template variables string.
- webui/index.html and webui/static/script.js: update UI help text and client-side template validation to include $quality.
This prevents folder fragmentation for albums with mixed-quality files while still allowing quality information in filenames.