Major version bump with 40+ commits of features and fixes:
- Deezer as 6th download source (ARL auth, FLAC/MP3, Blowfish decrypt)
- Cache-powered discovery: 5 sections + Genre Deep Dive modal
- Listening Stats page with charts and play buttons
- Picard-style MB release preflight for consistent album tagging
- Album Tag Consistency repair job
- Unified glass UI across dashboard, sync, and modals
- Mobile responsive overhaul for all pages
- Enrichment fixes: retry loops, rate limits, worker pause during scans
- AcoustID skip for trusted API sources
- Stats page: database storage donut chart with per-table breakdown and total size
- Discover page: 5 new sections mined from metadata cache (zero API calls):
Undiscovered Albums, New In Your Genres, From Your Labels, Deep Cuts, Genre Explorer
- Genre Deep Dive modal: artists (clickable → artist page), popular tracks,
albums with download flow, related genre pills, in-library badges
- All cache queries filtered by active metadata source (Spotify/iTunes/Deezer)
- Stale cache entries (404) gracefully fall back to name+artist resolution
- Album cards show "In Library" badge, artist avatars scaled by prominence
Full stats dashboard that polls Plex/Jellyfin/Navidrome for play
history and presents it with Chart.js visualizations:
Backend:
- ListeningStatsWorker polls active server every 30 min
- listening_history DB table with dedup, play_count/last_played on tracks
- get_play_history() and get_track_play_counts() for all 3 servers
- Pre-computed cache for all time ranges (7d/30d/12m/all) rebuilt each sync
- Single cached endpoint serves all stats data instantly
- Stats query methods: top artists/albums/tracks, timeline, genres, health
Frontend:
- New Stats nav page with glassmorphic container matching dashboard style
- Overview cards (plays, time, artists, albums, tracks) with accent hover
- Listening timeline bar chart (Chart.js)
- Genre breakdown doughnut chart with legend
- Top artists visual bubbles with profile pictures + ranked list
- Top albums and tracks ranked lists with album art
- Library health: format breakdown bar, unplayed count, enrichment coverage
- Recently played timeline with relative timestamps
- Time range pills with instant switching via cache
- Sync Now button with spinner, last synced timestamp
- Clickable artist names navigate to library artist detail
- Last.fm global listeners shown alongside personal play counts
- SoulID badges on matched artists
- Empty state when no data synced yet
- Mobile responsive layout
DB migrations: listening_history table, play_count/last_played columns,
all with idempotent CREATE IF NOT EXISTS / PRAGMA checks.
- Add get_artist_top_tracks to Last.fm client (up to 100 tracks)
- Include lastfm_listeners, lastfm_playcount, lastfm_tags, lastfm_bio,
and soul_id in artist detail API response
- New endpoint: /api/artist/<id>/lastfm-top-tracks for lazy loading
- Hero layout: image (160px) | center (name, badges, genres, bio,
listener/play stats, progress bars) | right card (scrollable top
100 tracks from Last.fm)
- Badges 36px with hover lift, bio in subtle card with Read More
toggle, Last.fm tags merged with existing genres
- Numbers formatted: 1234567 → 1.2M
- Graceful degradation: sections hidden when Last.fm data unavailable
Lossy copy now supports MP3, Opus, and AAC (M4A) codecs with a
configurable dropdown in settings. Each codec uses the appropriate
ffmpeg encoder (libmp3lame/libopus/aac) and Mutagen tag writer
(ID3/Vorbis/MP4). Quality tag, filename substitution, and Blasphemy
Mode file cleanup all work per-codec. Backward compatible — existing
configs default to MP3.
SoulID worker generates deterministic soul IDs for all library entities:
- Artists: hash(name + debut_year) — searches iTunes + Deezer APIs,
verifies correct artist by matching discography against local DB
albums via MusicMatchingEngine, pools years from both sources and
picks the earliest. Falls back to hash(name) if no match found.
- Albums: hash(artist + album)
- Tracks: song ID hash(artist + track) + album ID hash(artist + album + track)
Dashboard button with trans2.png logo, rainbow spinner, hover tooltip.
Worker orb with rainbow effect. SoulSync badge on library artist cards.
DB migration adds soul_id columns with indexes to artists/albums/tracks.
Migration version flag auto-resets artist soul IDs when algorithm changes.
- Remove redundant enable checkbox — fallback dropdown is the enable
- Hydrabase option only appears in dropdown when connected
- Connect/disconnect dynamically adds/removes dropdown option
- _is_hydrabase_active checks fallback_source == hydrabase (not config toggle)
- Fallback client returns hydrabase_client when selected, iTunes if disconnected
- Auto-reconnect respects fallback selection for dev_mode handling
- hydrabase added to settings save service list for persistence
- Status shows green Connected on page load when auto-connected
Add Hydrabase section to Settings → Connections with enable toggle,
WebSocket URL, API key, auto-connect, and connect/disconnect button.
_is_hydrabase_active() now checks hydrabase.enabled config in addition
to dev_mode — either path activates it. Default disabled, zero change
for existing users. Dev admin page stays behind dev mode password.
Replace fixed primary/secondary hybrid dropdowns with an ordered list
of all 5 download sources. Users enable/disable each source and reorder
with up/down arrows to set download priority. Sources are tried in
order until one returns results.
- New hybrid_order config field (backward compat with legacy primary/secondary)
- Download orchestrator loops ordered list with per-source error handling
- Sortable source list UI with icons, toggle switches, priority numbers
- Source-specific settings shown for all enabled hybrid sources
- Seamless migration from legacy 2-source to N-source format
Replace 3-column glassmorphic card wall with centered single-column
tabbed interface. Horizontal pill tab bar (Connections, Downloads,
Library, Appearance, Advanced) with category switching.
- Kill glassmorphic cards, accent gradient bars, and box shadows
- Clean section headers with subtle dividers
- Horizontal setting rows (label left, control right)
- Custom styled select dropdowns with SVG arrow
- Quality Profile moved into Downloads tab (conditionally visible)
- Help text wraps to new line below controls
- Path inputs and template inputs properly styled
- Mobile responsive (rows stack, tab bar scrolls)
- Zero functional changes — all element IDs and JS logic preserved
Search results now show switchable tabs for alternate metadata sources.
Primary source renders immediately, alternate sources load in parallel
and tabs appear progressively as each completes.
- New /api/enhanced-search/source/<name> endpoint for per-source queries
- Source-aware routing via ?source= param on discography, album tracks,
album detail, and artist image endpoints (prevents numeric ID
misrouting between iTunes and Deezer)
- Source override stored on artistsPageState for consistent navigation
- Tabs styled with source brand colors, show result counts
- All additive — users who ignore tabs see zero behavioral change
- Add max_peer_queue setting to skip peers with long queues (soft filter
with fallback to unfiltered if all results removed)
- Add download_timeout setting replacing hardcoded 10-minute limit
- Include quality_score (peer health: upload speed, free slots, queue
length) in result ranking — was calculated but never used in sort key
- New UI controls in Soulseek settings section
Replace category-based tag settings (10 toggles) with per-tag controls
grouped by source service in an accordion UI. Each of the 11 service
groups (Spotify, iTunes, MusicBrainz, Deezer, AudioDB, Tidal, Qobuz,
Last.fm, Genius, General) has a master toggle that disables all child
tags, with individual toggles for fine-grained control. ISRC and
copyright fallback chains are now per-source toggleable. Genre merge
contributions from each source are independently controllable. All
tags default to enabled for backward compatibility.
Post-processing now writes all 18 MusicBrainz tags that Picard writes:
Release Group ID, Album Artist ID, Release Track ID, Release Type,
Status, Country, Original Date, Media, Barcode, Catalog #, ASIN,
Script, Total Discs (plus the 5 already supported). One cached API
call per album via get_release with recordings include.
New "Tags to Embed" settings section with 10 category toggles (all
enabled by default): MusicBrainz IDs, Release Info, Source IDs, ISRC,
BPM, Mood & Style, Copyright & Label, Genre Merging, URLs, Quality.
Each shows inline description of what it includes.
New dropdown in Soulseek settings lets users filter out slow peers at
search time (Any/1/2/3/4/5/10 Mbps). Passes minimumPeerUploadSpeed
to slskd API in bytes/sec.
Also fixes quality scoring tiers which were using wrong units — old
thresholds (5000, 1000, 500) treated bytes/sec values as kbps,
making speed scoring effectively meaningless. Now uses correct
bytes/sec thresholds based on real peer data.
- Fix enrichment progress never updating: remove `continue` that skipped
progress_callback for successful tracks in enrich_chart_tracks
- Split chart/extract into two-step flow: extract raw tracks, then enrich
via polling endpoint with live progress overlay updates
- Move Beatport enrichment cache to persistent metadata cache system
- Fix metadata cache detail modal for Beatport (URL entity_ids with slashes)
- Add per-source Clear dropdown (Spotify/iTunes/Deezer/Beatport) to cache browser
- Remove debug logging from enrichment progress tracking
- Persistent download bubble cards on Beatport page and dashboard,
matching the existing artist/search bubble UX with click-to-reopen,
green checkmark on completion, and snapshot persistence
- In-memory enrichment cache (2h TTL, thread-safe) skips re-scraping
when the same chart is clicked twice
- Batch enrichment replaces one-by-one HTTP requests with a single
POST, using WebSocket progress events for overlay updates
- Fix _beatportModalOpening guard blocking modal open after fast
cached enrichment by resetting the flag in openBeatportChartAsDownloadModal
- Hide Browse/My Playlists tabs — Browse is now the only Beatport view
- New sync_history DB table tracks last 100 syncs with full cached context
- Records history for all sync types: Spotify, Tidal, Deezer, YouTube,
Beatport, ListenBrainz, Mirrored playlists, and Download Missing flows
- Sync History button on sync page with modal showing entries, source
filter tabs, stats badges, and pagination
- Re-sync button: server syncs expand card inline with live progress bar,
matched/failed counts, and cancel button; download syncs open download modal
- Re-syncs update the original entry (moves to top) instead of creating duplicates
- Delete button (x) on each entry with smooth remove animation
- Fix mirrored playlist source detection (youtube_mirrored_ matched youtube_)
- Fix broken server import thumbnails with URL validation
New toggle under Post-Download Conversion: automatically converts 24-bit
or high sample rate FLAC files to 16-bit/44.1kHz after download, replacing
the original. Uses ffmpeg with temp file + verify + atomic swap for safety.
Runs before lossy copy so MP3s are made from the downsampled version.
Also prevents bit depth strict mode from rejecting files that will be
downsampled anyway.
- Switch user automations to 2-column grid layout (matches system automations)
- Add duplicate button on non-system cards with POST /api/automations/<id>/duplicate
- Add search/filter bar (text search + trigger/action dropdowns) shown at 6+ automations
- Add Inspiration section with 8 starter templates that pre-fill the builder
- Add folder-style automation grouping with group_name DB column, dropdown
popover for assignment, collapsible group sections, and builder group input
- Dark translucent background with backdrop blur instead of bright colored fills
- Animated flowing gradient border using CSS mask-composite technique
- Color-tinted labels and count badges (amber for watchlist, accent for wishlist)
- Shimmer sweep clipped inside button bounds
- Structured HTML: separate icon, label, badge, and shimmer elements
- Badge pulses when count > 0
- Worker orbs: 7s delay before collapsing back after mouse leaves header
Worker buttons shrink to floating colored orbs with physics-based
movement, spark emissions from active workers, connection lines, and
center gravity. Hovering the header expands orbs back to full buttons
with staggered spring animation. Desktop only, toggleable in Settings
under UI Appearance.
Per-job and per-status filtering — respects active toolbar filters so
users can clear e.g. only track number findings or only dismissed items.
Confirmation uses the app's styled modal instead of browser confirm().
Users can now choose between iTunes/Apple Music and Deezer as their free
metadata source in Settings. Spotify always takes priority when authenticated;
the fallback handles all lookups when it's not.
Core changes:
- DeezerClient: full metadata interface (search, albums, artists, tracks)
matching iTunesClient's API surface with identical dataclass return types
- SpotifyClient: configurable _fallback property switches between iTunes/Deezer
based on live config reads (no restart needed)
- MetadataService, web_server, watchlist_scanner, api/search, repair_worker,
seasonal_discovery, personalized_playlists: all direct iTunesClient imports
replaced with fallback-aware helpers
Database:
- deezer_artist_id on watchlist_artists and similar_artists tables
- deezer_track_id/album_id/artist_id on discovery_pool and discovery_cache
- Full CRUD for Deezer IDs: add, read, update, backfill, metadata enrichment
- Watchlist duplicate detection by artist name prevents re-adding across sources
- SimilarArtist dataclass and all query/insert methods handle Deezer columns
Bug fixes found during review:
- Similar artist backfill was writing Deezer IDs into iTunes columns
- Discover hero was storing resolved Deezer IDs in wrong column
- Status cache not invalidating on settings save (source name lag)
- Watchlist add allowing duplicates when switching metadata sources
Saves successfully loaded playlist URLs to localStorage and displays
them as clickable pills between the input bar and playlist container.
Clicking a pill re-loads that URL; X button removes it. Max 10 per
source, most recent first. Source-colored hover accents match each
tab's brand styling.
Also fixes duplicate playlist bug — YouTube and Spotify Public now
check for already-loaded playlists before making API calls, preventing
broken duplicate cards when the same URL is entered twice.
New library_history table logs every completed download and every new
track imported from Plex/Jellyfin/Navidrome. A "History" button next
to "Recent Activity" on the dashboard opens a modal with Downloads and
Server Imports tabs, album art thumbnails, quality/source badges, and
pagination.
New download mode alongside Soulseek, YouTube, Tidal, and Qobuz. Uses
community-run REST API instances (no auth required) that serve Tidal CDN
FLAC streams. Features quality fallback chain (hires→lossless→high→low),
automatic instance rotation on failure, and full hybrid mode support.
Also fixes 6 missing streaming source checks for HiFi and Qobuz in the
frontend that were blocking playback with "format not supported" errors.
Scrapes Spotify's embed endpoint to extract track data from any public
playlist or album URL. Full discovery flow with Deezer parity: parse →
card → discovery modal → live progress → sync → download missing.
- New scraper: core/spotify_public_scraper.py (embed endpoint parsing)
- 12 API endpoints mirroring Deezer's discovery/sync/download flow
- WebSocket live discovery updates via spotify_public_discovery_states
- Green-branded tab, cards, and input styling (#1DB954)
- Album vs playlist detection with distinct card icons (💿/🎵)
- Download persistence: card click reopens download modal after close
- Card phase reset on cancel/completion (closeDownloadMissingModal)
- Backend download linking for spotify_public_ and deezer_ prefixes
- Completion handlers (V2 + no-missing-tracks) for both platforms
- Source URLs stored on mirrored playlists for future auto-refresh
Southern hemisphere users now see correct seasons (e.g. March = Autumn,
December = Summer). Holidays (Halloween, Christmas, Valentine's) stay
calendar-fixed regardless of hemisphere.
- Hemisphere dropdown in Discovery Pool Settings
- GET/POST /api/discovery/hemisphere endpoints
- Season detection offsets months by 6 for southern hemisphere
- Stored in metadata table, defaults to northern
- Add whisoul.png to modal header with subtitle text and gradient background
- Responsive: smaller logo on mobile, hide subtitle
- Share _resolve_file_path in repair_worker for cross-environment path compat
- Use path resolution in orphan and duplicate file deletion
- Guard directory cleanup against removing the transfer folder itself
- Restore correct button label text on fix error recovery
- Add findings dashboard with summary stats and per-job clickable filter chips
- Redesign findings cards with expandable detail panels and per-type renderers
- Redesign history tab with status dots, stat pills, and full timestamps
- Fix dead file cleaner false positives by using suffix-based path resolution
- Fix orphan file detector false positives by matching via path suffixes
- Add help text modal for each repair job card
- Enlarge maintenance modal (1100px wide, 90vh tall)