Commit graph

4407 commits

Author SHA1 Message Date
Broque Thomas
020ce6d765 Hide discovery modal sync buttons in standalone mode
"Sync This Playlist" buttons in YouTube/Tidal/Deezer/Spotify/Beatport/
ListenBrainz discovery modals were not gated by _isSoulsyncStandalone.
Added check to the hasSpotifyMatches condition that generates them.
2026-04-18 09:15:19 -07:00
Broque Thomas
33911a7001 Show Sync page in standalone mode, only hide sync buttons
The Sync page was hidden entirely for standalone users, blocking
access to playlist browsing, discovery, and downloads. Now the page
is accessible — only the sync-to-server buttons are hidden since
there's no server to push playlists to.
2026-04-18 09:00:55 -07:00
Broque Thomas
8df5cf3be0 Fix lossy copy conversion failing on FLACs with embedded cover art
Added -vn flag to all codec ffmpeg commands (MP3, Opus, AAC) to strip
video/image streams during conversion. Embedded cover art in FLAC
files caused ffmpeg to fail when the output muxer couldn't handle
the image stream, producing 0KB output files. Cover art is
re-embedded afterwards by Mutagen.
2026-04-18 08:57:21 -07:00
Broque Thomas
9369924968 Fix discography dedup dropping studio albums for compilations
The dedup key (normalized_title, year) caused different albums from
the same year to collide when title normalization stripped too much.
The "prefer more tracks" logic then kept compilations over studio
albums.

Two fixes:
- Title similarity check: if normalized titles are <85% similar,
  they're different albums, not variants — keep both
- Compilation deprioritization: studio albums win over compilations
  and "best of" collections when they do collide
2026-04-18 08:53:46 -07:00
Antti Kettunen
cca396e7bd Centralize Hydrabase enablement
Move Hydrabase availability checks into metadata_service so source resolution owns the policy. Keep web_server delegating to the centralized helper and add tests for the enabled/disabled cases.
2026-04-18 18:47:54 +03:00
Antti Kettunen
2b575a59ae Refactor artist discography lookup
Move artist discography resolution into core metadata_service, introduce MetadataLookupOptions, and keep web_server focused on request handling. Add focused tests for the new service boundary and preserve current fallback behavior for now.
2026-04-18 18:41:31 +03:00
Broque Thomas
ad9d5817cf Fix 'Loading playlist...' stuck forever on error
openDownloadMissingModal showed loading overlay but didn't hide it
on error paths (playlist not found, fetch failure). The overlay
persisted across page navigation, blocking the entire UI.
2026-04-18 08:37:31 -07:00
Broque Thomas
f4aaab8a66 Reorganize Settings Library tab with collapsible sections
Three collapsible categories, collapsed by default:
- Paths & Organization (file templates + music library paths)
- Post-Processing (metadata, tags, conversion, lyrics)
- Library Preferences (import, content filter, stats, playlists, M3U)

Section headers have data-stg=library so they only appear on the
Library tab. Bolder headers with accent-colored arrows and subtle
border. Collapse state preserved when switching settings tabs.
2026-04-18 08:28:12 -07:00
Antti Kettunen
1905678c7b Refactor get_artist_discography to respect metadata provider priority 2026-04-18 16:24:59 +03:00
Antti Kettunen
e687486561 Add caching for enhanced search, don't attempt searches with 2 characters or less 2026-04-18 15:06:00 +03:00
Antti Kettunen
688e84ce4d Fix MusicBrainz label, primarily show tabs with fetched results only 2026-04-18 14:57:12 +03:00
Antti Kettunen
02f190efc6 Reduce enhanced search stalls
Delay alternate-source fan-out until the primary enhanced-search response arrives, and stagger those follow-up requests so they do not all compete at once. Also parallelize artist, album, and track lookups inside each metadata source request to shorten the time the UI thread spends waiting on remote APIs. This keeps the single-worker web UI more responsive under the app's chatty search flow.
2026-04-18 14:46:50 +03:00
Broque Thomas
841ad42fdd Fix MusicBrainz tab not appearing in enhanced search
Source fetch list was hardcoded without musicbrainz — tabs only showed
for sources that were pre-fetched in the parallel search loop.
2026-04-18 02:10:12 -07:00
Broque Thomas
4f5025d526 Add MusicBrainz search tab, wider global search, bump to v2.32
New MusicBrainz tab in Enhanced and Global search — finds tracks and
albums on MusicBrainz's community database with Cover Art Archive
images. Covers obscure tracks that Spotify/Deezer/iTunes miss.

- core/musicbrainz_search.py: search adapter with Track/Artist/Album
  dataclasses, Cover Art Archive integration, smart query parsing
- Albums deduplicated (keeps best version with date and art)
- No artist results shown (MusicBrainz has no artist images)
- Album detail with full tracklist for download modal
- Smart word-boundary splitting for queries without separators
- Global search results container widened from 620px to 920px
- UI version bumped to 2.32
2026-04-18 02:06:27 -07:00
Broque Thomas
70005968b6 Restructure version modal and What's New for v2.31
SoulSync Standalone Library is now the first section in both the
version modal and What's New popup. Auto-Import section updated with
all improvements (recursive scan, singles, tag preference, AcoustID).
New Downloads & Soulseek section groups download-related improvements.
Recent Fixes cleaned up — feature items moved to proper sections.
2026-04-18 01:28:19 -07:00
Broque Thomas
b78616431a Add SoulSync standalone deep scan and skip incremental scan
Deep scan for standalone mode:
- Scans Transfer folder for all audio files
- Compares against soulsync DB records by file_path
- Moves untracked files to Staging for auto-import processing
- Removes stale DB records where files no longer exist
- Cleans orphaned albums and artists with no tracks

Incremental scan skips for standalone — library updates at download
time, no periodic scanning needed. Both changes are purely additive
and only activate when server_type is 'soulsync'.
2026-04-18 01:17:32 -07:00
Broque Thomas
b4475152a9 Hide all sync buttons in standalone mode, bump UI to v2.31
All sync-related buttons hidden when active server is SoulSync
Standalone. Covers static buttons (querySelectorAll on status update)
and dynamic modal buttons (_isSoulsyncStandalone flag).
UI version bumped to 2.31 (Docker stays at 2.3).
2026-04-18 01:14:54 -07:00
Broque Thomas
a51e1b6651 Update README: SoulSync standalone mode, no media server required
Added standalone mode to What It Does and Library Management sections.
Media server is now optional — Plex, Jellyfin, Navidrome, or Standalone.
2026-04-18 00:57:14 -07:00
Broque Thomas
2d57831e91 Fix Docker build: stop excluding requirements.txt from build context
PR #311 renamed requirements-webui.txt to requirements.txt but the
.dockerignore still excluded requirements.txt (previously the PyQt6
desktop version). Docker COPY failed because the file was ignored.
2026-04-18 00:37:37 -07:00
Broque Thomas
12e9c0034b Hide Sync page in sidebar for SoulSync standalone mode
No media server to sync playlists to — sync page is irrelevant.
M3U generation is still available via settings toggle and download
modal buttons for standalone users who want playlist files.
2026-04-18 00:27:38 -07:00
Broque Thomas
d688e7fa15 Update What's New and version modal with standalone library and import fixes 2026-04-17 23:29:04 -07:00
Broque Thomas
6d5538de74 Fix single import: prefer tag data over weak metadata/AcoustID matches
Files with embedded tags (artist+title from post-processing) were
failing import because the metadata search scored low (66%) and the
AcoustID result returned before the tag-preference code could run.

- Tag-based identification now returns 85% confidence when embedded
  tags have an artist field, borrowing album art from weak metadata
- AcoustID search result only accepted at 80%+ confidence, otherwise
  kept as fallback (doesn't short-circuit past tag preference)
- AcoustID None artist/title falls back to tag data via 'or' operator
- Stop retrying failed/unidentified items every scan cycle
2026-04-17 23:18:03 -07:00
Broque Thomas
7bad4a4fa9 Stop auto-import retrying failed/unidentified items every scan cycle
Items with status needs_identification, failed, or rejected were not
in the skip list, causing them to be re-scanned and re-logged every
60 seconds indefinitely. Now skips all terminal statuses.
2026-04-17 22:52:01 -07:00
Broque Thomas
0b13a9d886 Fix single track completion check not filtering by active server
Single track ownership check was calling check_track_exists without
server_source, matching against all servers instead of the active one.
Album and EP checks already passed server_source correctly — this was
the only missing spot. Affects all server types.
2026-04-17 22:50:39 -07:00
Broque Thomas
d916b01fd6 Fix soulsync library entries creating own artist/album records
Previously reused existing plex/jellyfin artist IDs, causing soulsync
tracks to be invisible on the library page (filtered by server_source).
Now always creates soulsync-specific artist and album records with
server_source='soulsync', avoiding PK collisions with hash suffixes.
2026-04-17 22:14:54 -07:00
Broque Thomas
c009acdbb6 Add SoulSync Standalone toggle to Settings page
Fourth server option on the Connections tab with SoulSync logo and
'Standalone' label. Config panel shows Transfer folder path and
Verify Folder button. Test connection counts audio files in the
Transfer folder. Settings save/load properly detects soulsync toggle.
2026-04-17 20:56:05 -07:00
Broque Thomas
43dedeb2ee Add SoulSync standalone library — no media server required
New 'soulsync' media server option manages the library directly from
the filesystem, bypassing Plex/Jellyfin/Navidrome entirely.

Two paths populate the library:
1. Downloads/imports write artist/album/track to DB immediately at
   post-processing completion, with pre-populated enrichment IDs
   (Spotify, Deezer, MusicBrainz) so workers skip re-discovery
2. soulsync_client.py scans Transfer folder for incremental/deep scan
   via DatabaseUpdateWorker (same interface as server clients)

New files:
- core/soulsync_client.py: filesystem scanner implementing the same
  interface as Plex/Jellyfin/Navidrome clients. Recursive folder scan,
  Mutagen tag reading, artist/album/track grouping, hash-based stable
  IDs, incremental scan by modification time.

Modified:
- web_server.py: _record_soulsync_library_entry() at post-processing
  completion, client init, scan endpoint integration, status endpoint,
  web_scan_manager media_clients dict, test-connection cache updates
- config/settings.py: accept 'soulsync' in set_active_media_server,
  get_active_media_server_config, is_configured, validate_config
- core/web_scan_manager.py: add soulsync to server_client_map

Dedup: checks existing artist/album by name across ALL server sources
before inserting to avoid duplicates. Enrichment IDs only written when
the column is empty (won't overwrite existing data).
2026-04-17 20:34:55 -07:00
Broque Thomas
bbf5af1ce1 Fix auto-import rescan race condition, coverage penalty, and UI
Race condition: scanner re-scanned folders while post-processing was
still moving files, causing partial matches and ghost failures. Now
tracks in-progress paths and skips them on subsequent scans.

Coverage penalty fix: individual tracks that match at 80%+ confidence
now auto-import even when overall album coverage is low (e.g. 2 of 18
tracks present). Previously low coverage killed the entire import.

Import page: stats bar, filter pills, Scan Now, Approve All, Clear
History (clears imported + failed), live scan progress.
2026-04-17 19:37:42 -07:00
Broque Thomas
a2e3ce8000 Fix auto-import track numbers, dates, cover art, and track name display
- Track numbers defaulted to 1 instead of using metadata source values
- Release dates not captured, causing missing year in path templates
- Cover art missing for Deezer (direct image_url not checked)
- Track names in expanded view showed Unknown (wrong JSON field name)
- Read year/date from embedded file tags as fallback
- Add Deezer get_album_metadata/get_album_tracks fallbacks
- Handle Deezer tracks.data response format
2026-04-17 19:05:52 -07:00
Broque Thomas
d2c6979ce4 Recursive staging scan, singles support, and improved import UI
Auto-import now scans the staging folder recursively — any folder
structure depth works (Artist/Album/tracks, Album/tracks, etc.).
Loose audio files are treated as singles with tag/filename/AcoustID
identification.

Import results UI redesigned:
- Click cards to expand per-track match details with confidence scores
- Shows identification method badge (Tags, Folder Name, AcoustID)
- Per-track grid: track name, matched filename, confidence percentage
- Time ago labels, folder path, better status badges
- Approve/Dismiss buttons use event.stopPropagation for clean UX
2026-04-17 17:48:00 -07:00
Broque Thomas
d66adb3c6e Add single file support to auto-import worker
Loose audio files in the staging root are now picked up alongside album
folders. Singles are identified via embedded tags, filename parsing
(Artist - Title.ext), or AcoustID fingerprinting, then matched against
the configured metadata source. Confidence-gated processing applies
the same way as album folders (90%+ auto, 70-90% review, <70% manual).
2026-04-17 17:15:56 -07:00
Broque Thomas
c20529dbb5 Fix auto-import toggle visual and import page refresh
Toggle appeared off when running because CSS :checked rules were
scoped to .repair-master-toggle. Added auto-import-toggle-label
selectors. Refresh now re-renders whichever tab is active.
2026-04-17 17:07:19 -07:00
Broque Thomas
1446c7b63a Fix auto-import toggle visual flicker
Toggle state was set by browser click, then immediately overwritten by
the status reload callback. Now optimistically sets the toggle and
status text before the API call, reverting only on failure.
2026-04-17 16:33:05 -07:00
Broque Thomas
eb2218ec8d Add file deletion option to album delete on enhanced library page
Album delete now shows a smart delete dialog with two options:
- Remove from Library (DB only, files untouched)
- Delete Files Too (removes DB records AND deletes audio files from
  disk, cleans up empty album folder)

Backend /api/library/album/<id> DELETE now accepts ?delete_files=true
parameter, resolves each track's file path, and removes files before
deleting DB records. Reports files_deleted and files_failed counts.
2026-04-17 15:26:25 -07:00
Broque Thomas
619b7ab4be Update What's New and version modal with recent fixes 2026-04-17 15:05:57 -07:00
Broque Thomas
aede7dd089 Fix download modal freezing by moving M3U save to completion only
autoSavePlaylistM3U was called on every 2-second poll cycle once any
track completed, flooding the server with heavyweight M3U generation
requests (fuzzy matching all tracks against the DB). This exhausted
Flask's thread pool, causing the batch status endpoint to hang and
killing the poller — making the modal freeze mid-download.

Now fires once when the batch completes instead of on every poll.
2026-04-17 15:01:45 -07:00
Broque Thomas
7415485b9a Harden download modal polling against premature completion
- Include completed batches in poll cycle so late task updates still
  render (prevents modal freezing when batch completes before all rows
  update)
- Require server to report no active tasks before client-side
  completion fires (prevents phase=complete from prematurely ending UI)
- Apply same fix to backoff poller and WebSocket resubscription

Note: modal stalling issue persists — setInterval stops firing after
~12 cycles for unknown reasons. Needs deeper browser-level debugging.
2026-04-17 14:52:18 -07:00
Broque Thomas
57259b6e3a Always run HTTP polling for download modal updates
The global download poller was disabled when WebSocket was connected,
but WebSocket connections can silently stop delivering messages without
triggering a disconnect event (room subscription lost, server emit
error, proxy timeout). This left modals frozen while downloads
continued server-side. Removing the socketConnected gate ensures the
2-second HTTP poll always runs as a fallback alongside WebSocket.
2026-04-17 14:08:48 -07:00
Broque Thomas
1384b966e8 Fix Unknown Artist when adding playlist tracks to wishlist
When adding tracks to wishlist from a playlist download modal,
process.artist was undefined (only set for album downloads) and
defaulted to {name: 'Unknown Artist'}. This got stored in
source_info.artist_name and was prioritized over the track's own
artist data during wishlist download post-processing.

Now resolves the artist per-track from the track's own artists array,
falling back to the process-level artist only for album downloads
where it's actually set correctly.
2026-04-17 14:01:56 -07:00
Broque Thomas
f0ccd197e5 Fix lossy copy error logging hiding actual ffmpeg error
The ffmpeg failure log was truncated to 200 chars which only showed the
version banner, hiding the actual error message. Now strips the banner
preamble and shows up to 500 chars of the real error with exit code.
Also cleans up 0KB output files left behind when ffmpeg fails.
2026-04-17 13:17:16 -07:00
BoulderBadgeDad
6b70d7331c
Merge pull request #295 from pxjx22/fix/socketio-polling-fallback
Fix Socket.IO client fallback order
2026-04-17 13:12:22 -07:00
Broque Thomas
168b4c21dd Fix batch panel collapse button not clickable when collapsed
Title text was pushing the toggle button out of the 44px collapsed
panel. Now hides the title and centers the button when collapsed.
2026-04-17 12:58:29 -07:00
BoulderBadgeDad
0cf5cbe9cd
Merge pull request #311 from kettui/feat/remove-desktop-app
Remove desktop app, clean up test files, remove unused dependencies
2026-04-17 12:52:10 -07:00
BoulderBadgeDad
e11c303dac
Merge pull request #308 from kettui/fix/acoustid-scanner
Fix generated IDs in repair flows & acoustid_scanner NPE
2026-04-17 12:41:03 -07:00
Broque Thomas
8f7a3b4861 Use SoulSync confirm dialog for batch cancel instead of browser alert 2026-04-17 12:29:50 -07:00
Broque Thomas
04b8c02ea9 Reject junk artist Soulseek results and cancel downloads on wishlist clear
Soulseek results from "Various Artists", "VA", "Unknown Artist", and
"Unknown Album" folders are now rejected before scoring. These
compilation folders rarely contain properly tagged files for the target
artist.

Clearing the wishlist now also cancels any active wishlist download
batch and resets the auto-processing flag, so downloads don't keep
running after the source tracks are removed.
2026-04-17 12:24:50 -07:00
Broque Thomas
9898bd1190 Add batch context panel to Downloads page
Split Downloads page into main list (left) and batch panel (right).
Each active batch gets a color-coded card with artwork thumbnail,
progress bar, per-track status with download percentages, and
expandable track list. Download rows get matching color indicators.

- Click batch name to open its download/wishlist modal
- Filter icon narrows main list to one batch with clear banner
- Collapsible panel toggle for full-width list view
- Completed batches fade out after 15 seconds
- 7-day batch history with source type color dots
- Artwork fallback shows colored initial when no art available
- Per-track progress: download %, spinner for searching, proc label
- source_page column on sync_history for UI origin tracking
- /api/downloads/all includes batch summaries and per-track progress
- /api/downloads/batch-history endpoint for history queries
- Responsive layout, overflow-x hidden to prevent scroll flicker
2026-04-17 12:18:00 -07:00
Antti Kettunen
bddbe8023c Prune ununsed python dependencies 2026-04-17 21:28:27 +03:00
Antti Kettunen
0e2aa42b10 Realign test file names 2026-04-17 21:25:01 +03:00
Antti Kettunen
279000435b Trim redundant websocket phase tests
Keep room/subscription, parity, and state-reflection coverage.
Drop bare receipt checks and duplicated HTTP smoke tests.
2026-04-17 21:20:15 +03:00