soulsync/core/metadata
BoulderBadgeDad 6bc2836f47 Feature: preferred album-art source selection (opt-in, ordered, with fallback)
Lets users pick which providers' cover art to use and in what priority,
generalizing the single prefer_caa_art toggle into an ordered, mix-and-match
list (Sokhi's request). Fully opt-in: default album_art_order is [], so every
existing install is byte-for-byte unchanged until the user enables sources.

How it works:
- Per album, walk the user's ordered sources top-to-bottom; the first source
  that actually has THIS album's cover wins. A miss falls through to the next;
  if all miss, the download's own art is kept (today's default). The worst case
  is always exactly the cover you'd get today -- never wrong art, never an
  error into the download.
- Connection-gated: a source is only tried when the user is connected to it
  (free sources CAA/Deezer/iTunes/AudioDB always; Spotify only when
  authenticated). Tidal/Qobuz/HiFi deferred (cover-URL construction + no clean
  core accessor -- not shipping unverified extraction).
- Album-match validated: a source's art is used only when the album it returns
  matches the requested artist+album (significant-token subset, tolerant of
  Deluxe/Remastered/articles/feat./multi-artist). A loose top search hit for a
  different record is treated as a miss -> guarantees no wrong-album art.
- The list supersedes the legacy prefer_caa_art toggle: when album_art_order is
  non-empty it is the sole authority (add 'caa' to the list to use Cover Art
  Archive), and prefer_caa_art is neutralized for both the embedded-tag art and
  cover.jpg paths. With an empty list, prefer_caa_art behaves exactly as before.

Implementation:
- core/metadata/art_sources.py: pure resolver -- effective_art_order (config +
  legacy back-compat) and resolve_cover_art (ordered walk + fallback,
  exception-safe per source). No network/config/DB; fully unit-testable.
- core/metadata/art_lookup.py: availability gating, per-source lookups against
  existing clients (Deezer/iTunes/AudioDB/Spotify search + CAA via MBID),
  album-match validation, per-album caching, and select_preferred_art_url --
  the single gate the pipeline calls (no-op unless an explicit list is set).
- core/metadata/artwork.py: wired into embed_album_art_metadata and
  download_cover_art, gated so no configured list == current behavior.
- web_server.py: GET /api/metadata/art-sources (connected sources only).
- config/settings.py: default album_art_order: [].
- webui (index.html + settings.js): reorderable list in Core Features reusing
  the hybrid-source-list pattern + real service logos (with emoji fallback);
  load/save wired through the existing metadata_enhancement settings flow.
  loadArtSourceOrder populates the saved order synchronously (filtered to known
  sources, not availability) so a save before the availability fetch resolves,
  or a temporarily-disconnected source, can never wipe the saved order.

Tests: 40 unit/seam tests (resolver ordering/fallback/back-compat, availability,
per-source extraction, album-match validation incl. wrong-album/wrong-artist
rejection, caching, exception-safety, the off-by-default gate). Full metadata
suite still green (610 passed) -- the gated integration changes nothing when no
list is configured.

Note: the settings UI (DOM-heavy, not unit-testable in the JS harness) and the
live per-source art-fetch quality are validated by manual testing.
2026-06-01 11:45:07 -07:00
..
__init__.py Add MusicBrainz as a metadata source 2026-05-18 18:47:13 -07:00
album_mbid_cache.py Final silent-exception sweep + ruff S110 lint guardrail — ~45 sites 2026-05-07 11:16:06 -07:00
album_tracks.py Show MusicBrainz release variants in import 2026-05-24 09:33:19 -07:00
art_lookup.py Feature: preferred album-art source selection (opt-in, ordered, with fallback) 2026-06-01 11:45:07 -07:00
art_sources.py Feature: preferred album-art source selection (opt-in, ordered, with fallback) 2026-06-01 11:45:07 -07:00
artist_image.py Move metadata helpers into package modules 2026-04-29 11:28:42 +03:00
artist_resolution.py Fix Soulseek downloads losing collab artist tags 2026-05-13 22:11:20 -07:00
artwork.py Feature: preferred album-art source selection (opt-in, ordered, with fallback) 2026-06-01 11:45:07 -07:00
cache.py Metadata cache: hard LRU row cap to stop unbounded growth (7.6GB incident) 2026-05-30 23:22:05 -07:00
common.py Surface silent exceptions across remaining modules — ~70 sites 2026-05-07 10:28:58 -07:00
completion.py Tighten artist discography soundtrack matching 2026-05-17 22:51:38 -07:00
discography.py feat: add explicit badges to discography modal and artist-detail cards 2026-05-12 23:35:08 -04:00
discography_filters.py Skip already-owned tracks during download discography 2026-05-12 15:10:41 -07:00
enrichment.py Stop writing TRCK as "6/0" when album total_tracks is unknown 2026-05-14 15:25:16 -07:00
lookup.py Move metadata API into package 2026-04-29 08:10:18 +03:00
lyrics.py Move shared metadata helpers into package 2026-04-27 19:54:44 +03:00
multi_source_search.py Fix Download Discography pulling wrong artist + log routing 2026-05-06 13:03:43 -07:00
registry.py Add MusicBrainz as a metadata source 2026-05-18 18:47:13 -07:00
release_type.py fix(metadata): surface MusicBrainz 'Other' release-groups in discography (#650) 2026-05-19 20:20:28 -07:00
relevance.py MB rerank: prefer_known_duration is now a score boost, not a tiebreaker 2026-05-27 08:13:28 -07:00
service.py Move metadata API into package 2026-04-29 08:10:18 +03:00
similar_artists.py Move metadata helpers into package modules 2026-04-29 11:28:42 +03:00
source.py Fix: import overwrites album-artist tag to "Unknown Artist" (#735) 2026-05-29 09:30:38 -07:00
status.py Make Spotify status updates event-driven 2026-05-02 22:02:01 +03:00
track_number_format.py Stop writing TRCK as "6/0" when album total_tracks is unknown 2026-05-14 15:25:16 -07:00
types.py fix(metadata): surface MusicBrainz 'Other' release-groups in discography (#650) 2026-05-19 20:20:28 -07:00