New core/genre_filter.py with ~180 curated default genres. When strict
mode is enabled in Settings → Library Preferences → Genre Whitelist,
only whitelisted genres pass through during enrichment. Junk tags from
Last.fm (artist names, radio shows, playlist names) are silently dropped.
Applied at all 10 genre write points: Spotify, Last.fm, AudioDB, Deezer,
Discogs, iTunes, Qobuz enrichment workers + post-processing genre merge
+ initial download artist/album creation.
Strict mode is OFF by default — zero behavior change for existing users.
First enable auto-populates the whitelist with defaults. Users can add,
remove, search, and reset genres via the Settings UI.
Per-artist log lines now show the full details string from the worker
(e.g. "5 albums, 0 new tracks (150 existing updated)") instead of
just "5 albums, 0 tracks". Finished message shows "library up to date"
when no new content is found instead of "0 successful, 0 failed".
Full Refresh now clears all soulsync library records and rebuilds from
file tags in the output folder. Reads tags via Mutagen, groups by
artist/album, creates DB records with stable IDs. Files stay in place.
Previously Full Refresh did nothing for standalone — just returned.
Dashboard scan polling checked for 'completed' but backend sets 'finished'.
Added 'finished' to the completion check so polling stops, button resets,
stats refresh, and toast fires correctly. Also fixed deep scan reporting
stale record removals as 'failed' instead of 'successful'.
Playlist and single track downloads pass None as album_info to
_enhance_file_metadata. The downstream _extract_spotify_metadata
called .get() on it without a null guard, crashing with AttributeError.
All user-facing labels, docs, help text, tooltips, error messages, and debug
info output updated. Backend config keys, variable names, actual path values,
and Docker volume mounts are completely unchanged — zero functional impact.
Users can now override which metadata provider (Spotify, Deezer, Apple Music,
Discogs) is used when scanning a specific watchlist artist for new releases.
The selector appears in the artist config modal and only shows sources the
artist has enrichment IDs for. Default behavior is unchanged — all artists
use the global metadata source unless explicitly overridden.
The gunicorn PR blocked direct Python execution with SystemExit.
Replaced with _DIRECT_RUN flag at top and startup block at bottom
so both paths work:
- python web_server.py (Werkzeug dev server, Windows compatible)
- gunicorn -c gunicorn.conf.py wsgi:application (production)
Switch the web UI from Werkzeug's built-in server to Gunicorn for a more stable production deployment path.
Keep a separate dev config so local runs still reload quickly, while the production path uses a dedicated WSGI entrypoint and cleaner startup behavior.
The main motivation is to reduce the websocket teardown noise and make the server behavior more predictable under the app's mostly background-driven workload.
Added -vn flag to all codec ffmpeg commands (MP3, Opus, AAC) to strip
video/image streams during conversion. Embedded cover art in FLAC
files caused ffmpeg to fail when the output muxer couldn't handle
the image stream, producing 0KB output files. Cover art is
re-embedded afterwards by Mutagen.
The dedup key (normalized_title, year) caused different albums from
the same year to collide when title normalization stripped too much.
The "prefer more tracks" logic then kept compilations over studio
albums.
Two fixes:
- Title similarity check: if normalized titles are <85% similar,
they're different albums, not variants — keep both
- Compilation deprioritization: studio albums win over compilations
and "best of" collections when they do collide
Move Hydrabase availability checks into metadata_service so source resolution owns the policy. Keep web_server delegating to the centralized helper and add tests for the enabled/disabled cases.
Move artist discography resolution into core metadata_service, introduce MetadataLookupOptions, and keep web_server focused on request handling. Add focused tests for the new service boundary and preserve current fallback behavior for now.
Delay alternate-source fan-out until the primary enhanced-search response arrives, and stagger those follow-up requests so they do not all compete at once. Also parallelize artist, album, and track lookups inside each metadata source request to shorten the time the UI thread spends waiting on remote APIs. This keeps the single-worker web UI more responsive under the app's chatty search flow.
New MusicBrainz tab in Enhanced and Global search — finds tracks and
albums on MusicBrainz's community database with Cover Art Archive
images. Covers obscure tracks that Spotify/Deezer/iTunes miss.
- core/musicbrainz_search.py: search adapter with Track/Artist/Album
dataclasses, Cover Art Archive integration, smart query parsing
- Albums deduplicated (keeps best version with date and art)
- No artist results shown (MusicBrainz has no artist images)
- Album detail with full tracklist for download modal
- Smart word-boundary splitting for queries without separators
- Global search results container widened from 620px to 920px
- UI version bumped to 2.32
SoulSync Standalone Library is now the first section in both the
version modal and What's New popup. Auto-Import section updated with
all improvements (recursive scan, singles, tag preference, AcoustID).
New Downloads & Soulseek section groups download-related improvements.
Recent Fixes cleaned up — feature items moved to proper sections.
Deep scan for standalone mode:
- Scans Transfer folder for all audio files
- Compares against soulsync DB records by file_path
- Moves untracked files to Staging for auto-import processing
- Removes stale DB records where files no longer exist
- Cleans orphaned albums and artists with no tracks
Incremental scan skips for standalone — library updates at download
time, no periodic scanning needed. Both changes are purely additive
and only activate when server_type is 'soulsync'.
All sync-related buttons hidden when active server is SoulSync
Standalone. Covers static buttons (querySelectorAll on status update)
and dynamic modal buttons (_isSoulsyncStandalone flag).
UI version bumped to 2.31 (Docker stays at 2.3).
Single track ownership check was calling check_track_exists without
server_source, matching against all servers instead of the active one.
Album and EP checks already passed server_source correctly — this was
the only missing spot. Affects all server types.
Previously reused existing plex/jellyfin artist IDs, causing soulsync
tracks to be invisible on the library page (filtered by server_source).
Now always creates soulsync-specific artist and album records with
server_source='soulsync', avoiding PK collisions with hash suffixes.
Fourth server option on the Connections tab with SoulSync logo and
'Standalone' label. Config panel shows Transfer folder path and
Verify Folder button. Test connection counts audio files in the
Transfer folder. Settings save/load properly detects soulsync toggle.
New 'soulsync' media server option manages the library directly from
the filesystem, bypassing Plex/Jellyfin/Navidrome entirely.
Two paths populate the library:
1. Downloads/imports write artist/album/track to DB immediately at
post-processing completion, with pre-populated enrichment IDs
(Spotify, Deezer, MusicBrainz) so workers skip re-discovery
2. soulsync_client.py scans Transfer folder for incremental/deep scan
via DatabaseUpdateWorker (same interface as server clients)
New files:
- core/soulsync_client.py: filesystem scanner implementing the same
interface as Plex/Jellyfin/Navidrome clients. Recursive folder scan,
Mutagen tag reading, artist/album/track grouping, hash-based stable
IDs, incremental scan by modification time.
Modified:
- web_server.py: _record_soulsync_library_entry() at post-processing
completion, client init, scan endpoint integration, status endpoint,
web_scan_manager media_clients dict, test-connection cache updates
- config/settings.py: accept 'soulsync' in set_active_media_server,
get_active_media_server_config, is_configured, validate_config
- core/web_scan_manager.py: add soulsync to server_client_map
Dedup: checks existing artist/album by name across ALL server sources
before inserting to avoid duplicates. Enrichment IDs only written when
the column is empty (won't overwrite existing data).
Race condition: scanner re-scanned folders while post-processing was
still moving files, causing partial matches and ghost failures. Now
tracks in-progress paths and skips them on subsequent scans.
Coverage penalty fix: individual tracks that match at 80%+ confidence
now auto-import even when overall album coverage is low (e.g. 2 of 18
tracks present). Previously low coverage killed the entire import.
Import page: stats bar, filter pills, Scan Now, Approve All, Clear
History (clears imported + failed), live scan progress.
Album delete now shows a smart delete dialog with two options:
- Remove from Library (DB only, files untouched)
- Delete Files Too (removes DB records AND deletes audio files from
disk, cleans up empty album folder)
Backend /api/library/album/<id> DELETE now accepts ?delete_files=true
parameter, resolves each track's file path, and removes files before
deleting DB records. Reports files_deleted and files_failed counts.
The ffmpeg failure log was truncated to 200 chars which only showed the
version banner, hiding the actual error message. Now strips the banner
preamble and shows up to 500 chars of the real error with exit code.
Also cleans up 0KB output files left behind when ffmpeg fails.
Soulseek results from "Various Artists", "VA", "Unknown Artist", and
"Unknown Album" folders are now rejected before scoring. These
compilation folders rarely contain properly tagged files for the target
artist.
Clearing the wishlist now also cancels any active wishlist download
batch and resets the auto-processing flag, so downloads don't keep
running after the source tracks are removed.
Split Downloads page into main list (left) and batch panel (right).
Each active batch gets a color-coded card with artwork thumbnail,
progress bar, per-track status with download percentages, and
expandable track list. Download rows get matching color indicators.
- Click batch name to open its download/wishlist modal
- Filter icon narrows main list to one batch with clear banner
- Collapsible panel toggle for full-width list view
- Completed batches fade out after 15 seconds
- 7-day batch history with source type color dots
- Artwork fallback shows colored initial when no art available
- Per-track progress: download %, spinner for searching, proc label
- source_page column on sync_history for UI origin tracking
- /api/downloads/all includes batch summaries and per-track progress
- /api/downloads/batch-history endpoint for history queries
- Responsive layout, overflow-x hidden to prevent scroll flicker
Priority 0 query (artist + album + title) was gated behind a download
mode check that excluded Soulseek, the source that benefits most from
it. Soulseek searches match against file paths where users organize as
Artist/Album/Track — without the album name, ambiguous artist names
could match wrong-artist results (e.g. "Bleakness" as an album folder
instead of an artist). Removed the mode gate so all sources get the
most specific query first.
Dashboard stats (every 10s) and download status endpoint were
unconditionally calling slskd transfers/downloads API, causing
connection timeout spam for users with a slskd URL configured but
using YouTube/Tidal/etc as their download source. Now checks both
download source mode and status cache before making the API call.
Adds April 17 entries for Auto-Import, Wishlist Nebula, automation group
management, bidirectional artist sync, provider-agnostic discovery, live
sidebar badges, and critical source ID embedding fix. Version modal
reorganized to lead with current features and summarize earlier v2.2 work.
Full auto-import pipeline: background worker watches the staging folder,
identifies music using embedded tags → folder name parsing → AcoustID
fingerprinting, matches files to metadata source tracklists, and
processes high-confidence matches through the existing post-processing
pipeline automatically.
Worker: AutoImportWorker with start/stop/pause/resume, configurable
scan interval (default 60s), confidence threshold (default 90%), and
auto-process toggle. Processes one folder per cycle, alphabetical
order. Disc folder detection, stability checking, content hash dedup.
Confidence gate: 90%+ auto-processes silently, 70-90% queued as
pending review with approve/dismiss actions, <70% flagged for manual
identification. Track matching uses weighted algorithm (title 45%,
artist 15%, track number 30%, album tag 10%).
Database: auto_import_history table tracks every scan result with
folder hash, match data JSON, confidence, status, timestamps.
API: 7 endpoints — status, toggle, settings (GET/POST), results
(filtered/paginated), approve, reject.
UI: Auto tab on Import page with enable toggle, confidence slider,
scan interval selector. Live result cards with album art, confidence
bar (green/yellow/red), status badges, match stats. 5-second polling.
Artist Sync button on enhanced library page now does true bidirectional
sync: Phase 1 pulls new albums/tracks from the media server using the
DatabaseUpdateWorker in deep scan mode (preserves enrichment), Phase 2
removes stale DB entries for files no longer on disk. Works for Plex,
Jellyfin, and Navidrome. Toast shows +albums, +tracks, -stale counts.
Repair jobs tab redesigned: 2-column grid layout with glass gradient
cards, accent top line on hover, hover lift effect, job description
text below name, running state with pulsing accent bar. Responsive
to single column under 900px.
Fixed deezer_artist_id → deezer_id column name on artists table lookup.
The artists table uses 'deezer_id' but the enhanced library artist
lookup was querying 'deezer_artist_id' (the watchlist_artists column
name). Fixed to use the correct column name.