Skip to content

Release history

SoulSync releases

Automated Music Discovery and Collection Manager

All releases

29 shown

Config change
2.6.5 Breaking risk

Web-player revamp + art source selector

No immediate action
2.6.4 Mixed

Year path bug + poll hang + UI buttons

No immediate action
2.6.3 Mixed

Dashboard UX + Auto‑Sync UI + download/wishlist fixes

No immediate action
2.6.2 Breaking risk

Sync-page tabs + Auto-Sync overhaul

No immediate action
2.6.1 Bug fix

Album download fix + MB variants

No immediate action
2.6.0 Breaking risk

Qobuz sync, Soulseek bundles, Stats React

No immediate action
2.5.9 Mixed

Torrent/Usenet support + bugfixes

No immediate action
2.5.7 Breaking risk

MusicBrainz fixes + subsystem reliability

No immediate action
2.5.6 Mixed

MusicBrainz primary + artist deep links + quarantine fixes

No immediate action
2.5.5 New feature

Manual Library Match + import + scorer

Config change
2.5.4 Breaking risk
Auth

Token leak fix

No immediate action
2.5.3 New feature

Multi-artist tag overhaul

No immediate action
2.5.2 New feature

Discography limit increase + album‑type fix

No immediate action
2.5.1 Breaking risk

Discography filter respect + skip existing

2.5.0 Breaking risk
⚠ Upgrade required
  • 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.
Notable features
  • 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

devmain. 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 yug1900 on issue #502 located the working endpoint after the prior /v2/favorites attempt returned empty data. New collection.read OAuth 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=consent on 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_pick so 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 createDiscoverSectionController factory. 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 dropped source + album_name + album_artist from 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_number collapsed 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 source field, so record_soulsync_library_entry couldn't pick the right source-id column. Every auto-imported track landed with NULL on spotify_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 raw SequenceMatcher against the primary artist while AcoustID returns the full credit. Lifted into shared core/matching/artist_aliases.py::artist_names_match with credit-token splitting on common separators.
  • AcoustID scanner: compilation albums no longer flag every track (skowl) — scanner SQL joined artists via tracks.artist_id (album artist, not per-track). tracks.track_artist column was already populated correctly by every server scan + auto-import path. Switched the SELECT to COALESCE(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. New core/matching/artist_aliases.py helper + artists.aliases column 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 had track.flac AND track.opus side-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_scan ignored the auto-detected self.music_library and called library.section("Music") with hardcoded English fallback. Música / Musique / Musik / Musica / 音乐 / موسيقى all hit NotFound.
  • Search for match: no more karaoke / cover / "originally performed by" junk at the top (issue #534, radoslav-orlov) — new core/metadata/relevance.py reranks 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 returns cover_xl URLs 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_url helper 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 NULL gate. User clicked download on a track with no source IDs → silent failure. Lifted into shared _select_discovery_tracks with the gate hard-coded so every public method inherits it.
  • Discover: source-aware popularity, library dedup, SQL genre filterpopularity thresholds 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 via LIKE placeholders. Discovery selectors now exclude tracks the user already owns via correlated NOT EXISTS subquery.

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_available gate 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] pytest full 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.32.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).

2.4.2 Breaking risk patches CVE-2023-4863
Notable features
  • 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 /status instead of the admin-only /api/settings. (#515)
  • Stop swallowing exceptions silently. ~330 silent except: pass blocks 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 selectioncore/metadata_service.py is 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.
2.4.1 Bug fix
Security fixes
  • Socket.IO same-origin default enforced
  • /api/settings endpoint restricted to admin users only
Notable features
  • 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.

2.4.0 Breaking risk
Breaking changes
  • Reorganize modal no longer accepts per-call template override; must use configured download template instead
Notable features
  • 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_count column 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_quality against 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 source to /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 + Event replaced with threading.Condition; enqueue_many deduplicates 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 /artists URL still resolves.
  • Search page renamed to /search. Old /downloads URL still resolves; profile ACLs migrate.

Internals

  • Shared createSearchController factory in shared-helpers.js — Search page and global widget consume the same state machine. ~380 lines of near-duplicate code consolidated.
  • /api/enhanced-search accepts an explicit source param to skip backend fan-out; cache keys isolate per-source.
  • Reorganize logic extracted to core/library_reorganize.py (~195 lines out of web_server.py, +13 unit tests).
  • Semver: _SOULSYNC_BASE_VERSION is 2.4.0. Replaced parseFloat() 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 /downloads and /artists URLs still resolve. No DB migration needed — api_track_count is added via the existing schema-migration path on first launch.

Full changelog: https://github.com/Nezreka/SoulSync/compare/v2.3...v2.4.0

2.3 Breaking risk
Notable features
  • Music Videos search and download
  • First-Run Setup Wizard
  • Centralized Downloads page
2.2 New feature
Notable features
  • 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
2.1 New feature
Notable features
  • Deezer download source
  • Cache-powered discovery
  • Listening stats dashboard
2.0 New feature
Notable features
  • Deezer metadata source
  • HiFi lossless downloads
  • Spotify link scraping
1.9 New feature
Security fixes
  • Encrypted sensitive config values (API keys, passwords, tokens) at rest with Fernet
Notable features
  • Deezer/Tidal/Qobuz playlist sync
  • Hybrid mode redesign with drag-drop priority
  • Spotify rate limit protection with escalating bans
1.8 New feature
Notable features
  • Visual drag-and-drop automation builder with 20+ triggers
  • Playlist discovery pipeline with fuzzy matching
  • Notification integrations (Discord, Pushbullet, Telegram)
1.7 Feature
Notable features
  • Tidal as download source with quality selection
  • REST API v1.0
  • Incremental scan improvements
1.6 Bugfix

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

1.5 New feature
Notable features
  • AcoustID verification with fuzzy matching
  • Quarantine system for failed verifications
  • Auto-downloaded fpcalc binary
1.4 New feature
Notable features
  • Apple Music fallback metadata source
  • Separate discovery pools per service

Beta — feedback welcome: [email protected]