soulsync/webui/static
Broque Thomas c11a5b7eab Multi-artist tag settings: implement artist_separator + feat_in_title + populate _artists_list
Three settings on Settings → Metadata → Tags were partially or
completely unimplemented. Reporter (Netti93) traced each one.

(1) `write_multi_artist` only "worked" because of a never-populated
    `_artists_list` field. `core/metadata/source.py` built
    `metadata["artist"]` as a hardcoded ", "-joined string but never
    assigned `metadata["_artists_list"]`. `core/metadata/enrichment.py`
    line 107 reads that field and gates the multi-value tag write
    on `len(_artists_list) > 1` — always saw an empty list, silently
    no-op'd the write.

(2) `artist_separator` (default ", ") was referenced in the UI +
    settings.js save path but ZERO Python code read the value. Every
    multi-artist track ended up with hardcoded ", " regardless of
    what the user picked.

(3) `feat_in_title` (when true: pull featured artists into the title
    as " (feat. X, Y)" and leave only primary in the ARTIST tag —
    Picard convention) had no implementation at all.

Fix in source.py:

* Populate `_artists_list` from the search response's artists array
* Read `feat_in_title` and `artist_separator` configs
* When `feat_in_title=True` and >1 artist: ARTIST = primary only,
  append "(feat. X, Y)" to title with double-append guard
* Else: ARTIST = artists joined with `artist_separator`
* Single-artist case unaffected by either setting

Double-append guard uses a word-boundary regex catching all common
"feat" variants source platforms produce — `feat`, `feat.`,
`featuring`, `ft`, `ft.` — case-insensitive. Substring matches
(e.g. "Aftermath" containing "ft") correctly fall through to the
append path.

Fix in enrichment.py ID3 branch:

* TPE1 stays as the display string (with separator or primary-only
  per the user's settings)
* Multi-value list goes to a separate `TXXX:Artists` frame (Picard
  convention) when `write_multi_artist` is on
* Pre-fix the ID3 path wrote TPE1 twice — single-string then list
  — and the second `add` overwrote the first, clobbering both the
  configured separator AND the feat_in_title semantics. Vorbis path
  was already correct (separate "artist" + "artists" keys).

Known limitation (flagged in WHATS_NEW): Deezer's `/search` endpoint
only returns the primary artist. The full contributors array lives
on `/track/<id>`. Enrichment uses search-result data so Deezer-
sourced tracks may still get only the primary artist until a follow-
up commit wires the per-track contributors fetch into the enrichment
flow. Spotify, Tidal, and iTunes search responses include all
artists so they work now.

23 new tests in `tests/metadata/test_multi_artist_tag_settings.py`:

* `_artists_list` populated for multi/single/no-artist cases
* `artist_separator` drives ARTIST string (default ", " + custom
  ";" + custom "; " + " & ")
* Single-artist case unaffected by either setting
* `feat_in_title=True` pulls featured to title, leaves primary in
  ARTIST
* `feat_in_title` no-op for single artist
* Double-append guard recognizes 9 source-title variants ("(feat.
  X)", "(Feat. X)", "(FEAT X)", "(feat X)", "(Featuring X)",
  "[feat. X]", "ft. X", "(ft X)", "FT. X")
* Substring guard test pins "Aftermath" doesn't false-positive
* Combined-settings precedence: feat_in_title wins ARTIST string
  but `_artists_list` carries everyone for multi-value tag

Full pytest 2711 passed.
2026-05-11 15:16:42 -07:00
..
docs update docs page 2026-03-13 08:35:28 -07:00
vendor Add WebSocket real-time updates with automatic HTTP polling fallback 2026-03-03 18:26:29 -08:00
api-monitor.js Stop watchlist countdown refetch loop 2026-05-02 22:02:00 +03:00
artists.png add static images 2026-03-09 17:35:46 -07:00
automation.png add static images 2026-03-09 17:35:46 -07:00
beatport-ui.js Add "All Libraries (combined)" mode to PlexClient 2026-05-06 15:39:19 -07:00
core.js Rename metadata source status selectors 2026-05-02 22:02:01 +03:00
dashboard.png add static images 2026-03-09 17:35:46 -07:00
discover-section-controller.js Discover controller — Cin pre-review polish 2026-05-07 20:35:10 -07:00
discover.js Your Albums: selectable wishlist modal + Tidal album resolution 2026-05-11 12:36:16 -07:00
discover.png add static images 2026-03-09 17:35:46 -07:00
docs-images-needed.md Service Badges, Page Headers, Docs Page, and Bug Fixe 2026-03-10 14:47:11 -07:00
docs.js Clean up stale Artists-page references in helper.js + docs.js 2026-04-22 20:29:32 -07:00
downloads.js Server playlist sync: append mode (preserve user-added tracks) 2026-05-10 22:52:11 -07:00
enrichment.js Repair job card badge — show pending count, not last-scan count 2026-05-07 08:28:17 -07:00
explorer.png Create explorer.png 2026-03-30 17:23:31 -07:00
favicon.png enlarge favicon 2026-02-13 16:11:32 -08:00
help.png Add media server setup, processing settings, text import, automation history, and streaming details 2026-03-10 15:35:18 -07:00
helper.js Multi-artist tag settings: implement artist_separator + feat_in_title + populate _artists_list 2026-05-11 15:16:42 -07:00
hydrabase.png Add Hydrabase dev UI and WebSocket support 2026-02-21 00:32:04 -08:00
import.png add static images 2026-03-09 17:35:46 -07:00
init.js Show artist breadcrumb on sidebar Library button when on artist-detail page 2026-05-01 17:49:34 -07:00
library.js Add download buttons + bulk action to artist top-tracks sidebar 2026-05-07 15:44:47 -07:00
library.png add static images 2026-03-09 17:35:46 -07:00
manifest.json Service worker for cover art + PWA manifest 2026-04-26 22:17:52 -07:00
media-player.js Split monolithic script.js (78K lines) into 17 domain modules 2026-04-21 23:52:30 -07:00
mobile.css Drop Show/Hide Results button + auto-restore cached results on navigate-back 2026-04-23 22:11:49 -07:00
pages-extra.js Fix bulk discography losing album source context (#399) 2026-04-30 12:42:46 -07:00
particles.js Add particle background toggle & optimize accent color caching 2026-03-13 16:05:22 -07:00
placeholder-album.png Add missing placeholder-album.png to stop 404 spam in console 2026-03-23 18:05:11 -07:00
pwa-icon-192.png Service worker for cover art + PWA manifest 2026-04-26 22:17:52 -07:00
pwa-icon-512.png Service worker for cover art + PWA manifest 2026-04-26 22:17:52 -07:00
search.js MusicBrainz: Fix artist images, total_tracks off-by-one, and Artist+Title queries 2026-04-24 10:17:59 -07:00
search.png add static images 2026-03-09 17:35:46 -07:00
settings.js Tidal: surface Favorite Tracks as virtual playlist (issue #502) 2026-05-10 21:36:22 -07:00
settings.png add static images 2026-03-09 17:35:46 -07:00
setup-wizard.css Add informative help text and tips to every setup wizard step 2026-04-11 23:26:01 -07:00
setup-wizard.js Rebrand folder terminology: Download→Input, Transfer→Output, Staging→Import 2026-04-18 17:35:20 -07:00
shared-helpers.js Fix non-admin profiles defaulting to Spotify on search picker 2026-05-07 11:53:02 -07:00
stats-automations.js Auto-import: bounded ThreadPoolExecutor + per-candidate UI state isolation 2026-05-09 17:45:42 -07:00
style.css Server playlist sync: append mode (preserve user-added tracks) 2026-05-10 22:52:11 -07:00
sw.js Service worker for cover art + PWA manifest 2026-04-26 22:17:52 -07:00
sync-services.js Server playlist sync: append mode (preserve user-added tracks) 2026-05-10 22:52:11 -07:00
sync-spotify.js Server playlist sync: append mode (preserve user-added tracks) 2026-05-10 22:52:11 -07:00
sync.png add static images 2026-03-09 17:35:46 -07:00
trans.png Add SoulID worker with API-based debut year disambiguation 2026-03-21 10:21:06 -07:00
trans2.png Create trans2.png 2026-03-21 10:23:36 -07:00
whisoul.png Add library repair worker and UI 2026-02-21 15:00:23 -08:00
wishlist-tools.js Rename status cache to metadata_source 2026-05-02 22:02:00 +03:00
worker-orbs.js Add Discogs to worker orbs animation and fix button styling 2026-04-02 17:18:31 -07:00