soulsync 3.5.4: dev → main
subtitles grow up so you can drop bazarr, audiobooks get series follows and an m4b merge plex never sees half-built, music and video share one downloads page, usenet can pull a song out of an album release, scrobbling stops depending on the stats toggle, mirrors stop wiping themselves, your own playlists get covers, phones get a full layout pass, plus a stack of library, watchlist and import fixes. scope: everything on dev since 3.5.3.
subtitles, minus bazarr
- subtitle providers are pluggable now. opensubtitles is the first one, and providers are tried in order like download sources. every video x language is tracked as wanted, have or failed, so a miss gets retried later instead of lost for good. fetching is on by default for new imports (#1635).
- a wanted loop retries misses with backoff and a daily provider quota (20/day). scoring is hash first, then filename, and weak matches never download. every attempt is logged so the upgrade loop knows what it has (#1638).
- per-movie and per-show language overrides from the detail page, episodes inherit the show's. files get proper .hi.srt / .forced.srt / .hi.forced.srt names so plex and jellyfin pick them up. the loop upgrades a subtitle when a clearly better one shows up, and never touches one you picked yourself (#1643).
- manual search: see the scored candidates and download the one you want. your pick is marked and never auto-replaced (#1643).
- optional local whisper as the last resort. pick a model (tiny/base/small, default small at ~486MB), download it once, flip the toggle. it only transcribes when every provider misses, burns no quota, and a transcript can never replace a real subtitle (#1644).
- the provider chain is a drag-to-reorder settings widget, and an admin-only backfill runs your existing library with its own progress bar (#1668).
audiobooks
- follow a whole series, next to author and narrator follows. backfill every missing volume now (the default) or only new books, and new volumes land on the wishlist on the daily scan. editions collapse to one book per position, so a dramatised adaptation doesn't queue a second copy of every volume (#1599).
- optional m4b merge on import, off by default in audiobook settings. a book that arrives as many files becomes one .m4b with a chapter per file. aac copies without re-encoding when the specs match, and the originals are only deleted after the merged file verifies (thanks SeadogsBooty on discord) (#1648).
- the merge doesn't leave ghosts in plex. the book is built in a hidden staging folder and revealed with one atomic rename, so plex only ever sees the finished book. docker/nas mounts that can't rename across filesystems copy each file over atomically instead. a scan nudge goes out after the import, and a folder that belongs to a different release is refused before the merge, not after (thanks SeadogsBooty on discord) (#1664).
- complete books stop getting held for being a different narration than the wishlisted edition. the narration is checked first (release and folder titles, then file tags). a clearly different complete reading skips the runtime gate, an unknown one gets a loose 0.6x floor, so samples and truncated downloads still get caught. holds say which narration and which check applied (thanks SeadogsBooty on discord) (#1647, #1655).
- the audiobook wishlist wears the music design: same hero, stat strip (wanted / looking / in flight / in library / not found yet), a triage banner for failed titles and one sticky command bar. status badges stay readable over bright covers (#1675).
- clicking audiobooks or podcasts in the sidebar from a detail page goes back to the section home instead of doing nothing (#1648).
one downloads page
- music and video share one downloads page. a music | video switcher on top of /active-downloads, deep-linkable with ?media=video, and the old /video-downloads lands on the video view. review, history and the clients tab all carry over (thanks @c5pie) (#1651).
- opening downloads from the video side stays on the video side. the hand-off flipped you to the music shell, and the side toggle then re-ran the hand-off, so every video click bounced straight back to music. the page now shows inside the video shell (#1685).
- video wishlist drains stop throwing everything at the clients in one go. new "max downloads per run" on both wishlist automations, default 3. the rest stay wishlisted for the next run with no searches wasted, and the slot goes back if a client refuses a grab. rss and manual grabs aren't capped (thanks @c5pie) (#1650).
your own playlists
- your playlists get a cover, a description and a duplicate button. upload a cover (re-encoded square), use a track's art, paste a url, or remove it. the cover pushes to your media server on every sync, and duplicate copies tracks, cover and description (#1666).
usenet finds the album for the song
- usenet and torrents can use an album release to fill a single-song request. ask for "money", soulsync finds the dark side of the moon and imports money out of it. title, artist, version and duration pick the track, and if the files can't be told apart safely the import fails instead of guessing (thanks @nick2000713) (#1604).
- the same release on several indexers takes one slot before the candidate limit, with the others kept as sources. quality, editions, years and repacks stay separate (#1604).
- new per-profile option under settings > quality > album releases: "requested tracks only" stays the default, "also the album's other tracks that pass" keeps the other missing tracks from a download you already made. they go through the normal import checks, and a failed extra never starts another download (#1604).
scrobbling
- scrobbling stopped depending on the listening stats toggle. the flush runs every 5 minutes on its own, gated only by each service's scrobble setting and credentials. the toggle only controls stats polling, like the label says (thanks @cin) (#1654).
- the web player scrobbles by last.fm's rule: half the track or 4 minutes, whichever comes first, 30s minimum. 10 seconds still counts as a play for soulsync's own stats (thanks @cin) (#1654).
- scrobble timestamps are stored as utc instead of being read as local time (#1654).
mirror safety
- a failed spotify playlist fetch no longer refreshes a mirror to empty. any fetch error used to come back as an empty playlist, and the refresh deleted the mirror's tracks to match while logging "no changes". since february that's every followed playlist, because spotify 403s them. a failed fetch is an error now and the stored tracks stay put, and a 403 logs that spotify only serves playlists you own or collaborate on (thanks @splitsec2) (#1657).
- posting an empty track list to /api/mirror-playlist no longer wipes a mirror that has tracks. that's what a failed client-side fetch looks like, so it 400s. new and already-empty mirrors can still save zero tracks (thanks @splitsec2) (#1659).
library and matching
- the dashboard's in-library count sees deezer, tidal and qobuz tracks, not just spotify ones, and compilations count through the per-track artist. profile 1's downloads also stopped reading as missing: they were stamped with owner 1 while profile 1 reads the shared scope, so the missing-tracks analysis queued re-downloads of things you had (thanks Spectro) (#1660, #1662).
- wishlist batches with no real album id stop poisoning every track's tags. one batch filed a whole pile of tracks under a bogus "music" album by nickelback because it trusted the first row's album for all of them. the album is checked against each track now, last.fm and genius look up the track's artist, and a repair finding flags albums shaped like this so existing libraries can be cleaned up (thanks @kevin2xk) (#1620).
- playlist sync matches again when the candidate pool comes back empty. empty means "no info" now and falls through to search instead of "no match", compilation credits match through the per-track artist, and the wishlist step logs when it adds 0 of N instead of failing silently (thanks @zikasak) (#1624).
- the soulseek scorer stops accepting a wrong song that only shares a version tag ("damnation flame (orchestral version)" for "crystalline (orchestral version)"). acoustid handles dash- and tilde-wrapped subtitles, and a same-title recording in another script skips instead of failing, so correct files stop landing in quarantine (thanks @zikasak) (#1630).
- album release year findings apply on navidrome libraries. text ids were cast to int and every finding 400'd (thanks @Logan-Selley) (#1634).
- the album you pinned as canonical is the one everything uses, so redownload pulls the pinned edition too (thanks @kiwijar) (#1670).
- the album picker stops grabbing tribute albums for originals (a wishlisted tribute still works). a song actually titled "live again" stops getting the live-version penalty, and the pre-download duration check gives the same 15s allowance the post-download one did (thanks @TheJimmerJammer123) (#1641).
watchlist
- the scanner only wishlists tracks credited to the artist you watch. watching metallica was wishlisting royal blood and st. vincent covers off tribute albums and soundtracks. features count, tracks with no artist info still go through, and a new "other artists" toggle (per artist and global, off by default) brings back the old behavior (thanks SeadogsBooty on discord) (#1636).
- the scan deck stops vanishing a second after it shows up. manual scans and the live feed kept status in two places that never synced. they share one now, which also fixes cancel on auto-scans (#1645).
- discover's "based on your listening" row gets a view all button like the artists row (#1642).
quarantine and imports
- quarantine tells you when a song already arrived another way: a muted icon when the same recording is already in your library, or the item was just an extra track from an album import. live and remix versions, conflicting isrcs and deleted copies don't count, and it says unknown instead of guessing (thanks @nick2000713) (#1653).
- slowed, sped up and nightcore are their own versions. "devil eyes - slowed" and "devil eyes" both read as the original, so the slowed cut could land as the studio track. the scorer and acoustid both reject the mismatch now (thanks @nick2000713) (#1653).
- approving a file from quarantine actually keeps it. the re-import treated the approved file as a redundant copy and deleted it. approve counts as a replace now, still behind the short-file guard (thanks @nick2000713) (#1653).
- discogs dates keep their full precision, and a correct full date in the file isn't flagged when the provider only knows the year (thanks @nick2000713) (#1653).
- imports skip what you already own. each file is checked against the library before any transcoding, tagging or moving, and the queue says why it skipped. real upgrades still go through the upgrade path (thanks SeadogsBooty on discord) (#1667).
- the import inbox stays honest during a staging scan: files mid-scan stop showing the wrong status, plus cross-mount path and pathless duplicate fixes (#1679).
- backup rotation can't throw away the last real backup. an interrupted backup left a 0-byte file that sqlite reads as a healthy empty database, and rotation kept it as the newest. empty files don't count as healthy now, and a failed copy cleans up after itself (thanks @nick2000713) (#1653).
webui
- the ui follows your accent color instead of hardcoded spotify green. 184 accent greens became var(--accent), and status greens (owned, matched, success, running) moved to a --success token so they stay green under a purple accent. spotify brand marks keep their green (thanks @Thundernerd) (#1617).
- per-feature palettes collapse onto the global tokens, and 9,656 white-alpha and 4,583 border-radius literals move onto the token ramp (thanks @Thundernerd) (#1625).
- dead css goes, a few thousand lines of it: the retired enhanced search views, the unused wishlist overview modal, artists page and hydrabase rules, the old dashboard quick actions, rate gauges and enrichment chips. a class-reference checker guards against string-built class names, and every deletion was screenshot-compared (thanks @Thundernerd) (#1631, #1649, #1669).
- scrollbars fade in while you scroll and out a second after you stop, app-wide on desktop and pwa (thanks @Thibs) (#1673). horizontal rails get an edge fade and brighter thumbs so they read as scrollable (#1637).
phones
- a full phone pass from 22 screenshots. buttons stop stretching into ovals, the watchlist rail scrolls, tools, downloads, logs and quality fit the screen, settings titles and open modals clear the menu button, the player's play button is round and the eq is reachable, and the dashboard listen cards stack instead of clipping mid-word. measured at 390, 360 and 320 wide, desktop untouched (thanks @Thibs, @c5pie and gzetk) (#1683).
- the nav drawer scrolls in landscape: the header compacts under 500px tall and the drawer follows the visible viewport (thanks @Thibs) (#1661).
more fixes
- deezer re-logins on its own when the session token dies, instead of downloads failing and the arl taking the blame (#1681).
- standalone library scans work. scan library and the auto-scan after downloads silently failed with no media server connected, now they rescan the transfer folder (thanks Spectro on discord) (#1682).
- acoustid keys apply without a restart. a bad key is caught before the scan and stops it instead of burning thousands of lookups a night, and the log says the key is the problem (thanks @splitsec2) (#1672).
- automations can notify through ntfy and gotify, with a send-test button (thanks @SimpleSimonLA) (#1674).
- an api key can manage invites while login mode is on (thanks @splitsec2) (#1676).
- bpm backfill: "reopen all" on findings groups brings back dismissed findings, skips are logged at info/warning so you can see why, and locally analyzed bpm always writes to the file, the deezer tag setting only gates deezer's bpm (thanks Specialmed on discord) (#1646).
- docs: spotify needs an https redirect uri for anything but loopback, so nas/docker setups need an https proxy. the tailscale recipe and a troubleshooting entry are in (thanks @Thibs) (#1663).
validation
- every fix shipped with regression tests and green neighboring suites, per commit and per pr.
- full suite green on this head: 24,418 passed, 21 skipped, 0 failed.