Commit graph

2019 commits

Author SHA1 Message Date
Broque Thomas
c1ef32acd2 Fix source-info popover showing no data due to path format mismatch
track_downloads stores local Windows paths but tracks table stores
server-side paths (Plex/Jellyfin). Both the track_id lookup (NULL
due to failed auto-link at insert time) and exact file_path fallback
were failing.

Added filename-suffix LIKE matching as a final fallback in
get_track_source_info, plus a back-link so the track_id gets written
back for fast future lookups. Also improved the auto-link in
record_track_download to use the same suffix matching when exact path
fails.
2026-04-14 12:30:39 -07:00
Broque Thomas
0edb8e93bb Fix MBID Mismatch Detector comparing MB title against DB title (filename) instead of embedded tag
The scan was reading `t.title` from the database for comparison, which is
populated from the filename rather than the file's embedded TITLE tag. Any
track whose filename differs from the clean MB title (e.g. includes artist,
album, bitrate) was incorrectly flagged as a mismatch.

Fix: extend `_read_mbid_from_file` → `_read_file_tags` to also read the
embedded TITLE tag (ID3 TIT2, Vorbis `title`, MP4 `©nam`). The comparison
now uses the embedded title, falling back to the DB title only when no TITLE
tag is present in the file.

Fixes #296
2026-04-14 12:19:41 -07:00
Broque Thomas
751024ec64 Fix M3U playlist export to use real library file paths
M3U entries now resolve actual file paths from the DB instead of
synthesising a fake 'Artist - Title.mp3' string that no media server
could use. Adds optional M3U Entry Base Path setting (Downloads tab)
so servers requiring absolute paths (e.g. /mnt/music) can be supported.

- New POST /api/generate-playlist-m3u endpoint: per-artist batch DB
  lookups with fuzzy title matching, prefixes entry_base_path when set
- autoSavePlaylistM3U and exportPlaylistAsM3U now call the new endpoint
- M3U Entry Base Path input added below Music Videos Dir in settings,
  follows path-input-group pattern with Unlock button and autosave
2026-04-14 12:07:35 -07:00
Broque Thomas
86621704fe Fix Discover synced playlists not appearing under Server Playlists
Server Playlists was filtered to only show playlists matching mirrored_playlists entries,
but Discover syncs are stored in sync_history (not mirrored_playlists), so they were
excluded. Adds GET /api/sync/history/names returning distinct synced playlist names,
and includes those in the filter alongside mirrored playlists.
2026-04-14 08:24:44 -07:00
Broque Thomas
61c6b6f3f9 Fix staging files bypassing path template
_try_staging_match() built a minimal context missing spotify_artist,
spotify_album, is_album_download, and has_clean_spotify_data. Post-
processing returned early at the missing-spotify_artist guard and the
copied file was left at the transfer root with its original filename.

Now mirrors the sync modal worker's context-building: uses
_explicit_album_context/_explicit_artist_context when available
(artist-page album downloads), falls back to track.album/track.artists
for playlists and sync modal. track_number and disc_number are also
forwarded so multi-disc albums land in the correct Disc N/ subfolder.
2026-04-14 08:19:10 -07:00
Broque Thomas
ab9064d3f6 Fix import pipeline not placing multi-disc albums into Disc N/ subfolders
import_album_process never computed total_discs, so spotify_album.total_discs
always defaulted to 1 in _build_final_path_for_track. Now pre-computes
total_discs from the matched tracks before the per-track loop.
2026-04-14 08:04:15 -07:00
Broque Thomas
3b8b369492 Add Your Albums — multi-source liked albums pool (Spotify, Tidal, Deezer)
Builds a new Your Albums section on the Discover page that aggregates
saved/liked albums from all connected services, mirroring the Your Artists
pattern. Deezer works via both OAuth and ARL.

- tidal_client: add get_favorite_albums() with V2/V1 API fallback
- deezer_client: add get_user_favorite_albums() via OAuth (user/me/albums)
- deezer_download_client: add get_user_favorite_albums() via ARL session
- music_database: add liked_albums_pool table (deduped by artist::album
  normalized key), upsert_liked_album, get_liked_albums,
  get_liked_albums_last_fetch, clear_liked_albums
- web_server: GET /api/discover/your-albums (ownership-checked, paginated),
  GET /api/discover/your-albums/sources, POST /api/discover/your-albums/refresh,
  _fetch_liked_albums background worker (Spotify + Tidal + Deezer OAuth/ARL)
- frontend: Your Albums section with source selector cog, album grid reusing
  spotify-library-card styles, search/filter/sort/pagination, download missing
  button, auto-refresh poll on first load

Also fix: Deezer greyed out in Your Artists sources when using ARL — connection
check now accepts ARL auth (deezer_dl.is_authenticated()) in addition to OAuth,
and _fetch_and_match_liked_artists falls back to ARL client for artist fetching.
2026-04-13 23:02:37 -07:00
Broque Thomas
453eb90f19 Add Deezer ARL favorite artists support
Add get_user_favorite_artists(limit=200) to DeezerDownloadClient to fetch a user's favorite artists via the public API using an ARL-authenticated session (paginated, error-handled, returns deezer_id, name, image_url).

Update web_server to treat Deezer as connected if either OAuth or ARL is authenticated, and to fetch favorite artists from OAuth client when available or from soulseek_client.deezer_dl (ARL) otherwise. Fetched artists are upserted into the database and appropriate log/console messages and counters are updated.
2026-04-13 19:46:39 -07:00
Broque Thomas
8fc4484846 Fix $track rejected as invalid variable in single path template validator 2026-04-13 19:15:04 -07:00
Broque Thomas
1fbc699879 Fix server playlist Find & Add not persisting to Plex
Three issues fixed:

1. Plex add-track used delete+recreate (Playlist.create) which was
   unreliable — switched to addItems() which atomically appends the
   track without touching existing playlist items.

2. After a successful add, the UI only did an optimistic local update.
   On reopen the automatic matcher ran fresh and couldn't connect the
   manually selected track to the source slot, making it look unfixed.
   Now both add and replace re-fetch the compare view from the server
   so the matcher sees the actual updated Plex state.

3. Matching algorithm was too strict for common title variants. Added
   _norm_title() which strips feat./ft., remaster/remastered, and
   edition qualifiers before comparison — so "Boy 1904" matches
   "Boy 1904 (2019 Remaster)" and "Float Away" matches "Float Away
   (feat. Flamingosis & Eric Benny Bloom)". Display titles unchanged.
2026-04-13 19:09:25 -07:00
Broque Thomas
5bb36412be Fix Jellyfin playlist track fetch truncating at default API limit
get_playlist_tracks() had no Limit or StartIndex params, so Jellyfin
defaulted to ~100 items per response. This caused the Server Playlists
comparison to show most tracks as missing even though they were present.

Now paginates in batches of 1000 until a partial batch signals the last page.
2026-04-13 15:17:52 -07:00
Broque Thomas
20c8bff85c Upgrade artwork to highest available resolution for Spotify and iTunes
Spotify album art: replace the 4-char size segment after '0000ab67616d'
with '82c1' to request the original uploaded master (up to 2000px+).
Applied via _upgrade_spotify_image_url() in Track, Artist, and Album
dataclass constructors and as a catch-all in _download_cover_art.
Scoped to the ab67616d album art prefix only — artist images use a
different prefix (ab676161) where the trick does not apply.

iTunes/Apple Music: replace '100x100bb' with '3000x3000bb' in all
artworkUrl100 replacements across Track, Artist, Album, and the
get_album images arrays. Also applied as a catch-all in _download_cover_art.

Deezer already uses cover_xl at its maximum — no changes needed there.
2026-04-13 15:04:37 -07:00
Broque Thomas
f851a67d6b Expose $track variable in Single Path Template settings hint 2026-04-13 14:54:54 -07:00
Broque Thomas
4cf18ad2c9 Fix service status not showing Spotify as active metadata source
The status cache's source field was being set by _get_metadata_fallback_source(),
which internally calls is_spotify_authenticated() a second time via module import.
Any inconsistency between that path and the direct auth check already performed
would return 'deezer', poisoning the cache — and since the WebSocket takes over
from the HTTP poll, it never corrected itself.

Now reads config directly and reuses the single auth probe result.
2026-04-13 14:50:49 -07:00
Broque Thomas
ff5684bded Add configurable sources for Your Artists section on Discover page
Gear button next to View All opens a sources modal letting users pick
which connected services (Spotify, Tidal, Last.fm, Deezer) contribute
artists to the Your Artists carousel. Setting saved via standard
/api/settings endpoint under discover.your_artists_sources.

- GET /api/discover/your-artists/sources returns enabled config + which
  services are currently connected
- _fetch_and_match_liked_artists skips sources not in the enabled list
- Disconnected services shown dimmed and non-interactive in modal
- Saving with nothing selected blocked with error toast
- Remove z-index from .sidebar-header (fixes artist map overlap)
- Add padding-bottom to #automations-list-view (search bar overlap fix)
2026-04-13 13:39:36 -07:00
Broque Thomas
d210c2311f Add bottom padding to automations list view
Add a 10vh bottom padding rule for #automations-list-view in webui/static/style.css to provide extra spacing at the bottom of the automations list and prevent content from being obscured by fixed UI elements (e.g., footer).
2026-04-13 10:14:58 -07:00
Broque Thomas
c77cf25912 Fix Settings page content clipped by global search bar
Add padding-bottom: 10vh to #settings-page .settings-content so the
bottom section is not obscured by the floating search bar overlay.

Closes #292 (item 3)
2026-04-13 10:11:43 -07:00
Broque Thomas
82a621ff05 Use anchors for artist badges and update styles
Replace div badges with data-url/onclick handlers by semantic <a> elements (with href, target="_blank" and rel="noopener noreferrer") for clickable artist badges, keeping non-clickable badges as divs. Update CSS to target .artist-hero-badge and unify hover/image rules instead of relying on data-url attribute, preserving visual behavior and removing pointer cursor for non-clickable divs. Also remove rendering of the server_source badge from the artist meta panel. These changes improve accessibility, security, and maintainability of badge markup and styling.
2026-04-13 10:10:41 -07:00
Broque Thomas
05dfcc3e30 Reorder nav: move Import button earlier
Add an Import nav button after Downloads (new SVG/icon) and remove the duplicate Import button that was located after Stats. This adjusts the sidebar navigation order (Downloads → Import → Library → ...) and removes the redundant element in webui/index.html.
2026-04-13 09:59:05 -07:00
Broque Thomas
352ad5ff68 Fix AcoustID test connection falsely failing for valid API keys
The fallback test (used when no audio files exist in the library) sends
a dummy fingerprint to the AcoustID API. The API correctly rejects the
dummy fingerprint but this is not error code 4 (invalid key), so the
test was returning False instead of True. Any non-code-4 error from the
fallback means the API key was accepted — only code 4 means a bad key.
2026-04-13 09:37:50 -07:00
Broque Thomas
e1c8620928 Record Spotify API calls via api_call_tracker
Instrument web_server.py to record Spotify API usage by calling core.api_call_tracker.api_call_tracker.record_call before various Spotify SDK calls. Added local imports and record_call invocations around artist, playlist, playlist_tracks_page, current_user_saved_tracks, artists_batch, track and related endpoints across functions such as get_artist_image, get_playlist_tracks, watchlist_artist_config, enrich_similar_artists, get_your_artist_info, get_artist_map_explore, import_album_process and import_singles_process. This enables per-endpoint monitoring and helps with rate-limit tracking while keeping the import local to avoid top-level dependency issues.
2026-04-13 08:29:20 -07:00
Broque Thomas
0edd8f5c81 Raise artist discography limit from 50 to 200 with Deezer pagination
Deezer and iTunes defaulted to 50 albums max, silently truncating large
discographies. Deezer now paginates (100 per page) up to 200. iTunes
raised to 200 (single call). All callers in web_server.py updated to
use the new defaults instead of hardcoding limit=50.

Also adds diagnostic logging for allow_duplicates album comparison
to help debug inconsistent singles behavior.
2026-04-12 21:46:04 -07:00
Broque Thomas
b4cf8b4cc1 Fix allow_duplicates for same-track-ID across different albums
The wishlist table has a UNIQUE constraint on spotify_track_id, so
INSERT OR REPLACE silently overwrote the existing entry when the same
track appeared on a different album (same Spotify track ID). Now uses
a composite key (track_id::album_id) when allow_duplicates is on and
the base track ID already exists, allowing both versions to coexist.
Only affects users with allow_duplicates enabled.
2026-04-12 19:37:13 -07:00
Broque Thomas
3a7c25f20f Fix allow_duplicates not working for singles in watchlist scanner
Singles like "idol" weren't added when the same song existed on a
different album because check_track_exists used album-aware matching
that found the track via album name. Now skips the album hint when
allow_duplicates is on so matching is title+artist only, then compares
album names ourselves with a strict 0.85 threshold. Only affects users
with allow_duplicates enabled.
2026-04-12 17:41:55 -07:00
Broque Thomas
f9450b4fea Purge cached tracks with junk artist names on first v2.3 startup
One-time migration deletes metadata cache entries where artist_name is
null, empty, 'unknown', 'unknown artist', etc. The cache gate now
prevents new junk entries, but existing poisoned data from before the
fix needs cleaning so users don't keep getting Unknown Artist from
stale cache hits.
2026-04-12 16:33:08 -07:00
Broque Thomas
e65f73abe2 Fix allow_duplicates setting not working in watchlist scanner
The setting only affected wishlist dedup but the watchlist scanner's
library check still skipped tracks by title+artist regardless. Now
when allow_duplicates is enabled, the scanner compares album names
and only skips if the same album matches. Same song on a different
album is allowed through to the wishlist.
2026-04-12 15:41:55 -07:00
Broque Thomas
45ba51ce3c Add diacritic-insensitive matching to library artist search
search_artists now uses unidecode_lower() and _normalize_for_comparison()
so 'Tiesto' finds 'Tiësto'. Track search already had this — artist
search was the only gap. No change to stored data, only the comparison.
2026-04-12 15:26:21 -07:00
Broque Thomas
26b2ca60fc Bump to v2.3, sticky sidebar header, compact idle player, Plex fixes
Version bump to 2.3 with rewritten What's New modal covering all
changes since v2.2. Docker publish workflow default updated.

Sidebar improvements:
- Header stays pinned at top while nav and player scroll beneath it
- Media player collapses to compact single-line when no track is
  playing, expands to full size when playback starts

Fixes:
- Server playlists endpoint Plex Tag object crash (getattr fix)
- Server playlists tab auto-refreshes after download completion
- Fixed dead code syntax error in archived version notes
2026-04-12 14:43:18 -07:00
BoulderBadgeDad
4f25cf4661
Merge pull request #286 from kettui/fix/improve-graceful-shutdown
Improve shutdown procedures so that the application can close gracefully
2026-04-12 12:42:00 -07:00
BoulderBadgeDad
187a925037
Merge pull request #283 from kettui/fix/logging-init
Setup logging early to prevent initial logs from being swallowed, fix db init operations
2026-04-12 12:27:29 -07:00
Broque Thomas
48eff57ed0 Fix sync tab content clipping — enable vertical scrolling
sync-tab-content had overflow:hidden which clipped long content like
the file import preview table and server playlist editor. Changed to
overflow-y:auto so all sync tabs scroll when content exceeds the
container height.
2026-04-12 12:06:29 -07:00
Broque Thomas
445cb242f2 Use Deezer contributors field for multi-artist track tagging
Deezer's API returns a contributors array with all credited artists on
a track, but only the primary artist field was used. Now extracts all
contributor names into the artists array for feature/collab tracks.

Only affects the per-track ARTIST tag (TPE1) — album artist, folder
paths, and matching are unchanged. Falls back to single primary artist
when contributors field is absent (search results) or has only one
entry.
2026-04-12 11:52:06 -07:00
Broque Thomas
501140104e Fill interactive help gaps for all new UI elements
Added ~30 HELPER_CONTENT entries covering:
- Sidebar: Downloads page, Playlist Explorer nav buttons
- Dashboard: all 10 enrichment service worker pills, recent syncs,
  API rate monitor, maintenance, SoulID, blacklist
- Sync: Server Playlists tab, ListenBrainz tab
- Active Downloads page: filters, list, clear button
- Playlist Explorer page: picker, view toggle, action bar
- Issues page: header, filters, findings list
- Discover: Your Artists carousel
- Personal Settings gear button
2026-04-12 11:43:06 -07:00
Broque Thomas
3d26c2b140 Fix [object Object] in M3U files and database reference error
M3U generator was calling .join() on an array of artist objects instead
of extracting .name first, producing "[object Object] - Track Name".
Now handles all artist formats: array of objects, array of strings,
single string, single object.

Also fix "name 'database' is not defined" error when updating album
year in post-processing — was using bare 'database' instead of
get_database() helper.
2026-04-12 11:10:11 -07:00
Broque Thomas
55f0532f30 Fix M3U files created for non-playlist downloads
Single track downloads from Search, album downloads, redownloads, and
issue downloads were not in the M3U skip list, so auto-save M3U created
playlist files for them. Expanded skip list to cover all non-playlist
prefixes: enhanced_search_track_, issue_download_, library_redownload_,
and redownload_.
2026-04-12 11:01:15 -07:00
Broque Thomas
a7877e6e0b Skip albums with placeholder track names in watchlist scanner
Spotify lists unreleased albums with placeholder names like "Track 1",
"Track 2" before the real tracklist is revealed. The scanner was trying
to download these, searching Soulseek for "Track 1" by artist which
matches random files. Now skips any album where more than half the
tracks match the placeholder pattern. Covers both the watchlist scan
and discovery pool paths.
2026-04-12 10:55:06 -07:00
Broque Thomas
027549be59 Fix album matching using full similarity instead of word subset check
The old subset check treated "Paradise" as matching "Club Paradise"
because {'paradise'} is a subset of {'club', 'paradise'}. Both got
the same +0.10 bonus, so the wrong album could be selected.

Now uses SequenceMatcher for full-string similarity between the wanted
album name and each path segment. Exact matches (>= 0.85) get +0.10,
partial matches (>= 0.60) get +0.03, no match gets +0.00. No penalty
applied — purely adjusts bonus sizing so the correct album ranks higher.
2026-04-12 10:48:24 -07:00
Broque Thomas
1c1be6190a Fix Plex playlists crash on Tag objects and add Unknown Artist guard
Plex API can return Tag objects mixed with playlists — these lack the
playlistType attribute, causing AttributeError. Use getattr with safe
default instead of direct attribute access.

Add 3-tier Unknown Artist guard in post-processing: checks track_info
artists, original search result, then re-fetches from metadata API
before building folder paths or embedding tags. Prevents files from
landing in Unknown Artist folders when the download context has
incomplete artist data.
2026-04-12 10:43:51 -07:00
Broque Thomas
d44c78c87b Fix music library paths not auto-saving on settings page
Dynamic music path inputs were created after auto-save listeners were
attached, so typing in them never triggered a save. Now attaches change
listeners when creating or rendering path rows. Removing a path also
triggers auto-save immediately.
2026-04-12 09:31:42 -07:00
Broque Thomas
d77274c2ea Reject tracks with Unknown Artist from metadata cache
The junk entity filter checked track name but not artist_name, allowing
tracks like "Woman Like You by Unknown Artist" to be cached. Now rejects
any track or album where artist_name matches the junk names list
(unknown, unknown artist, empty, null, etc). Prevents stale incomplete
data from persisting across retries.
2026-04-12 09:27:17 -07:00
Antti Kettunen
7f5384a89e Add some more shutdown checks for websocket handlers 2026-04-12 18:30:11 +03:00
Antti Kettunen
29d964e8b0 Tighten up teardown logic
- Stop active DB update work before tearing down executor pools.
- Short-circuit scan completion callbacks during shutdown so in-flight timer ticks don’t queue follow-up work.
- Prevent the download monitor from draining deferred/completed tasks after shutdown starts.
- Make listening stats startup stop-aware so it exits cleanly if teardown begins during warmup.
- If a metadata update is already running, it can now observe should_stop and exit cleanly instead of continuing after SIGTERM.
2026-04-12 18:13:27 +03:00
Antti Kettunen
aec3047216 Improve graceful shutdown and rollback safety
- Add interruptible stop events to background workers so shutdown
  wakes out of long sleeps instead of waiting on fixed delays.
- Stop scan managers, repair worker, executors, and cleanup helpers
  deterministically so process exit does not leave background threads
  alive.
- Add startup warnings for stale SQLite WAL/SHM sidecars so unclean
  shutdowns are easier to spot before init/migration errors cascade.
- Prevent forced kills from leaving SQLite sidecars behind, which
  made rollbacks to older branches fail with malformed database
  errors.
2026-04-12 15:17:18 +03:00
Antti Kettunen
1348de96c1 Fix db initialization
When starting from scratch (no existing .db file), certain db init steps were being skipped. Upon subsequent startup, these remaining 4-5 steps would execute.

Up until recently this has not been much of an issue since all the db init steps were run repeatedly throughout the process' lifetime, but after the init was changed to be only done once per startup, this became more problematic
2026-04-12 12:51:52 +03:00
Antti Kettunen
5a40c185e1 Remove lingering local db file 2026-04-12 11:57:56 +03:00
Antti Kettunen
7a8cc854db Setup logging early to prevent initial logs from being swallowed
There's a lot side-effects happening at import-time (eg. client initialization etc.), some of which include meaningful logging as well (eg. migration-related logging in MusicDatabase client).

If we delay the logging initialization to the __main__ block, we'll lose out on these early logs
2026-04-12 11:45:55 +03:00
Broque Thomas
fff76a4be0 Add optional slskd service to docker-compose.yml
Commented-out slskd block that users can uncomment to run both services
together. Shares the ./downloads volume so SoulSync reads slskd's
downloads directly. Includes API key env var and setup instructions.
2026-04-12 00:07:46 -07:00
Broque Thomas
d607054f16 Add centralized Downloads page to What's New modal 2026-04-12 00:00:28 -07:00
Broque Thomas
7798f56885 Add centralized Downloads page with live status across all sources
New sidebar page showing every download task across the app in a unified
live-updating list. Tracks from Sync, Discover, Artists, Search, and
Wishlist all appear in one place.

Features:
- Filter pills: All / Active / Queued / Completed / Failed
- Section headers grouping by status category
- Track position (3 of 19) for album/playlist batches
- Album art, artist/album metadata, batch context, error messages
- Status dots with accent glow for active, green for complete, red fail
- Clear Completed button removes terminal items from tracker
- Nav badge shows active download count from any page via WebSocket

Fixes artist [object Object] display — handles all format variations
(list of dicts, list of strings, dict, string) for artist and album
fields in the API response.
2026-04-11 23:58:22 -07:00
Broque Thomas
ce8c3b9cbb Fix Hybrid status check to prioritize serverless sources
Serverless check (YouTube/HiFi/Qobuz) now runs before the slskd
connection test. Prevents 4-minute timeout hangs when Hybrid mode
includes both Soulseek and serverless sources but slskd isn't running.
Also reports green if any serverless source is in the hybrid order,
not just the first.
2026-04-11 23:30:34 -07:00