Release history
SoulSync releases
Automated Music Discovery and Collection Manager
All releases
29 shown
- Existing Tidal OAuth tokens will return 401 once and require reconnection with the new collection.read scope.
- Docker image no longer auto‑downloads ffmpeg, reducing container size.
- Manual search bar in the failed-track candidates modal
- Shared `createDiscoverSectionController` lifecycle wrapper unifying all Discover page sections
Full changelog
SoulSync 2.5.0 — Release
dev → main. Minor bump (new features + fixes, no breaking changes).
Summary
Three new features, eleven fixes, one packaging change.
New features
- Tidal Favorite Tracks as a virtual playlist — favorited tracks (Tidal's "My Collection") now show up alongside real playlists on the sync page, same treatment Spotify gets for "Liked Songs". Reporter
yug1900on issue #502 located the working endpoint after the prior/v2/favoritesattempt returned empty data. Newcollection.readOAuth scope; existing tokens hit a 401 once and the sync page surfaces a "reconnect Tidal to enable" placeholder card with a hint pointing at Settings. New Disconnect button +prompt=consenton the OAuth flow so re-auth actually picks up new scopes. - Manual search in the failed-track candidates modal — when a download fails or returns "not found", the modal now has a search bar. Type any query, hit search, get fresh results from the configured download sources without restarting the whole download flow. Source picker is smart: single-source mode shows a label, hybrid mode shows a dropdown with "all sources" default. Results stream in via NDJSON as each source completes. Manual picks tagged with
_user_manual_pickso the auto-retry monitor leaves them alone — failure surfaces to the user instead of getting silently fallen back. - Discover section controller — every section on the discover page (recent releases, your artists, your albums, seasonal, fresh tape, archives, etc.) was reimplementing the same lifecycle by hand. ~30 sections all subtly drifting — different empty messages, different error handling, different sync-status icons, no consistent error toast. Lifted the lifecycle into a shared
createDiscoverSectionControllerfactory. Renderers stay per-section because section data shapes legitimately differ (album cards vs artist circles vs playlist tiles vs track rows); the controller is the wrapper, not a forced visual abstraction. 32 node-test pinning the controller contract.
Fixes
- Manual import: stop writing "Unknown Artist / album_id / 0 tracks" garbage (issue #524,
radoslav-orlov) — click handler droppedsource+album_name+album_artistfrom the match POST. Backend then guessed source via primary-source priority chain, all returned None, fell through to a failure-fallback dict with the album_id as the title. - Multi-disc albums no longer lose half the tracks — caught while testing #524 with Mr. Morale & The Big Steppers. Quality-dedup keyed on
track_numbercollapsed multi-disc releases to one disc's worth of files BEFORE the matcher ran. Track-number scoring bonus also fired across discs causing the wrong file to "win" the match. Both fixed by switching to(disc_number, track_number)tuples. - Auto-import: SoulSync standalone library now gets full server-quality rows — context dict had no
sourcefield, sorecord_soulsync_library_entrycouldn't pick the right source-id column. Every auto-imported track landed with NULL onspotify_track_id/deezer_id/ etc., and watchlist scans re-downloaded them on the next pass. Plus genre-tag aggregation onto artists row, ISRC/MBID type hardening, album duration as album total (not first-track duration), and conservative re-import UPDATE path that fills empty columns without clobbering populated ones. - AcoustID scanner: multi-artist songs no longer flagged as wrong (
foxxify) — scanner used rawSequenceMatcheragainst the primary artist while AcoustID returns the full credit. Lifted into sharedcore/matching/artist_aliases.py::artist_names_matchwith credit-token splitting on common separators. - AcoustID scanner: compilation albums no longer flag every track (
skowl) — scanner SQL joinedartistsviatracks.artist_id(album artist, not per-track).tracks.track_artistcolumn was already populated correctly by every server scan + auto-import path. Switched the SELECT toCOALESCE(NULLIF(t.track_artist, ''), ar.name). - Cross-script artist names no longer quarantine files (issue #442,
afonsog6) — Hiroyuki Sawano vs 澤野弘之, Sergey Lazarev vs Сергей Лазарев, etc. Verifier compared expected vs actual with raw_similarity(0% — no shared chars) and never consulted MusicBrainz aliases. Newcore/matching/artist_aliases.pyhelper +artists.aliasescolumn populated by MB enrichment + multi-tier resolver (library DB → cache → live MB) so the verifier finds aliases for un-enriched artists too. - Library Reorganize: stop leaving orphan audio files behind + hint for Unknown-Artist rows (
foxxify) — lossy-copy users hadtrack.flacANDtrack.opusside-by-side at the source; reorganize moved the canonical, left the orphan, and the empty-folder cleanup never fired. Plus the placeholder-metadata rows from the pre-#524 manual-import bug couldn't be relocated and emitted a misleading "run enrichment first" hint. Both addressed in the same PR. - Plex: library scan trigger no longer fails on non-English section names (issue #535,
adrigzr) —trigger_library_scanignored the auto-detectedself.music_libraryand calledlibrary.section("Music")with hardcoded English fallback. Música / Musique / Musik / Musica / 音乐 / موسيقى all hitNotFound. - Search for match: no more karaoke / cover / "originally performed by" junk at the top (issue #534,
radoslav-orlov) — newcore/metadata/relevance.pyreranks results locally with cover/karaoke/tribute penalties + exact-artist-match boost + variant-tag penalty (skipped when user explicitly typed the variant). Applied at deezer + itunes + spotify search-tracks endpoints. - Deezer cover art no longer looks blurry (
tim) — Deezer's API returnscover_xlURLs at 1000×1000 but the underlying CDN serves up to 1900×1900 by rewriting the size segment in the URL path. New_upgrade_deezer_cover_urlhelper mirrors the spotify scdn / iTunes mzstatic upgrade pattern. - Discover: stop showing undownloadable tracks — five discovery_pool selection methods had no
WHERE source_id IS NOT NULLgate. User clicked download on a track with no source IDs → silent failure. Lifted into shared_select_discovery_trackswith the gate hard-coded so every public method inherits it. - Discover: source-aware popularity, library dedup, SQL genre filter —
popularitythresholds were spotify-shaped (0–100); deezer writes its rank value (often six-digit integers). New_get_popularity_thresholds(source)returns per-source values. Genre filter pushed down into SQL viaLIKEplaceholders. Discovery selectors now exclude tracks the user already owns via correlatedNOT EXISTSsubquery.
Packaging
- Stop docker image bloat from auto-downloaded ffmpeg — yt-dlp / pydub probe for ffmpeg at import time and download a static binary if the system one isn't found. Container image grew accordingly. Added an explicit
is_availablegate so the auto-download path stays disabled in container builds.
Files / scope
50+ commits, 9 merged PRs (#525, #531, #532, #536, #539, #540, #541, #542, #543, #544, #545, #546). See webui/static/helper.js '2.5.0' block in WHATS_NEW for full per-feature breakdown.
Test plan
- [x]
pytestfull suite green on tip of dev (2639 tests at last run, +94 since 2.4.3) - [x] No new ruff lint findings
- [x] Token scope verified manually after Tidal disconnect+reauth — includes
collection.read - [x] Reorganize tested on a multi-format album (.flac + .opus side-by-side) — both files end up at destination
- [x] Manual import via search modal verified writes correct artist/album/track to library DB
- [ ] Reviewer smoke test: load sync page, verify Tidal Favorite Tracks card appears with real count
- [ ] Reviewer smoke test: open the failed-track candidates modal, verify manual search bar appears + returns results
- [ ] Reviewer smoke test: discover page loads all sections without error toasts
Version
_SOULSYNC_BASE_VERSION bumped from 2.4.3 → 2.5.0. Sidebar, version modal, update check, backup metadata all read from this constant.
Reporters credited
yug1900 (#502), radoslav-orlov (#524, #534), afonsog6 (#442), adrigzr (#535), foxxify (Discord — reorganize, AcoustID multi-artist), skowl (Discord — AcoustID compilation), tim (Discord — Deezer cover art), bafoed (#499, #500 from prior 2.4.3 line).
- All Plex libraries can be combined into a single "all libraries (combined)" view with one API call.
- Discogs collection can be imported as a source in Your Albums after setting a Discogs token.
Full changelog
2.4.2
Headlines
- Big sync sessions no longer wedge after 2-3 hours. slskd had no http timeout, so when it hung the worker thread blocked forever. Bounded the timeout, downloads keep flowing. (#499)
- Plex: all libraries combined. new "all libraries (combined)" option in plex settings — works for users with multiple music libraries (e.g. plex home families). One api call, plex aggregates. (#505)
- Discogs collection in Your Albums. set your discogs token, add it as a source, your full discogs collection shows up in discover.
- SoundCloud as a download source. plug it into the existing source picker — works alongside soulseek / youtube / tidal / qobuz / hifi / deezer / lidarr.
- Top tracks download on artist page. the "popular" sidebar on artist detail now pulls top tracks from your primary source (spotify / deezer) with per-row + bulk download buttons. itunes / discogs / musicbrainz still falls back to the last.fm display. (#513)
Bug Fixes
- AcoustID stops letting instrumentals through as vocal tracks. title-similarity check was stripping "(Instrumental)" / "(Live)" / etc before comparing — vocal vs instrumental versions both normalized to the same string and passed. Now detects version on each side first, rejects mismatches. Discord report.
- Library reorganize no longer misclassifies album tracks as singles. rewrote to delegate to the per-album planner the artist-detail modal already uses — db-driven, knows the album has n tracks regardless of how many sit in the transfer folder. (#500)
- Enrich now honors manual album matches. clicking enrich after manually matching an album would overwrite your match with whatever the worker's fuzzy search returned. Now reads the stored id first, fetches directly. (#501)
- HiFi instance add no longer errors with "no such table". defensive lazy-create on every CRUD method — self-heals when the bulk db init rolled back the create due to a later migration failure. (#503)
- Download Discography no longer shows the wrong artist's albums. clicking discog on 50 cent could show young hot rod's albums. Now resolves per-source ids from the library row.
- Enhance Quality matches Download Discography behavior. uses stored source ids (spotify / deezer / itunes / soul) for direct lookup instead of fuzzy text — same path discog already used. Falls back to text search only when no ids are stored.
- Watchlist no longer re-downloads compilations / soundtracks. album-name fuzzy check now strips qualifier parentheticals (music from..., ost, deluxe edition, etc) before comparing. One user reported the same song downloaded 7 times — fix kills the loop.
- Watchlist no longer re-downloads tracks already on disk. added matching by stable external id (spotify / deezer / itunes / tidal / qobuz / musicbrainz / audiodb / hydrabase / isrc) before falling through to fuzzy.
- ReplayGain wrote +52 dB to every track. parser was reading the per-window value at t=0.5s (silent intro) instead of the integrated loudness from ffmpeg's summary block. Now reads the right value.
- Tracks no longer show "completed" when the file was actually quarantined. integrity rejection now correctly marks the task failed. Discord report on Mr. Morale download.
- Lossy copy now deletes original FLAC when configured. setting was being read but ignored.
- Repair card "X findings" badge now reflects current state. showed historical "found in last scan" count even after bulk-fix moved findings to resolved. Now shows pending count when > 0, muted "X found in last scan" when zero.
- Auto-import handles multi-disc folders + featured-artist tag drift. kendrick deluxe rip (Disc 1/Disc 2 only) and albums with varying per-track artists now classify correctly.
- Lidarr right-track-lands fix + metadata profile lookup. matched against lidarr's tracklist instead of taking the first imported file.
- Tidal auth error 1002 for docker / remote access. dropped the request-host fallback that overrode the documented redirect uri.
- Reject broken slskd files before tagging. size sanity, mutagen parse, duration agreement (3s tolerance) — broken files quarantined instead of polluting the library.
- MBID mismatch detector + persistent release cache. stops navidrome from splitting albums when tracks carry different MUSICBRAINZ_ALBUMID tags.
- Album completeness auto-fill works on docker / shared library setups. path resolver now searches the configured library paths and plex-reported library locations, not just transfer + downloads. (#476)
- Search picker no longer defaults to spotify on non-admin profiles. read from
/statusinstead of the admin-only/api/settings. (#515) - Stop swallowing exceptions silently. ~330 silent
except: passblocks across the codebase now log to debug — failure paths are inspectable instead of disappearing. Ruff S110 enabled to catch regressions. (#369)
Changes
- Discogs primary source gated by token — no token = revert gracefully instead of erroring.
- Spotify worker pauses on non-spotify primary — stops burning api budget when not selected.
- Per-track sources persisted at download time — for plex / jellyfin / navidrome users, source ids no longer wait for enrichment.
- Auto-import live per-track progress in history — see "track 3/14" while it processes instead of waiting 5+ minutes for an empty card.
- Sidebar library button shows artist breadcrumb when viewing an artist detail page.
- Library disk usage stat on the stats page (uses media-server-reported file_size, no filesystem walk).
- Beatport tab hidden — cloudflare turnstile broke the scraper. Backend kept for future revival.
Internals
- Major web_server.py decomposition — ~30 routes / workers / helpers extracted into focused modules under core/.
- Download engine refactor — plugin contract + central worker / state / fallback. ~700 LOC removed across client files. Adding a new source (e.g. usenet) now needs one client + one registry entry.
- Media server engine — owns plex / jellyfin / navidrome / soulsync clients via a shared contract, generic accessors instead of per-client globals.
- Centralized metadata source selection —
core/metadata_service.pyis the single source of truth. - Enrichment bubble routes consolidated — every
/api/<service>/<action>flows through one registry-driven endpoint instead of 30 hand-rolled routes.
- Socket.IO same-origin default enforced
- /api/settings endpoint restricted to admin users only
- Duplicate detector catches slskd dedup orphans via canonical filename stem
Full changelog
Version 2.4.1
Patch release. ~144 commits since 2.4.0.
Highlights
Watchlist no longer re-downloads compilation tracks. Strict 0.85 album-name fuzzy match was failing on every soundtrack/compilation because Spotify and the media-server scan name them differently. One user got 7 copies of the same song. Now strips qualifier parentheticals (Music From..., OST, Deluxe Edition, Remastered) and uses a relaxed threshold with a volume/disc/part guard.
Duplicate detector catches slskd dedup orphans. Files like Song_<19-digit-timestamp>.mp3 got bucketed apart from the canonical Song.mp3 because of inconsistent media-server tag parsing. Added a second pass that re-buckets by canonical filename stem with the slskd suffix stripped.
Slskd dedup cleanup after import. Stops new orphans from being created in the first place — every successful import now prunes timestamp siblings of the canonical filename in the source folder.
Spotify auth flow reworked. Cleaner connection states, no more completion-sync race, real toasts on failure, simpler service status reads. Spotify worker pauses when Spotify isn't your primary source. Daily call budget capped at 500.
Match engine fixes. Featured-artist tracks and soundtrack tracks now match correctly during discography completion checks (used to be reported missing because they were matched against the wrong artist).
Provider-neutral wishlist + quality scanner. Both used to hardcode Spotify; now respect your configured primary metadata source. Bulk watchlist add falls back through every cached source ID before failing.
Parallel singles import (3 workers). Long backlogs finish ~3x faster.
Beatport tab hidden temporarily. Cloudflare Turnstile blocks the scraper, official OAuth API closed to public devs. Backend kept in code, revival is one HTML edit.
Performance. Service worker for cover art + installable PWA. Static assets 1y browser cache, discover pages 5min.
Security. Socket.IO same-origin default. /api/settings admin-only.
Bug round-up. #434 (config DB lock spam), #399 (bulk discography source context), Tidal auth port shown wrong, Discogs gracefully handles missing token, automation errors surface in last_error, etc.
Internal. Major web_server.py decomposition — ~30 routes / workers / helpers lifted into focused modules under core/. Search endpoints alone shed 612 lines + 94 new tests; automation endpoints shed 383 + 72 new tests.
- Reorganize modal no longer accepts per-call template override; must use configured download template instead
- Unified search with per-source icon picker reducing API calls 6-7×
- Reorganize FIFO queue with live status panel and 'Reorganize All' backend call
- Reorganize routed through download pipeline with proper multi-disc and AcoustID handling
Full changelog
SoulSync v2.4.0
First release on proper 3-part semver. Two big projects: Search & Artists unification, and the Library Reorganize FIFO queue. Plus bug fixes from production reports and PR reviews.
Highlights
- Unified search with per-source icon picker. Search page and global popover both have one icon per source (Spotify, Apple Music, Deezer, Discogs, Hydrabase, MusicBrainz, Music Videos, Soulseek). Typing only searches the active source; results are cached per query. Roughly 6–7× fewer API calls per search vs. the old fan-out default.
- Reorganize FIFO queue with live status panel. Buttons stay clickable; spam-clicks dedupe. A backend worker drains items one at a time. Status panel shows active progress, queued count, and recent finishes; expands to a per-item list with cancel buttons. "Reorganize All" is now one backend call instead of N JS-driven calls.
- Reorganize routed through the download pipeline (winecountrygames). 3-disc albums no longer collapse to single-disc; tracks no longer silently disappear. Same template, tagging, multi-disc subfolder logic, and AcoustID verification as fresh downloads.
- Album Completeness job actually finds incomplete albums (sassmastawillis). New
api_track_countcolumn populated by metadata workers gives the job a real expected total to compare against. - Tidal: no more silent quality downgrades (Netti93). With "HiRes only, no fallback", tracks were downloading as m4a 320kbps. Now compares returned
audio_qualityagainst requested tier and rejects downgrades. - MusicBrainz search rewritten. Artist search re-enabled, track/album search uses artist-first browse instead of literal title match, cover art uses deterministic URLs instead of HEAD probes. ~3 sec on cold cache vs. 30+ before.
Bug Fixes
- Spotify post-ban cooldown bumped 5 min → 30 min. First call after cooldown was getting re-banned within 32 seconds.
- Discover hero "View Discography" 404 — click handler stopped passing
sourceto/api/artist-detail. - Clean Search History automation crashing with
'DownloadOrchestrator' object has no attribute 'base_url'. - Reorganize-preview Apply button getting stuck disabled on errors / early returns.
- Soulseek handoff from global search going through metadata flow instead of basic file search.
- Stale search requests flashing empty results on fast retype.
- Reorganize queue concurrency fixes: atomic worker pick + status flip;
Lock + Eventreplaced withthreading.Condition;enqueue_manydeduplicates within a batch. - DB helpers swallowing query errors as "album not found" — now lets exceptions bubble.
UX
- Rate-limit fallback banner when the backend swaps your selected source for a working one.
- Soulseek icon dimmed when slskd isn't configured; clicking it routes to Settings → Downloads.
- Search results restore on navigate-back from
/search. - "Show / Hide Results" toggle removed; visibility is a function of query state.
- Artist detail back button uses browser history instead of dumping you on an empty Artists page.
- Embedded Download Manager removed from Search page (~330 lines of dead code).
- Artists sidebar entry retired (duplicated unified Search). Old
/artistsURL still resolves. - Search page renamed to
/search. Old/downloadsURL still resolves; profile ACLs migrate.
Internals
- Shared
createSearchControllerfactory inshared-helpers.js— Search page and global widget consume the same state machine. ~380 lines of near-duplicate code consolidated. /api/enhanced-searchaccepts an explicitsourceparam to skip backend fan-out; cache keys isolate per-source.- Reorganize logic extracted to
core/library_reorganize.py(~195 lines out ofweb_server.py, +13 unit tests). - Semver:
_SOULSYNC_BASE_VERSIONis2.4.0. ReplacedparseFloat()version comparison with a component-by-component comparator so future patch bumps are distinguishable.
Upgrade Notes
- Reorganize modal no longer accepts a per-call template override. Reorganize uses your configured download template instead.
- Album Completeness first scan after upgrade may be slow on un-enriched albums (live API fallback). Subsequent scans are fast.
- Old
/downloadsand/artistsURLs still resolve. No DB migration needed —api_track_countis added via the existing schema-migration path on first launch.
Full changelog: https://github.com/Nezreka/SoulSync/compare/v2.3...v2.4.0
- Music Videos search and download
- First-Run Setup Wizard
- Centralized Downloads page
- Wing It Mode: Download or sync playlists without metadata discovery, bypassing Spotify/iTunes/Deezer matching
- Server Playlist Manager: Compare and edit mirrored playlists side-by-side with title similarity scoring and album art
- Track Redownload: Fix mismatched downloads with three-step process and full pipeline parity
- Deezer download source
- Cache-powered discovery
- Listening stats dashboard
- Deezer metadata source
- HiFi lossless downloads
- Spotify link scraping
- Encrypted sensitive config values (API keys, passwords, tokens) at rest with Fernet
- Deezer/Tidal/Qobuz playlist sync
- Hybrid mode redesign with drag-drop priority
- Spotify rate limit protection with escalating bans
- Visual drag-and-drop automation builder with 20+ triggers
- Playlist discovery pipeline with fuzzy matching
- Notification integrations (Discord, Pushbullet, Telegram)
- Tidal as download source with quality selection
- REST API v1.0
- Incremental scan improvements
Update version to 1.6 in sidebar and API. Add local import, enhanced tagging, mobile layout, and performance improvements. Fix track popularity field access for upcoming Spotify API changes (February 2026). Loads of bug fixes and ongoing bug fixes in this release
- AcoustID verification with fuzzy matching
- Quarantine system for failed verifications
- Auto-downloaded fpcalc binary
- Apple Music fallback metadata source
- Separate discovery pools per service