Commit graph

688 commits

Author SHA1 Message Date
Broque Thomas
a33f891fa6 Add per-artist watchlist lookback period override
New "Scan Lookback" dropdown in the watchlist artist config modal.
Each artist can override the global lookback period (7d to entire
discography). Default is "Use Global Setting" (NULL in DB).

- Database: lookback_days INTEGER DEFAULT NULL on watchlist_artists,
  auto-migrated on startup
- Scanner: checks per-artist lookback_days first, falls back to
  global discovery_lookback_period if NULL
- Backend: GET/POST /api/watchlist/artist/<id>/config includes
  lookback_days. Changing lookback clears last_scan_timestamp to
  force a rescan with the new window
- Frontend: dropdown with 8 options in artist config modal
- Fully backwards compatible — existing artists unchanged
2026-03-27 08:17:02 -07:00
Broque Thomas
835ddcdbd5 Fall back to stream source when library file not found on disk
When clicking play on an "In Library" track, if the file can't be
resolved on disk (e.g., media server path not accessible from
SoulSync), silently falls back to streaming via the configured
stream source instead of showing an error.
2026-03-26 19:48:03 -07:00
Broque Thomas
9fcbd323a5 Add stream source setting, auto-update yt-dlp on container start
Stream source:
- New setting in Settings → Downloads: "Stream / Preview Source"
- Options: YouTube (instant, default) or Active Download Source
- YouTube streams require no auth and are instant
- If active source is Soulseek, automatically falls back to YouTube
- Uses direct client search (bypasses orchestrator's download mode)
- Config key: download_source.stream_source

Docker:
- entrypoint.sh now runs pip install -U yt-dlp on every container
  start, so Docker users always have the latest yt-dlp without
  rebuilding the image
2026-03-26 19:27:35 -07:00
Broque Thomas
8e41feaade Fix Navidrome playlist sync truncation, clarify discovery label
Navidrome fix:
- createPlaylist and other write operations now use POST instead of
  GET. Large playlists (161+ tracks) exceeded URL length limits when
  all songId params were in the query string, causing silent truncation
  (e.g., only 6 of 161 tracks added). POST sends params as form body
  with no size limit.
- Write operation timeout bumped to 30s (was 10s)
- _WRITE_ENDPOINTS set defines which Subsonic endpoints use POST

UX fix:
- Mirrored playlist cards now show "161/161 discovered on Spotify"
  instead of just "161/161 discovered" — clarifies that discovery
  means metadata matching, not library ownership
2026-03-26 14:15:20 -07:00
Broque Thomas
f19db4ecce Add launch PIN lock screen with credential-based recovery
Security:
- Toggle in Settings → Advanced: "Require PIN to access SoulSync"
- Full-screen lock overlay on every page load when enabled
- PIN validated server-side against admin profile (bcrypt hash)
- Inline PIN creation if admin has no PIN set, change PIN button if set
- One-time session flag: verify-launch-pin sets it, /profiles/current
  consumes it — every page load re-requires PIN

Recovery:
- "Forgot PIN?" on lock screen switches to credential verification
- User pastes any configured API key/token/secret (Spotify, Tidal,
  Plex, Jellyfin, Navidrome, ListenBrainz, AcoustID, Last.fm, Genius)
- Server checks against all 9 stored values — any match clears PIN
  and disables lock, with toast guiding to Settings to set a new one

Profile switch integration:
- Entering PIN during profile switch also sets launch_pin_verified
  flag, preventing double-PIN prompt on the subsequent page reload

Updated version modal and helper What's New with this feature.
2026-03-26 13:15:36 -07:00
Broque Thomas
59e258a922 Add idle glow pulse to help button for new users
Subtle accent border glow breathes every 3s on the ? button until
the user opens the menu for the first time, then stops permanently
via localStorage. Helps new users notice the help system exists.
2026-03-26 12:32:03 -07:00
Broque Thomas
5305481187 Update version modal and helper What's New with latest features
- Added 6 new sections to /api/version-info: Interactive Help System,
  Rich Artist Profiles, Enhanced Library Manager, In Library Badges,
  FLAC Bit Depth, Enrichment Worker Improvements
- Consolidated helper WHATS_NEW under v2.1 (no version bump) with
  12 entries covering all recent features with Show me navigation
- Removed stale v2.2 key from WHATS_NEW that referenced unbumped version
2026-03-26 12:25:53 -07:00
Broque Thomas
c1287f0ec0 Helper V2 complete + enrichment worker fixes
Helper system phases 2-7:
- Setup Progress: onboarding checklist with progress ring, auto-detection
  via /status, /api/settings, /api/library, /api/watchlist, /api/automations
- Quick Actions: accent pill buttons in popovers (service cards get
  "Open Settings" and "View Docs" actions)
- Keyboard Shortcuts: full-screen overlay with key cap styling, grouped
  by scope (Global, Player, Helper, Forms)
- Search: fuzzy search across 200+ help entries, 11 tours, and shortcuts
  with cross-page navigation via _guessPageFromSelector()
- What's New: version-tagged highlights with "Show me" navigation,
  red badge on ? button for unseen versions, older version cycling
- Troubleshoot: scans dashboard service cards for disconnected/error
  states, shows fix steps with action buttons, "All Clear" when healthy
- Contextual menu: page-aware tour suggestion at top of menu
- Ctrl+K / Cmd+K opens helper search globally
- First-launch welcome tooltip with pulsing ? button
- Redesigned floating button (48px, accent gradient, glass effect)
- Redesigned menu (unified card panel, accent left-stripe on contextual)

Enrichment worker fixes:
- AcoustID: individual recording matches downgraded INFO→DEBUG to reduce
  log noise (14 lines for one track → 1 summary line)
- Name normalization: strip " - Suffix" dash format (Spotify) same as
  "(Suffix)" parens format across all 8 workers. Fixes false mismatch
  on tracks like "Electric Eyes (Studio Brussels Remix)" vs
  "Electric Eyes - Studio Brussels Remix" (was 0.54, now matches)
2026-03-26 12:20:58 -07:00
Broque Thomas
019d485e2a Guided tours V2: 11 tours with 97 steps covering every page
- Rewrote all 6 existing tours to match dashboard quality (was 31 steps total)
- Added 5 new tours: Discover, Stats, Import, Settings, Issues
- Fixed broken selectors in first-download and artists-browse tours
  (referenced elements that only exist after user interaction)
- All tours now work on fresh page load — describe post-interaction UI
  instead of pointing at empty containers
- Sync tour expanded from 5→11 steps covering all 8 source tabs
- Library tour expanded from 2→7 steps with filters, pagination, detail view
- Automations tour expanded from 3→6 steps with builder detail and signals
2026-03-26 10:47:48 -07:00
Broque Thomas
38ee6b6957 Complete helper system: all pages covered (200+ entries)
Helper content for every major page and interactive element:
- Dashboard: service cards, stats, 9 tools, watchlist/wishlist modals
- Sync: 8 source tabs, discovery modals, URL inputs, sidebar
- Search: enhanced/basic modes, filters, download manager, library badges
- Discover: hero, 14 playlists, cache sections, tabs, build tool
- Artists: search, hero profile, discography, similar artists
- Automations: list, cards, hub tabs, builder slots, history
- Library: grid, detail hero, filters, enhanced view, tag modals
- Stats: time range, charts, rankings, library health, storage
- Import: staging, album/singles tabs, matching, processing
- Settings: 5 tabs, all source configs, quality profile, file org,
  post-processing, appearance, advanced, API keys

Docs updated with 4 new sync subsections (Spotify Public, Deezer,
Import from File, Sync History).
Total: 200+ contextual help entries with documentation links.
2026-03-26 09:50:40 -07:00
Broque Thomas
1e9abb588c Add clickable artist name link in download modal hero subtitle
The artist name in album download modals is now a clickable link
that navigates to the Artists page with that artist's discography.
Uses the correct source-specific artist ID from the album data.
Works on enhanced search, artists page, and discover page modals.
Excluded from playlist, wishlist, and default contexts where the
subtitle isn't an artist name.
2026-03-26 08:22:52 -07:00
Broque Thomas
77b6d4927c Add View Discography button to watchlist artist detail overlay
Clicking the button closes the watchlist modal and navigates to the
Artists page with the artist's discography loaded. Uses the correct
source ID based on the active metadata source (Spotify/Deezer/iTunes).
2026-03-26 08:01:31 -07:00
Broque Thomas
f6709c7cc3 Add library ownership badges to enhanced search results
Search results now show "In Library" badges on albums and tracks
that already exist in the user's library. Badges appear with a
staggered fade-in animation after results render (non-blocking).

- Backend: /api/enhanced-search/library-check endpoint builds
  owned album/track sets in 2 queries, O(1) lookups per result
- Frontend: async call after render, 30ms stagger per badge
- Tracks in library get play button rewired for direct playback
  from media server instead of searching download sources
- Fixed enhanced search album card text not visible (info div
  now absolute-positioned with gradient overlay)
- Download manager panel hidden by default for more search space
2026-03-25 19:39:19 -07:00
Broque Thomas
6c3b9ddfc2 Optimize watchlist card CSS, backfill missing album covers
Watchlist cards: removed spring-bounce transitions, staggered
animations, and multi-layer hover shadows. Added CSS containment
and will-change for smoother scrolling.

Recent releases: backfill missing album cover art on page load
via metadata source lookup. Persists found covers to database.
2026-03-25 18:36:45 -07:00
Broque Thomas
f725b66afe Extend helper system: wishlist tracks, download modal, track rows
Adds contextual help coverage for:
- Wishlist track list view (album cards, individual tracks, batch bar)
- Download modal (hero stats, track analysis table, all action buttons)
- Track rows (checkbox, library match, download status, actions)
- Force Download and Playlist Folder toggles
- Add to Wishlist button
- Dynamic ID selectors for playlist-scoped elements
2026-03-25 18:19:52 -07:00
Broque Thomas
38ba6ddbc1 Add interactive contextual help system with floating helper button
New feature: click the floating ? button (bottom-right corner) to
enter help mode. Click any UI element to see a popover explaining
what it is and how to use it. Covers Dashboard + Sidebar + Watchlist.

- Floating button always visible above modals (z-index 999999)
- Click interception via capture phase prevents accidental actions
- Popover with smart positioning (right/left/below fallback)
- Arrow pointing to target element with accent highlight pulse
- "View full documentation" links navigate to the correct docs section
- Escape key dismisses popover or exits help mode
- Works inside modals (watchlist, artist config, global settings)
- 45+ contextual help entries covering sidebar nav, service cards,
  stat cards, all 9 tool cards, watchlist modal buttons, artist
  config options, content filters, and activity feed
- Separate helper.js file for maintainability
2026-03-25 17:34:43 -07:00
Broque Thomas
d248c36da1 Redesign Artists page: rich hero section, full-bleed cards, multi-source genres
Artists page hero section:
- Large portrait artist photo (400x480px, rounded rectangle)
- Blurred saturated background from artist image
- 2.6em bold name with text shadow
- Real service logo badges (Spotify, MusicBrainz, Deezer, iTunes,
  Last.fm, Genius, Tidal, Qobuz) — matching library page
- Genre pills merged from metadata cache + Last.fm tags
- Last.fm bio with read more/show less toggle
- Last.fm listener count + playcount stats (large bold numbers)
- Backend enriches discography response with artist_info from
  metadata cache + library (all service IDs, Last.fm data, genres)

Album/Single/EP cards:
- Full-bleed cover art filling entire card with gradient overlay
- Album name + year overlaid at bottom over dark gradient
- Image zoom on hover, accent glow for dynamic-glow cards
- Responsive grid (220px desktop, 170px tablet, 140px mobile)

Similar artist cards:
- Full-bleed image cards matching library artist card style
- Gradient overlay with name at bottom, aspect-ratio 0.8
- Grid-controlled sizing via existing responsive breakpoints

Genre explorer (multi-source):
- Queries all allowed sources (iTunes+Deezer always, Spotify when
  authed) via _get_genre_allowed_sources() helper
- Deezer genre support: genre_id mapping from search results,
  one-time backfill from stored raw_json, album-to-artist propagation
- Genre deep dive deduplicates artists across sources
- Source dots on artists/tracks in deep dive modal
- Artist clicks route through source-specific client
- Album endpoint falls back across sources when IDs don't match
- Genre explorer cached 24hr in-memory, positioned at top of
  Discover page below hero slider

All changes mobile responsive with proper breakpoints.
2026-03-25 11:49:51 -07:00
Broque Thomas
e08462a002 Multi-source genre explorer with Deezer genre support and cross-source routing
Genre explorer and deep dive modal now combine data from all available
metadata sources (iTunes + Deezer always, Spotify when authenticated).
Artists are deduplicated by name across sources, preferring entries
with images. Source dots (green/red/purple) indicate data origin.

Deezer genre support:
- Extract genre_id from Deezer album search responses via ID-to-name
  mapping table (26 Deezer genre categories)
- Extract full genre names from Deezer get_album responses
- One-time backfill updates existing cached albums from stored raw_json
- Propagate album genres to Deezer artist entities

Cross-source album routing:
- /api/discover/album endpoint uses source-specific client (iTunes or
  Deezer) based on the item's source, not just the active fallback
- Spotify path falls back to active fallback when album not found
- Track clicks use album_id directly instead of name-based resolution
- resolve-cache-album adds partial match and live search fallback

Other fixes:
- Genre explorer positioned at top of Discover page (below hero)
- Genre explorer results cached 24hr in-memory for fast reload
- Related genres computed from all albums by matched artists
- Artist clicks open Artists page with discography (not library detail)
- Discovery pool genre queries restored to source-filtered (Browse by
  Genre tabs stay source-isolated as designed)
2026-03-24 19:36:11 -07:00
Broque Thomas
6101832ee1 Add search filter and rematch to discovery pool modal
Discovery pool lists (matched and failed) now have a search input
that filters tracks client-side by name, artist, or playlist.

Matched tracks get a "Rematch" button that opens the fix modal in
cache-only mode — deletes the old cache entry and saves the new
match directly to the discovery cache via /api/discovery-pool/rematch.
This works regardless of whether a mirrored playlist track exists.

Failed tracks retain the existing "Fix Match" flow unchanged.
2026-03-24 13:07:04 -07:00
Broque Thomas
54b02b86c1 Update help docs: add HiFi/Deezer sources, quality fallback, template vars
- Overview and setup sections now list all 6 download sources
- Services table adds HiFi (no auth) and Deezer (ARL token)
- Enhanced Search documents multi-source tabs (Spotify/iTunes/Deezer)
- Download Sources table adds HiFi and Deezer rows
- Post-processing explains AcoustID skip for streaming sources and
  the new artist/title verification for streaming candidates
- File Organization documents $albumtype, $disc, and all template vars
- Quality Profiles adds callout for per-source fallback toggle
- Settings credentials list adds HiFi and Deezer entries
2026-03-24 12:30:26 -07:00
Broque Thomas
89cfea0fe7 Add per-source quality fallback toggle for streaming downloads (#187)
Each streaming source (Tidal, Qobuz, HiFi, Deezer) now has an "Allow
quality fallback" checkbox in Settings. When disabled, the source only
tries the exact quality selected — if unavailable, it skips and lets
the orchestrator try the next source. Default is ON (current behavior).
2026-03-24 11:42:47 -07:00
Broque Thomas
e28aeccfd1 Redesign pool fix match modal: fixed height, no layout shift (#186)
Modal now uses a fixed 600px height from open — results scroll within
a dedicated area instead of growing the modal and pushing inputs up.
This eliminates the layout shift that caused accidental result clicks.

Other fixes:
- Input fields now have labels (Track, Artist)
- Overlay dismiss uses mousedown with stopPropagation to prevent
  accidental close when clicking near inputs
- Reduced results from 50 to 20 for faster response
- Clean minimal design matching app style
- Mobile: full-screen modal, stacked inputs with 44px touch targets
2026-03-24 11:16:37 -07:00
Broque Thomas
7b615a9534 Redesign enhanced search results: modern flat layout with responsive cards
- Search bar: stripped heavy purple chrome, minimal dark input style
- Dropdown: inline flow instead of overlay, hides page header when active
- Section labels: flat uppercase text, no bordered glass boxes
- Artist cards: full-bleed photo with gradient overlay and name at bottom
  (matches library page style), flexbox wrap layout with fixed dimensions
- Album cards: discover-style dark cards in horizontal scroll on desktop,
  wrap to 2-per-row on mobile
- Track rows: clean flat list, subtle hover, smaller cover art
- Source tabs: compact pills with per-source accent colors
- Renamed grid classes (enh-artists-grid, enh-albums-grid, enh-tracks-list)
  to avoid collision with generic .artists-grid rule
- Mobile: downloads-main-panel min-width:0 fix for 1190px overflow,
  cards use calc(50% - 8px) for 2-per-row fill, touch-friendly targets
2026-03-24 10:45:14 -07:00
Broque Thomas
96fa3fe388 Add SoulSync logo to sidebar header next to app name 2026-03-23 21:24:39 -07:00
Broque Thomas
e652726c22 Invert Tidal/Qobuz hero badges, add Artist Radio button and Last.fm play buttons
- Tidal and Qobuz SVG logos inverted on artist detail hero badges
- New Artist Radio button: clears queue, plays random artist track, enables radio
- Play buttons on Last.fm top tracks (hover reveal, resolves from library)
- Fixed inline JS escaping with data attribute delegation
2026-03-23 20:42:33 -07:00
Broque Thomas
4c3375745c Invert Tidal and Qobuz badge logos on library artist cards for visibility 2026-03-23 20:16:03 -07:00
Broque Thomas
cb32be4922 Refine settings page: glass buttons, accent section borders, softer inputs 2026-03-23 19:32:00 -07:00
Broque Thomas
dc6f0db916 Add missing placeholder-album.png to stop 404 spam in console 2026-03-23 18:05:11 -07:00
Broque Thomas
2ae5050ef1 Add Deezer download source: client, settings UI, ARL authentication
- New core/deezer_download_client.py: full download client with ARL auth,
  Blowfish decryption, quality fallback (FLAC/MP3 320/MP3 128), search,
  thread-safe download tracking. Not yet integrated into orchestrator.
- Settings UI: Deezer download quality selector, ARL token input, test
  connection button. Appears in download source dropdown and hybrid list.
- Test endpoint: /api/deezer-download/test verifies ARL and returns tier.
- Added deezer_download to settings save whitelist and sensitive paths.
- Fixed Spotify enrichment worker default to unpaused (like other workers).
2026-03-23 15:06:40 -07:00
Broque Thomas
7070b98756 Fix reorganize modal using hardcoded template instead of saved settings
The enhanced view reorganize modal had a hardcoded default path template
instead of loading the user's saved template from settings. Now fetches
the saved album_path template from /api/settings on modal open.
2026-03-23 12:47:25 -07:00
Broque Thomas
203f317236 Redesign download missing and wishlist modals with unified glass style 2026-03-22 22:16:25 -07:00
Broque Thomas
8d3d623f10 Redesign sync page playlist cards: unified glass style across all sources and mirrored playlists 2026-03-22 22:10:42 -07:00
Broque Thomas
1ac16ce0d4 Redesign dashboard cards: tool cards, service status, and system stats with unified glass style 2026-03-22 21:52:47 -07:00
Broque Thomas
e3d70da55a Add DB storage visualization + cache-powered discovery sections + Genre Deep Dive
- Stats page: database storage donut chart with per-table breakdown and total size
- Discover page: 5 new sections mined from metadata cache (zero API calls):
  Undiscovered Albums, New In Your Genres, From Your Labels, Deep Cuts, Genre Explorer
- Genre Deep Dive modal: artists (clickable → artist page), popular tracks,
  albums with download flow, related genre pills, in-library badges
- All cache queries filtered by active metadata source (Spotify/iTunes/Deezer)
- Stale cache entries (404) gracefully fall back to name+artist resolution
- Album cards show "In Library" badge, artist avatars scaled by prominence
2026-03-22 21:35:18 -07:00
Broque Thomas
c937045192 Mobile responsive overhaul: stats, artist detail, enhanced library, automations, hydrabase, docs
- Stats page: full mobile layout with compact cards, charts, ranked lists
- Artist hero: stacked layout, compact image/name/badges, top tracks below
- Enhanced library: meta header/expanded header stack vertically, track table
  collapses action columns into bottom sheet popover on mobile
- Automations: builder sidebar collapses, inputs go full width
- Hydrabase/Issues/Help: responsive stacking and compact layouts
- Fix grid blowout: add min-width:0 to stats grid children and overflow:hidden
2026-03-22 19:32:58 -07:00
Broque Thomas
93005298ee Cap Opus bitrate at 256kbps, fix lossy copy flavor text, redesign artist action buttons 2026-03-22 16:34:36 -07:00
Broque Thomas
21d7e65986 Speed up library page: split DB query, innerHTML rendering, staggered card animation 2026-03-22 16:14:28 -07:00
Broque Thomas
8c84189121 Add per-artist enrichment coverage rings to artist hero section 2026-03-22 15:56:12 -07:00
Broque Thomas
f6225ec9a8 Fix enrichment coverage: correct Spotify column name and add all 9 services 2026-03-22 15:36:25 -07:00
Broque Thomas
b59a0eaf95 Add play buttons to stats page with cover art support 2026-03-22 15:24:38 -07:00
Broque Thomas
9e75731f6c Add scrobbling to Last.fm/ListenBrainz + update What's New
Scrobbling:
- Last.fm: API signing, web auth flow, batch scrobble (50/request)
- ListenBrainz: submit_listens (batch 1000, listen_type=import)
- Worker hook: scrobbles unscrobbled events after each poll
- DB: scrobbled_lastfm/scrobbled_listenbrainz tracking columns
- Settings: API secret input, authorize button, scrobble toggles
- Config: lastfm.api_secret, lastfm.session_key, *.scrobble_enabled

What's New: added all features from this session (scrobbling,
personalized discovery, stats page, SoulID, lossy codecs, import,
hero redesign, Hydrabase, orphan fixes, year collection).
2026-03-22 14:38:53 -07:00
Broque Thomas
232481fd13 Personalize discovery playlists using listening stats
Integrates play history data into the discovery algorithm:

- Listening profile: _get_listening_profile() builds user's top artists,
  genres, play counts, and listening velocity from the last 30 days
- Artist genre cache: pre-built from local DB for O(1) genre lookups
- Release Radar: +10 genre affinity, +15 artist familiarity, -10 overplay
  penalty. Weights rebalanced to 45% recency + 25% popularity + bonuses
- Discovery Weekly: serendipity scoring within tiers — boosts unheard
  artists in preferred genres, penalizes overplayed artists
- Recent Albums: adaptive time window (21-60 days) based on listening
  velocity — heavy listeners get fresher content, casual listeners more
- New "Because You Listen To" sections: personalized carousels based on
  user's top 3 played artists via similar artists + genre fallback
- New endpoint: /api/discover/because-you-listen-to with artist images
- Frontend: BYLT sections with artist photo headers on discover page
- All changes gracefully fall back when no listening data exists
2026-03-22 13:54:37 -07:00
Broque Thomas
cfb0e85564 Add Listening Stats page with media server play data integration
Full stats dashboard that polls Plex/Jellyfin/Navidrome for play
history and presents it with Chart.js visualizations:

Backend:
- ListeningStatsWorker polls active server every 30 min
- listening_history DB table with dedup, play_count/last_played on tracks
- get_play_history() and get_track_play_counts() for all 3 servers
- Pre-computed cache for all time ranges (7d/30d/12m/all) rebuilt each sync
- Single cached endpoint serves all stats data instantly
- Stats query methods: top artists/albums/tracks, timeline, genres, health

Frontend:
- New Stats nav page with glassmorphic container matching dashboard style
- Overview cards (plays, time, artists, albums, tracks) with accent hover
- Listening timeline bar chart (Chart.js)
- Genre breakdown doughnut chart with legend
- Top artists visual bubbles with profile pictures + ranked list
- Top albums and tracks ranked lists with album art
- Library health: format breakdown bar, unplayed count, enrichment coverage
- Recently played timeline with relative timestamps
- Time range pills with instant switching via cache
- Sync Now button with spinner, last synced timestamp
- Clickable artist names navigate to library artist detail
- Last.fm global listeners shown alongside personal play counts
- SoulID badges on matched artists
- Empty state when no data synced yet
- Mobile responsive layout

DB migrations: listening_history table, play_count/last_played columns,
all with idempotent CREATE IF NOT EXISTS / PRAGMA checks.
2026-03-22 13:18:14 -07:00
Broque Thomas
598b63ba25 Add missing_lossy_copy to finding type labels and fixable types
Adds 'Convert' fix button and 'No Lossy Copy' type label for the
lossy converter repair job findings in the Library Maintenance UI.
2026-03-22 07:56:25 -07:00
Broque Thomas
491b89a1d2 Redesign library artist hero with Last.fm integration
- Add get_artist_top_tracks to Last.fm client (up to 100 tracks)
- Include lastfm_listeners, lastfm_playcount, lastfm_tags, lastfm_bio,
  and soul_id in artist detail API response
- New endpoint: /api/artist/<id>/lastfm-top-tracks for lazy loading
- Hero layout: image (160px) | center (name, badges, genres, bio,
  listener/play stats, progress bars) | right card (scrollable top
  100 tracks from Last.fm)
- Badges 36px with hover lift, bio in subtle card with Read More
  toggle, Last.fm tags merged with existing genres
- Numbers formatted: 1234567 → 1.2M
- Graceful degradation: sections hidden when Last.fm data unavailable
2026-03-21 23:04:34 -07:00
Broque Thomas
f9fc95c9f5 Add Opus and AAC codec options to lossy copy (Blasphemy Mode)
Lossy copy now supports MP3, Opus, and AAC (M4A) codecs with a
configurable dropdown in settings. Each codec uses the appropriate
ffmpeg encoder (libmp3lame/libopus/aac) and Mutagen tag writer
(ID3/Vorbis/MP4). Quality tag, filename substitution, and Blasphemy
Mode file cleanup all work per-codec. Backward compatible — existing
configs default to MP3.
2026-03-21 20:19:36 -07:00
Broque Thomas
a015e8653b Rename orphan file fix button from 'Delete File' to 'Resolve'
The handler now prompts for staging vs delete, so the button label
should not imply deletion is the only option.
2026-03-21 11:34:15 -07:00
Broque Thomas
4dba3757be Fix orphan detector false positives and add staging/delete choice
Orphan detector: add normalized tag matching that strips parentheticals
and brackets (feat. X, [FLAC 16bit], etc.) and tries first-artist-only
for comma-separated artists. Prevents false orphan flags for tracks
like "The Mountain (feat. Dennis Hopper...)" that exist in DB as
"The Mountain". All lookups remain O(1) set operations.

Orphan fix: replace auto-delete with user choice prompt. Single Fix
and Fix All both show modal asking "Move to Staging" or "Delete".
Move to Staging relocates file to import staging folder for proper
re-import with metadata matching. Fix action flows through API
endpoint → repair_worker.fix_finding → _fix_orphan_file handler.
Staging path uses docker_resolve_path for container compatibility.
2026-03-21 11:27:51 -07:00
Broque Thomas
85044261a4 Create trans2.png 2026-03-21 10:23:36 -07:00
Broque Thomas
2d511d0a16 Add SoulID worker with API-based debut year disambiguation
SoulID worker generates deterministic soul IDs for all library entities:
  - Artists: hash(name + debut_year) — searches iTunes + Deezer APIs,
    verifies correct artist by matching discography against local DB
    albums via MusicMatchingEngine, pools years from both sources and
    picks the earliest. Falls back to hash(name) if no match found.
  - Albums: hash(artist + album)
  - Tracks: song ID hash(artist + track) + album ID hash(artist + album + track)

Dashboard button with trans2.png logo, rainbow spinner, hover tooltip.
Worker orb with rainbow effect. SoulSync badge on library artist cards.
DB migration adds soul_id columns with indexes to artists/albums/tracks.
Migration version flag auto-resets artist soul IDs when algorithm changes.
2026-03-21 10:21:06 -07:00