Commit graph

6 commits

Author SHA1 Message Date
Broque Thomas
c454b1ebaf MusicBrainz: Dedupe same-named homonyms in artist search results
Typing "michael jackson" returned 7 identical-looking cards because
MusicBrainz has many different PEOPLE sharing a canonical name — the
King of Pop plus a NZ poet, a photographer, a mashup artist, a
didgeridoo player, and more, all scoring 80+ on exact-name match.
All 7 passed the score filter. All 7 rendered with the same
fallback image because iTunes/Deezer only know the famous one.

Fix dedupes by normalized (lowercase, whitespace-trimmed) name before
building Artist dataclasses. Keeps the highest-scoring entry per name,
so the King of Pop (score 100) wins over the others (all score 80-81).
Artists with genuinely different names stay separate — a search for
"the beatles" still surfaces tribute bands if they're above threshold.

Implementation note: fetch `max(limit*3, 10)` from MB instead of
`limit` directly, so the dedup pool is large enough to still return
`limit` distinct artists after collapsing duplicates. Previously the
raw fetch was capped at the caller's limit, which would have left
fewer-than-requested results after dedup for common names.

3 new tests (49 total):
- Dedupe collapses 5 same-named entries to 1 (keeps highest score).
- Dedup key is case-insensitive and whitespace-normalized.
- Dedup preserves distinct names ("The Beatles" vs "The Beatles Revival"
  stay separate).

Live-verified: "michael jackson" now returns 1 card, "kendrick lamar"
returns 1 card.

Credit: kettui spotted duplicate Michael Jackson cards in the search UI.
2026-04-24 10:27:45 -07:00
Broque Thomas
b3722449fc MusicBrainz: Fix artist images, total_tracks off-by-one, and Artist+Title queries
Three bugs from kettui's follow-up review pass on the MusicBrainz
search PR, all fixed in one commit because they share UI context.

1. Missing artist images on MB artist results

MusicBrainz doesn't store artist images directly. My earlier commit
returned `image_url=None` on every artist result and trusted the
frontend's lazy-loader — but the lazy-loader's `/api/artist/<id>/image?
source=musicbrainz` endpoint had no handler for MusicBrainz, so it
silently returned None and the emoji placeholder stayed.

Fix plumbs the artist name through:
- `renderCompactSection` stashes `data-artist-name` on artist cards.
- `search.js` and `downloads.js` lazy-loaders pass `name=<artist>` as a
  query param.
- `/api/artist/<id>/image` accepts an optional `name` param.
- `metadata_service.get_artist_image_url` has a new `musicbrainz`
  branch: since MB has no artist art, it searches fallback sources
  (iTunes/Deezer by configured priority) for the artist name and
  returns the first image found.

Verified live — Metallica/Kendrick Lamar/Daft Punk all resolve to
Deezer artist images via the name lookup.

2. total_tracks off-by-one on tracks with a release

`_recording_to_track` initialized `total_tracks = 1` and then summed
media track-counts on top. For an 11-track album, it reported 12. An
adapter-level regression introduced when the recording-projection
helper was extracted during the main MB refactor.

Fix: initialize at 0, sum normally. Standalone recordings with no
release (can happen for uncredited remixes etc.) still report 1 via
an explicit fallback — so the existing "single track" case isn't
broken.

3. "Artist Album Title" queries buried specific albums in the
   discography list

Bare-name queries like "The Beatles Abbey Road" used to resolve "The
Beatles" as the artist and then browse their full discography — Abbey
Road was buried alphabetically among 200+ releases instead of being
the top result.

Fix adds a title-hint extractor. When the query starts with the
resolved artist name followed by more words, the trailing portion is
treated as a title hint. Browse results are filtered to those whose
release-group title contains the hint. If the filter matches nothing,
falls back to text-search with the hint as the title (the "keep the
old split-by-whitespace fallback" path kettui called for). If text-
search also misses, shows the full discography rather than nothing.

10 new tests in tests/test_musicbrainz_search.py (46 total):
- Title-hint extractor: basic match, case-insensitive, whitespace
  tolerance, bare-artist-no-hint, artist-not-prefix-no-hint, word-
  boundary required (no false splits on "Metallicasomething").
- Browse filtering by title hint.
- Text-search fallback when the title hint matches nothing in browse.
- Bare-artist queries return the full discography unfiltered.
- total_tracks for single-release, multi-disc, and no-release cases.
2026-04-24 10:17:59 -07:00
Broque Thomas
7dfe1ae88d MusicBrainz: Resolve release-group MBIDs to a release on album click
Clicking a MusicBrainz album returned 404 because the browse-based
search path now stores release-GROUP MBIDs in Album.id, but `get_album`
still hit `/ws/2/release/<mbid>` directly. Release-group MBIDs don't
resolve as release MBIDs — MB 404s. User log:

    GET /api/spotify/album/b88655ba...?source=musicbrainz → 404
    Error fetching release b88655ba...: 404 Client Error

The fix requires a two-step resolution for the new browse path:

1. Look up the release-group with `inc=releases+artist-credits` to get
   the list of releases inside (original + reissues + regional + promo
   editions). MB release-groups routinely hold 5-20 releases.
2. Pick a representative release: prefer Official status over Promo,
   prefer releases with a real tracklist over stubs, then earliest date.
3. Fetch that release's full tracklist via `get_release`.

Two extra seconds at the 1-rps rate limit, but it's on click, not on
search results rendering.

Structure:
- New `MusicBrainzClient.get_release_group(mbid, includes)` method.
- New `_pick_representative_release(releases)` helper encapsulates the
  ranking logic.
- Tracklist projection extracted into `_render_release_as_album` so
  both paths share the same shape construction.
- `get_album` tries release-group first; falls back to direct release
  lookup when the MBID turns out to be a release from the text-search
  fallback path.
- Canonical Album.id stays the release-group MBID so a re-fetch with
  the same URL hits the same code path idempotently.

3 new tests (now 33 total):
- End-to-end release-group → release resolution with mocked client
- Fallback to direct release lookup when rg lookup misses
- Representative-release picker ranks correctly

Verified against live API with the exact MBID that 404'd for the user
(b88655ba... for DAMN. by Kendrick Lamar): now returns in 1.2s with
the full 14-track listing (BLOOD., DNA., YAH., ELEMENT., FEEL., ...).
2026-04-24 08:48:02 -07:00
Broque Thomas
ddbcdfe73a MusicBrainz: Filter live/compilation bootlegs + chronological sort
Three related fixes to make album/track results look like a real
artist discography instead of a firehose of fan-compiled bootlegs.

1. Drop 'compilation' from the release-group browse primary-type filter.
   MB's OR filter (`type=album|ep|single|compilation`) silently breaks
   when 'compilation' is included — Metallica drops from 1076 matches
   to 82 because `compilation` is a SECONDARY type on MB, not a primary
   type. The invalid value corrupts the filter for all types, not just
   itself. Now we request `type=album|ep|single` which returns the full
   1076; actual compilations (primary=Album + secondary=[Compilation])
   are filtered out by the studio-preference logic below.

2. Filter release-groups with non-studio secondary-types
   (Live/Compilation/Soundtrack/Remix/Demo/Mixtape/Interview/Audiobook/
   Audio drama). For Metallica, the first 100 browse results are 12
   studio albums + 83 live bootlegs + 5 compilations — without this
   filter the Albums section was dominated by 2019-2021 broadcast
   recordings. Falls back to the unfiltered list if filtering leaves
   the result set empty (covers live-only niche artists).

3. Sort chronologically ASC by first-release-date. Wikipedia-style
   discography ordering — debut album on top, then chronological.
   Previous DESC sort put the most recent release on top which, for
   prolific artists, meant 2020s material before their classics.

Track side of the same fix:

- Re-orders each recording's `releases` array to put studio releases
  first before `_recording_to_track` picks up the first release for
  album context. Without this, MB's arbitrary release order often
  buried the canonical studio album under random live bootlegs.
- Filters out recordings that only exist on live/compilation release-
  groups (keeps the ones with at least one studio release). Falls
  back to the full set if the artist has no studio recordings at all.
- Sorts recordings by earliest studio-release year ASC so classic
  tracks surface first.

Smoke test against live MB API confirmed:
- Artists: [Metallica score=100]
- Albums: Kill 'Em All (1983) → Ride the Lightning → Master of Puppets
  → ...And Justice for All → Metallica (Black Album) → Load → Reload
  → St. Anger → Death Magnetic → Lulu (2011)
- Tracks: real Metallica recordings (Killing Time, Nothing Else
  Matters, Creeping Death, etc.) — a few remastered demos still leak
  in where MB metadata quality is thin, but the bulk is correct.
- Total latency: 3.5 seconds.

4 new tests covering the studio filter, live-only fallback, preferred
release ordering, and live-only recording exclusion.

Credit: kettui flagged the poor MB results during PR #371 review.
2026-04-24 08:32:05 -07:00
Broque Thomas
8523724b03 MusicBrainz: Switch track lookup from browse to arid: search
The previous commit's `browse_artist_recordings` call passed
`inc=releases+artist-credits` — but MusicBrainz's recording browse
endpoint rejects `inc=releases` with HTTP 400. The adapter's error
handler returned an empty list, so the Tracks section stayed empty
even though the fix was supposed to populate it.

Browse without release info is useless for our search UI (tracks
would render with no album), so swap to the fielded Lucene search
`arid:<mbid>` on the `/recording` endpoint. That's the canonical MB
pattern for "find recordings by this artist WITH release context":
- arid: search accepts the artist MBID and returns recordings with
  `releases` (release-group, date, media) embedded in each result.
- One API call per lookup, same as browse would have been.

Renamed the method to `search_recordings_by_artist_mbid` so the name
matches its behaviour — it's a search, not a browse. Adapter updated
to call the new name; tests updated to match.

Verified against the live API: Metallica's MBID returns 5 recordings
in ~1.8 seconds (vs the previous 400 error).
2026-04-24 08:25:09 -07:00
Broque Thomas
394ac73877 MusicBrainz: Tests for new search behavior + WHATS_NEW entry
26 new unit tests in tests/test_musicbrainz_search.py covering:

- Cover Art URL construction (release + release-group scope, empty MBID,
  unknown scope fallback)
- Structured query splitting (hyphen, en-dash, em-dash, bare name, no
  false-positive splits on hyphens-inside-words)
- Artist search: score filtering, strict=False call contract, exception
  handling, genre extraction from MB tags, mbid/name validation
- Top-artist resolver: memoization by normalized query, sub-threshold
  returns None, negative-result caching, empty-query short-circuit
- Album search routing: bare query → browse path, structured query →
  text path, no-artist-match falls back to text, text path score filter
- Track search routing: browse path, dedupe-by-title across
  live/compilation variants, structured query → text path, text path
  score filter

All mock the underlying MusicBrainzClient — no network calls.

Also adds a WHATS_NEW entry under 2.40 explaining the three user-visible
changes: Artists section now populates, album/track results match the
searched artist instead of random title collisions, and search completes
in ~3 seconds instead of 30+.
2026-04-24 08:18:47 -07:00