Commit Graph

5 Commits

Author SHA1 Message Date
bvandeusen 4fca0e66cb fix(listenbrainz): similarity hits Labs API, not the 404'ing main API
Root cause of zero LB recommendations (every similar-recordings AND
similar-artists call returned HTTP 404, worker logs):

- Wrong host/path: client called
  api.listenbrainz.org/1/explore/similar-{recordings,artists}/{mbid}.
  /explore/... is a WEBSITE route, not an API endpoint — it 308s then
  404s. Similarity datasets live on the separate Labs API.
- Invalid algorithm: the hardcoded
  session_…_session_30_…_limit_100_filter_True_… is not a permitted
  Labs enum member (400s) regardless of host.

Verified against the live Labs API:
  GET labs.api.listenbrainz.org/similar-recordings/json
      ?recording_mbids=<mbid>&algorithm=<algo>
  GET labs.api.listenbrainz.org/similar-artists/json
      ?artist_mbids=<mbid>&algorithm=<algo>
  algorithm=session_based_days_9000_session_300_contribution_5_threshold_15_limit_50_skip_30
  → 200 for both. Response field names (recording_mbid/artist_mbid/
  name/score) already match the existing structs — parsing unchanged.

- Add defaultLabsBaseURL + Client.LabsBaseURL (separate from the main
  BaseURL; scrobble submission still uses api.listenbrainz.org).
- Drop the count/limit query param — result size is encoded in the
  algorithm name (limit_50); caller still applies its own top-K.
- Tests: newTestClient sets LabsBaseURL; the two *_LimitParamSet tests
  become *_MbidParamSet (assert the Labs path + mbid query param).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 15:50:32 -04:00
bvandeusen e2432caa65 fix(library): extract recording MBID → tracks.mbid; unblock LB similarity
tracks.mbid was 100% NULL: the scanner only extracted album + artist
MBIDs, never the recording MBID. The ListenBrainz similarity worker is
gated on tracks.mbid IS NOT NULL, so track_similarity could never
populate — starving For-You/radio's strongest candidate source
(lb_similar) on every library.

- mbids.go: add extractRecordingMBID (mbz.Recording / Picard
  musicbrainz_recordingid). Separate fn so extractMBIDs' signature +
  unit tests stay untouched.
- scanner.go: persist recording MBID via UpsertTrack (heals on the
  file_path conflict, so re-scans backfill for free).
- BackfillTrackMBIDs: one-shot pass mirroring BackfillMBIDs, wired as
  scan Stage 2b (idempotent via SetTrackMbidIfNull, gated by
  BackfillCap, log-only progress).
- migration 0029: tracks_mbid_unique (0002, written when the column
  was always NULL) wrongly assumes one MBID == one track. A recording
  appears on multiple releases, so rows legitimately share a recording
  MBID. Replace with a non-unique partial index. Zero-risk: column is
  100% NULL at migration time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 13:49:38 -04:00
bvandeusen cc12ab7e96 fix(server/m7-382): strip NUL bytes from MBIDs before persist
Follow-up to 947f944. With the dhowden/tag/mbz extractor now correctly
matching ID3v2 TXXX frames, mbid backfill started rejecting every row
with "invalid byte sequence for encoding UTF8: 0x00 (SQLSTATE 22021)".

Cause: dhowden's readTextWithDescrFrame (id3v2frames.go:454) — the path
that decodes TXXX text — does NOT strip the trailing/embedded NUL bytes
that ID3v2.4 uses as frame terminator and multi-value separator. Unlike
readTFrame, which does (line 313). So a TXXX:MusicBrainz Album Id of
"abc-123\x00" comes back to us verbatim, and Postgres refuses to store
NULs in text columns.

cleanMBID splits on NUL and returns the first non-empty segment, which
also handles the multi-value case (collaboration artist IDs are
NUL-separated; Minstrel uses the primary for MBCAA, so first wins).

Tests cover the trailing-NUL, multi-value, and all-NUL paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 21:35:01 -04:00
bvandeusen 947f944fe9 fix(server/m7-382): MBID extraction never matched dhowden/tag's Raw() keys
The hand-rolled extractor in internal/library/mbids.go looked up keys that
dhowden/tag never produces, for every supported audio format:

  Vorbis (FLAC/OGG): dhowden lowercases keys at parse time (vorbis.go:59),
    we looked up MUSICBRAINZ_ALBUMID. Always missed.
  ID3v2 (MP3): dhowden stores TXXX frames under bare TXXX/TXXX_N keys
    with *tag.Comm values; the Picard tag name is on the value's
    Description field, not part of the key. We looked up
    TXXX:MusicBrainz Album Id. Always missed.
  MP4 (M4A): dhowden stores freeform iTunes atoms under their bare
    sub-name. We looked up ----:com.apple.iTunes:MusicBrainz Album Id.
    Always missed.

Net effect: every album in the m7-380 boot backfill reported
processed=N, healed=0, skipped=N. The m7-379 enricher gate then
short-circuited NULL on every album, so MBCAA was never queried.
Symptom: "all albums skipped" during cover enrichment.

The pre-existing unit tests passed because they hand-built Raw() maps
with the intended-but-incorrect keys, never round-tripping through
dhowden's parser.

Replace the hand-rolled extractor with dhowden's own mbz sub-package,
which knows the per-format Raw() conventions and uses lowercase
canonical keys (mbz.Album, mbz.Artist). Update both call sites to pass
the full tag.Metadata instead of just Raw(). Rewrite the test with a
stubMeta implementing tag.Metadata, asserting against the actual
per-format Raw() shape, plus a regression guard that pins "uppercase
Vorbis keys must not match" so the v0 bug can't sneak back.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 21:15:51 -04:00
bvandeusen ab8fff834a feat(server/m7-379): extract MusicBrainz IDs from audio tags during scan
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 19:01:55 -04:00