Commit graph

1507 commits

Author SHA1 Message Date
Broque Thomas
6bccbd9cf2 Fix Hydrabase background comparison to use configured metadata source instead of hardcoded iTunes 2026-03-17 12:09:05 -07:00
Broque Thomas
503e45fd9c Update What's New modal with recent fixes and downsample feature 2026-03-17 11:58:21 -07:00
Broque Thomas
a9d0607d25 Add hi-res FLAC to CD quality downsampling in post-processing
New toggle under Post-Download Conversion: automatically converts 24-bit
or high sample rate FLAC files to 16-bit/44.1kHz after download, replacing
the original. Uses ffmpeg with temp file + verify + atomic swap for safety.
Runs before lossy copy so MP3s are made from the downsampled version.
Also prevents bit depth strict mode from rejecting files that will be
downsampled anyway.
2026-03-17 11:53:56 -07:00
Broque Thomas
a62cc821ee Fix $year template variable empty in playlist/sync downloads
The fallback download path (playlists, discovery, sync) built spotify_album_context
from track_info.album which can be a plain string (no release_date) for discovery
playlists or missing metadata. Now backfills release_date, album_type, total_tracks,
and album name from the existing get_track_details API call — zero extra API calls.
Also adds total_discs passthrough for fallback path parity.
2026-03-17 11:35:19 -07:00
Broque Thomas
31e9ff3385 Fix Navidrome library scan triggering via Subsonic startScan endpoint
trigger_library_scan and is_library_scanning were no-ops assuming
Navidrome auto-detects new files. Navidrome supports startScan and
getScanStatus via the Subsonic API, so use them like Plex and Jellyfin.
2026-03-17 10:29:12 -07:00
Broque Thomas
bd56c96634 Fix library reorganize not updating DB paths and leaving sidecars behind
- DB path update uses suffix matching when exact match fails, so server
  paths (/mnt/musicBackup/...) are found and updated after local moves
- Escape LIKE wildcards in artist/album names to prevent false matches
- Move album-level sidecars (cover.jpg, folder.jpg, etc.) alongside audio
- Post-pass sweep moves all remaining sidecars from emptied directories
- Multi-disc albums place album art in album root, not disc subfolders
2026-03-17 10:15:46 -07:00
Broque Thomas
0a5a874c08 Fix dead file cleaner false positives from transfer path resolution failure
Use config_manager for transfer path (same source as playback) instead of
separate DB read that silently falls back to ./Transfer under contention.
Abort scan if transfer folder doesn't exist to prevent mass false findings.
2026-03-17 09:51:31 -07:00
Broque Thomas
cc62541a65 Add Select All, Fix Selected & Fix All to Library Maintenance findings 2026-03-17 09:25:21 -07:00
Broque Thomas
162542a0cf Fix wishlist Unknown Album caused by stale cross-provider discovery data
Mirrored playlist discovery results are cached per-track in extra_data with
the provider that discovered them. When the user switches metadata source
(e.g. Spotify → Deezer), prepare-discovery was serving stale data from the
old provider — wrong IDs, empty albums, no cover art. Now it compares the
cached provider against the current active source and invalidates mismatched
entries, forcing a clean re-discovery with the correct provider.
2026-03-17 09:09:07 -07:00
Broque Thomas
b9d3c4051e Fix Unknown Album in wishlist: album name lost when raw Spotify data missing
Root cause: discovery searches return (Track, raw_data, confidence) but
raw_data can be None (Strategy 4 extended search) or have mismatched index.
When raw_data is None, album_obj becomes {} — an empty dict that passes
normalization unchanged, so album name is never populated.

Fix: after extracting album_obj from raw_data, fall back to track_obj.album
(always populated from the SpotifyTrack dataclass) when album_obj has no
name. Applied to all 3 discovery paths (Deezer, Tidal, Spotify Public).

Also handle both string and dict album formats in wishlist UI rendering.
2026-03-17 08:12:34 -07:00
Broque Thomas
8b403e3947 Fix wishlist normalization mutating tracks_json in-place
The normalization loop was modifying dict objects shared with the
SpotifyTrack construction loop. SpotifyTrack.artists expects List[str]
but normalization converted to [{'name': ...}] dicts. Use dict(t) copy
so original tracks_json is untouched.
2026-03-17 07:57:02 -07:00
Broque Thomas
7fd29c4f77 Fix confirm modals hidden behind other modals and wishlist showing Unknown Album
- Bump confirm dialog z-index to 200000 (both #confirm-modal-overlay and
  .confirmation-modal-overlay) so they always appear above all other modals
- Normalize track data in _run_sync_task before storing in original_tracks_map:
  album as dict {'name': ...} and artists as [{'name': ...}] — fixes all
  source converters (Deezer, YouTube, Tidal, Spotify Public, ListenBrainz,
  Beatport) whose fallback paths stored plain strings, causing the wishlist
  UI to show "Unknown Album" for every synced track
2026-03-17 07:53:59 -07:00
Broque Thomas
4c6e2fe1ec Revamp automation page: 2-col grid, duplicate, search/filter, templates, grouping
- Switch user automations to 2-column grid layout (matches system automations)
- Add duplicate button on non-system cards with POST /api/automations/<id>/duplicate
- Add search/filter bar (text search + trigger/action dropdowns) shown at 6+ automations
- Add Inspiration section with 8 starter templates that pre-fill the builder
- Add folder-style automation grouping with group_name DB column, dropdown
  popover for assignment, collapsible group sections, and builder group input
2026-03-17 07:40:14 -07:00
Broque Thomas
4a308483ab Add smart-skip to sync playlist node for efficient scheduled syncing
Sync now compares current discovered track count against last sync's
matched count. Skips when all discovered tracks are already on the server.
Re-syncs when wishlist may have downloaded missing tracks since last sync.
Saves matched/discovered/total counts to sync status after each run.

Enables putting sync on a schedule without wasting resources — it skips
instantly when nothing changed and only runs when there's something new.
2026-03-16 22:32:08 -07:00
Broque Thomas
a7e60f2aaf Improve mirrored playlist refresh logic and suppress no-op discovery events
- Set album_name from embed entity for album-type Spotify public scrapes
- Log warning and show skip status when Tidal not authenticated during refresh
- Only emit discovery_completed event when new tracks were actually discovered,
  preventing unnecessary sync triggers when retrying the same failed matches
2026-03-16 21:57:54 -07:00
Broque Thomas
7604239b9a Filter refresh playlist dropdown by source and add spotify_public refresh handler
- Exclude file and beatport playlists from refresh (no external API)
- Hide Spotify library playlists from refresh dropdown when not authenticated
- Add spotify_public refresh handler using public embed scraper via stored URL
- Fix YouTube refresh to use stored description URL instead of hash-based source_id
- API returns source and spotify auth status for frontend filtering
2026-03-16 20:37:44 -07:00
Broque Thomas
d58fee17b0 Fix duplicate mirrored playlists from YouTube pill system and add Deezer/Spotify public refresh
YouTube URL hash used Python's hash() which is randomized per process restart,
causing pill re-clicks to create duplicates instead of upserts. Replaced with
deterministic hashlib.md5 of canonical URL. Includes auto-migration to deduplicate
existing entries and update their source_playlist_id.

Also adds mirrored playlist refresh support for Spotify (public embed scraper
fallback when not authenticated) and Deezer playlists.
2026-03-16 20:21:02 -07:00
Broque Thomas
53ef9fa913 Add Deezer support to watchlist config modal and linked provider section
- Query/update watchlist artists by deezer_artist_id in config endpoint
- Return deezer_artist_id in config response and recent albums response
- Add Deezer provider badge (purple) to linked provider section
- Detect Deezer vs iTunes for provider linking using fallback source setting
- Show "X fans" instead of "Pop: 0" for Deezer artist search results
- Include followers count in match/search artist response
- Add deezer_artist_id matching to library enrichment and recent releases queries
2026-03-16 19:49:54 -07:00
Broque Thomas
7d0df2b9ed Fix discover page Deezer source support
- personalized_playlists._get_active_source() now returns 'deezer' when
  configured instead of always falling back to 'itunes'
- Add deezer_track_id to _build_track_dict() for discovery pool tracks
- Include album_deezer_id and artist_deezer_id in get_discovery_recent_albums()
  response — fixes "No deezer album ID available" error when clicking cards
- Skip Spotify library section entirely when Spotify is not authenticated
2026-03-16 19:35:26 -07:00
Broque Thomas
46d3309835 Revamp watchlist & wishlist buttons with dark glass design and animated gradient border
- Dark translucent background with backdrop blur instead of bright colored fills
- Animated flowing gradient border using CSS mask-composite technique
- Color-tinted labels and count badges (amber for watchlist, accent for wishlist)
- Shimmer sweep clipped inside button bounds
- Structured HTML: separate icon, label, badge, and shimmer elements
- Badge pulses when count > 0
- Worker orbs: 7s delay before collapsing back after mouse leaves header
2026-03-16 18:10:10 -07:00
Broque Thomas
775307c2b5 Add worker orbs animation to dashboard header
Worker buttons shrink to floating colored orbs with physics-based
movement, spark emissions from active workers, connection lines, and
center gravity. Hovering the header expands orbs back to full buttons
with staggered spring animation. Desktop only, toggleable in Settings
under UI Appearance.
2026-03-16 16:47:37 -07:00
Broque Thomas
42a4285e09 Add clear findings button to library maintenance modal
Per-job and per-status filtering — respects active toolbar filters so
users can clear e.g. only track number findings or only dismissed items.
Confirmation uses the app's styled modal instead of browser confirm().
2026-03-16 15:37:33 -07:00
Broque Thomas
658f5c60f7 Fix debug info copy button failing for Docker/LAN users over HTTP
When both clipboard APIs are blocked (non-secure context), show a modal
with pre-selected debug text instead of an unhelpful console message.
2026-03-16 15:18:15 -07:00
Broque Thomas
d6fd206efa Update sidebar version button to v2.0 2026-03-16 14:05:41 -07:00
Broque Thomas
dab7aa93d8 Update docker-publish.yml 2026-03-16 14:03:36 -07:00
Broque Thomas
a8f95592af Bump version to 2.0 with Deezer metadata source and latest changes
Add Deezer Metadata Source section to release notes covering configurable
fallback, source badges, auto-matching, and backward compatibility.
Update HiFi section with improved failover wording and add HiFi 500
failover fix to bug fixes list.
2026-03-16 14:02:46 -07:00
Broque Thomas
c3546ac0bd Fix HiFi client not failing over to next instance on HTTP 500
Previously only 502/503/504 triggered instance rotation. A 500 from one
instance (e.g. triton.squid.wtf choking on a specific query) would stop
the search entirely instead of trying the next instance.
2026-03-16 13:56:20 -07:00
Broque Thomas
e02ad6e86e Fix discovery_recent_albums table recreations dropping Deezer columns
Both NOT NULL and profile v2 migrations now include album_deezer_id and
artist_deezer_id columns, use safe shared-column data copy instead of
SELECT *, and add album_deezer_id to UNIQUE constraints.
2026-03-16 13:52:22 -07:00
Broque Thomas
f10beeea0a Add source badges (Spotify/iTunes/Deezer) to watchlist artist cards
Shows which metadata sources each artist is matched to with small
colored badges on the card. Also adds deezer_artist_id to the
watchlist API response and fixes the data-artist-id fallback chain
to include Deezer-only artists.
2026-03-16 13:37:53 -07:00
Broque Thomas
46ac46134b Add Deezer as configurable free metadata fallback source alongside iTunes
Users can now choose between iTunes/Apple Music and Deezer as their free
metadata source in Settings. Spotify always takes priority when authenticated;
the fallback handles all lookups when it's not.

Core changes:
- DeezerClient: full metadata interface (search, albums, artists, tracks)
  matching iTunesClient's API surface with identical dataclass return types
- SpotifyClient: configurable _fallback property switches between iTunes/Deezer
  based on live config reads (no restart needed)
- MetadataService, web_server, watchlist_scanner, api/search, repair_worker,
  seasonal_discovery, personalized_playlists: all direct iTunesClient imports
  replaced with fallback-aware helpers

Database:
- deezer_artist_id on watchlist_artists and similar_artists tables
- deezer_track_id/album_id/artist_id on discovery_pool and discovery_cache
- Full CRUD for Deezer IDs: add, read, update, backfill, metadata enrichment
- Watchlist duplicate detection by artist name prevents re-adding across sources
- SimilarArtist dataclass and all query/insert methods handle Deezer columns

Bug fixes found during review:
- Similar artist backfill was writing Deezer IDs into iTunes columns
- Discover hero was storing resolved Deezer IDs in wrong column
- Status cache not invalidating on settings save (source name lag)
- Watchlist add allowing duplicates when switching metadata sources
2026-03-16 13:12:12 -07:00
Broque Thomas
a02d14a23a Add URL history pills for YouTube, Deezer, and Spotify Link sync tabs
Saves successfully loaded playlist URLs to localStorage and displays
them as clickable pills between the input bar and playlist container.
Clicking a pill re-loads that URL; X button removes it. Max 10 per
source, most recent first. Source-colored hover accents match each
tab's brand styling.

Also fixes duplicate playlist bug — YouTube and Spotify Public now
check for already-loaded playlists before making API calls, preventing
broken duplicate cards when the same URL is entered twice.
2026-03-16 10:35:45 -07:00
Broque Thomas
e2ba879197 Update recent updates modal with library history, MBID repair, and latest fixes 2026-03-16 10:14:21 -07:00
Broque Thomas
ce3bbd86c8 Fix $year template variable empty for playlist/sync downloads & empty bracket cleanup
Two issues fixed:
1. Download Missing modal fallback path hardcoded release_date: '' and
   discarded album metadata that discovery had already found on track_info.
   Now extracts release_date, image_url, album_type, and total_tracks from
   the enriched track data, fixing empty $year for all non-album-page
   downloads (playlist syncs, wishlist, Tidal/streaming sources).
2. _sanitize_context_values turned empty strings into '_' via
   _sanitize_filename, so template cleanup regex couldn't match empty
   brackets like (). Now skips sanitization for empty strings so the
   existing () [] {} cleanup works correctly.
2026-03-16 10:10:54 -07:00
Broque Thomas
837c5ff680 Add persistent library history tracking downloads and server imports
New library_history table logs every completed download and every new
track imported from Plex/Jellyfin/Navidrome. A "History" button next
to "Recent Activity" on the dashboard opens a modal with Downloads and
Server Imports tabs, album art thumbnails, quality/source badges, and
pagination.
2026-03-16 09:36:05 -07:00
Broque Thomas
f4af4ea7db Fix missing album cover art in download bubbles for redownload and issue downloads 2026-03-16 08:40:16 -07:00
Broque Thomas
87b39634a0 Fix MusicBrainz recording matches with wrong titles & add MBID mismatch repair job
Add minimum 60% title similarity gate to match_recording() — prevents
artist bonus + MB score from pushing unrelated titles past the confidence
threshold (e.g. "Sweet Surrender" matching "Answers" by same artist).

New MBID Mismatch Detector repair job reads embedded MusicBrainz recording
IDs from audio files, verifies them against the MusicBrainz API, and flags
tracks where the MBID points to a different song. Fix action strips the bad
MBID tag so media servers like Navidrome fall back to correct file tags.
2026-03-16 07:52:00 -07:00
Broque Thomas
7871f4581c Add cancel button for watchlist scans (manual and automation-triggered) 2026-03-15 23:24:18 -07:00
Broque Thomas
c90fff37f1 Fix service status labels missing HiFi and Qobuz display names 2026-03-15 23:05:56 -07:00
Broque Thomas
e6fdefec1f Update recent updates with HiFi, Spotify Link, and latest fixes 2026-03-15 22:56:40 -07:00
Broque Thomas
ec389c5ae8 Add HiFi as free lossless download source via public hifi-api instances
New download mode alongside Soulseek, YouTube, Tidal, and Qobuz. Uses
community-run REST API instances (no auth required) that serve Tidal CDN
FLAC streams. Features quality fallback chain (hires→lossless→high→low),
automatic instance rotation on failure, and full hybrid mode support.

Also fixes 6 missing streaming source checks for HiFi and Qobuz in the
frontend that were blocking playback with "format not supported" errors.
2026-03-15 22:53:34 -07:00
Broque Thomas
483e45cbc0 Add Spotify Link tab for public playlist/album scraping without API credentials
Scrapes Spotify's embed endpoint to extract track data from any public
playlist or album URL. Full discovery flow with Deezer parity: parse →
card → discovery modal → live progress → sync → download missing.

- New scraper: core/spotify_public_scraper.py (embed endpoint parsing)
- 12 API endpoints mirroring Deezer's discovery/sync/download flow
- WebSocket live discovery updates via spotify_public_discovery_states
- Green-branded tab, cards, and input styling (#1DB954)
- Album vs playlist detection with distinct card icons (💿/🎵)
- Download persistence: card click reopens download modal after close
- Card phase reset on cancel/completion (closeDownloadMissingModal)
- Backend download linking for spotify_public_ and deezer_ prefixes
- Completion handlers (V2 + no-missing-tracks) for both platforms
- Source URLs stored on mirrored playlists for future auto-refresh
2026-03-15 21:25:05 -07:00
Broque Thomas
da2b42b59a Add Redownload button to enhanced library view & fix album download mode
- Redownload button on each album in enhanced view (admin only)
- Uses same flow as artist page: fetches API tracklist, opens Download
  Missing modal with force-download option
- Register dashboard bubbles for library redownload and issue downloads
- Add library_redownload_ prefix to album download whitelist so it uses
  1 worker with source reuse and sends full album context (release_date
  for year in folder name)
2026-03-15 19:15:46 -07:00
Broque Thomas
0742cc45e6 Add hemisphere setting for seasonal playlists on Discover page
Southern hemisphere users now see correct seasons (e.g. March = Autumn,
December = Summer). Holidays (Halloween, Christmas, Valentine's) stay
calendar-fixed regardless of hemisphere.

- Hemisphere dropdown in Discovery Pool Settings
- GET/POST /api/discovery/hemisphere endpoints
- Season detection offsets months by 6 for southern hemisphere
- Stored in metadata table, defaults to northern
2026-03-15 18:49:51 -07:00
Broque Thomas
2003e58358 Add Spotify rate limit guards to all repair jobs
Repair jobs were making Spotify API calls without checking the global
rate limit ban, causing them to churn through every item getting rejected
one by one during a ban. Now all Spotify calls in repair jobs check
context.is_spotify_rate_limited() first and skip/fallback gracefully.

- Add is_spotify_rate_limited() helper to JobContext (base.py)
- Guard calls in track_number_repair, album_completeness,
  missing_cover_art, and metadata_gap_filler
- Jobs fall through to iTunes/MusicBrainz fallbacks when rate-limited
2026-03-15 16:01:48 -07:00
Broque Thomas
186671aa2e Add play button to repair findings & increase finding image sizes
- Play button on acoustid_mismatch, acoustid_no_match, track_number_mismatch, fake_lossless, dead_file, orphan_file findings
- Uses playLibraryTrack() for proper media player integration (track info, sidebar, album art)
- Data attributes for safe escaping instead of inline onclick strings
- Finding images increased from 56px to 150px with hover effects
- Improved detail panel spacing and media card layout
2026-03-15 15:46:30 -07:00
Broque Thomas
078b1130f8 Fix watchlist migration dropping profile_id & fix profile delete dialog hidden behind overlay
- Watchlist nullable migration now preserves profile_id column and composite
  UNIQUE constraints when rebuilding the table
- Profile support migration always repairs missing profile_id columns on all
  tables, even if the migration metadata key already exists (handles tables
  rebuilt by other migrations)
- Confirm dialog z-index raised to 100000 to appear above profile picker
  overlay (99999), fixing invisible delete confirmation
2026-03-15 15:21:19 -07:00
Broque Thomas
219f453b74 update recent updates 2026-03-15 14:41:32 -07:00
Broque Thomas
25cc96e120 Fix watchlist NOT NULL constraint failing for iTunes-only artists
The migration to make spotify_artist_id nullable was using fragile string
matching against CREATE TABLE SQL, which silently failed for some databases.
Now uses PRAGMA table_info to reliably detect the NOT NULL flag.
2026-03-15 14:23:42 -07:00
Broque Thomas
ab21855af3 Enrich repair findings with album art, artist images & live job progress
- All 9 repair jobs now emit report_progress() for real-time card updates
  (phase, log lines, per-item activity) via WebSocket repair:progress events
- Enrich finding details with album/artist thumb URLs across all repair jobs
  (dead_file, duplicate, metadata_gap, album_completeness, missing_cover_art,
  acoustid_scanner, track_number_repair, fake_lossless, orphan_file)
- Track number repair: return match_score from fuzzy matching, add suffix-based
  DB lookup for album/artist art (handles cross-environment path mismatches)
- Fix Plex/Jellyfin relative thumb URLs in findings endpoint via fix_artist_image_url
- Labeled media cards in finding detail panels (album title + artist name under images)
- Dashboard tooltip shows current job name + per-job progress instead of stale stats
2026-03-15 14:08:26 -07:00
Broque Thomas
f08550140f Add whistle logo to maintenance modal header & harden fix handlers
- Add whisoul.png to modal header with subtitle text and gradient background
- Responsive: smaller logo on mobile, hide subtitle
- Share _resolve_file_path in repair_worker for cross-environment path compat
- Use path resolution in orphan and duplicate file deletion
- Guard directory cleanup against removing the transfer folder itself
- Restore correct button label text on fix error recovery
2026-03-15 13:25:40 -07:00