Commit graph

5 commits

Author SHA1 Message Date
Broque Thomas
4ca3f70bf3 Show MusicBrainz release variants in import
Expand matched MusicBrainz release groups into concrete releases for specific album searches so import users can choose the correct edition by track count, format, country, and disambiguation. Preserve distinct MusicBrainz release IDs instead of deduping same-title variants, carry release metadata through import matching, and surface those details on album result cards. Add coverage for variant preservation and release-group expansion.
2026-05-24 09:33:19 -07:00
Broque Thomas
5eae24b8bb Fix $albumtype defaulting to album for non-Spotify sources
- legacy duck-typed builder only checked the `album_type` key; deezer
  uses `record_type`, tidal uses `type` (uppercase), some flattened
  musicbrainz shapes use `primary-type` — all defaulted to album, so
  EPs and singles ended up filed under Album/ in user templates that
  reference $albumtype
- widen lookup to album_type / record_type / type / primary-type and
  route through new pure `_normalize_album_type` helper that
  case-folds + validates against the canonical token set
  (album / single / ep / compilation), unknown → album
- typed-converter path (spotify / deezer / itunes / discogs / mb /
  hydrabase / qobuz) unchanged — those were already correct

Discord report (CAL).
2026-05-12 21:09:16 -07:00
Broque Thomas
3246490800 Auto-import: MBID/ISRC fast paths + duration sanity gate
Brings the auto-import matcher to picard / beets / roon parity by
reaching for the existing AcoustID-grade infrastructure (typed Album
foundation, integrity check thresholds) and layering id-based exact
matches on top of the fuzzy scorer. Picard-tagged libraries now land
every track with full confidence on the first pass.

Three layered phases in `core/imports/album_matching.match_files_to_tracks`:

1. **MBID exact match** — file has `musicbrainz_trackid` tag, source
   returns the same id → instant pair, full confidence, no fuzzy
   scoring. Picard's primary identifier; per-recording.
2. **ISRC exact match** — file has `isrc` tag, source returns the same
   id → same fast-path, slightly lower priority than mbid (isrc can
   be shared across remasters). Both ids normalised before compare
   (uppercase + strip dashes/spaces for isrc, lowercase for mbid).
3. **Duration sanity gate** — files in the fuzzy phase whose audio
   length differs from the candidate track's duration by more than
   `DURATION_TOLERANCE_MS` (3s, matching the post-download integrity
   check) are rejected before scoring runs. Defends against the
   cross-disc / cross-release / wrong-edit problem the integrity
   check used to catch only AFTER the file had already been moved +
   tagged + db-inserted.

Tag reader (`_read_file_tags`) extended:

- Reads `isrc` (uppercased, strip / / spaces normalisation deferred
  to matcher)
- Reads `musicbrainz_trackid` as `mbid` (lowercased)
- Reads `audio.info.length` and converts to `duration_ms` to match
  the metadata-source convention

Metadata-source layer (`_build_album_track_entry`) extended:

- Propagates `isrc` from top-level OR `external_ids.isrc` (spotify
  shape — would otherwise be stripped before reaching the matcher)
- Propagates `musicbrainz_id` from top-level OR `external_ids.mbid`
  / `external_ids.musicbrainz`
- Without this layer, fast paths would silently never fire in
  production even though unit tests pass — pinned by
  `test_album_track_entry_propagates_isrc_and_mbid_from_source`

18 new tests in `tests/imports/test_album_matching_exact_id.py`:
- Direct: `find_exact_id_matches` with mbid, isrc, isrc normalisation,
  mbid > isrc priority, spotify-shape `external_ids.isrc`, no-id
  empty result, file-used-at-most-once
- Direct: `duration_sanity_ok` within / outside tolerance, missing
  durations defer
- End-to-end via `match_files_to_tracks`: mbid match short-circuits
  fuzzy scoring, id-matched files excluded from fuzzy phase, duration
  gate rejects wrong-disc collisions in fuzzy phase, normal matches
  pass through the gate, missing durations fall through, deezer
  seconds-vs-ms conversion, full picard-tagged 10-track album via
  mbid only
- Production-shape: `_build_album_track_entry` propagates isrc + mbid
  from spotify-shape (`external_ids.isrc`) AND itunes-shape (top-
  level `isrc`)

Verification:
- 35 album-matching tests pass total (17 helper + 18 fast-path)
- 23 multi-disc tests still pass after the extension (additive)
- Full suite: 2311 passed (+18 new), 1 pre-existing flaky timing test
  failure (`test_watchdog_warns_about_stuck_workers` — passes in
  isolation, fails only in full-suite runs, unrelated to this PR)
- Ruff clean

For users:
- Picard / Beets / Mp3Tag-tagged libraries (anyone who's organised
  their music) get instant perfect-confidence matches every time.
- Soulseek-tagged downloads (which usually carry isrc when sourced
  via metadata-aware soulseekers) get the fast path too.
- Naively-named files with no useful tags fall through to the
  improved fuzzy + duration-gated path — same correctness as before
  for the common case, much harder for the matcher to confidently
  pair the wrong file.
- One step closer to standalone-DB feature parity with plex /
  jellyfin / navidrome scanners. Acoustid fingerprint fallback
  (for files with NO useful tags AND no MBID/ISRC) is the next
  followup PR.
2026-05-09 09:57:33 -07:00
Broque Thomas
967c7f7c0a Migrate album-info builders to typed Album path
Steps 2+3 of typed metadata migration. Two album-info builders now
route through Album.from_<source>_dict() when caller passes a
known source:
- _build_album_info (album-tracks lookups)
- _build_single_import_context_payload (single-track import context)

Legacy duck-typing stays as fallback for unknown source, non-dict
input, or converter errors. Pure additive — existing callers
without source kwarg unchanged.
2026-05-03 22:53:12 -07:00
Antti Kettunen
50e1ae3a3f
Move metadata helpers into package modules
- split metadata lookup logic into core/metadata/*
- keep core/metadata_service.py as the legacy barrel
- update tests and artist-detail code to patch concrete modules
2026-04-29 11:28:42 +03:00