soulsync/core/library
BoulderBadgeDad e6d86dea26 Add "Import IDs from File Tags" backfill — gap-fill provider IDs from embedded tags
Files SoulSync (or MusicBrainz Picard) already tagged carry Spotify /
iTunes / MusicBrainz / Deezer / Tidal / AudioDB / Genius / Last.fm IDs in
their metadata. Enrichment workers gate their queues on
{provider}_match_status IS NULL, so reading those IDs back and gap-filling
the {provider}_id + match_status='matched' columns lets the workers skip
the API lookup entirely — big API savings on an already-tagged library.

New manual job in Tools -> Database & Scanning ("Import IDs from File
Tags"): scans every library file, reads embedded IDs, fills any that are
missing in the DB. Background job + progress card, mirroring the
write-tags-batch pattern.

core/library/embedded_id_reconcile.py (pure + tested):
- plan_reconcile(): gap-fill plan for a track + its album + artist. Only
  empty id columns are planned; a disagreeing embedded id is a conflict,
  never applied.
- apply_reconcile_plan(): one guarded UPDATE per id column —
  WHERE id=? AND (col IS NULL OR col=''). The guard makes the fill atomic:
  if an enrichment worker matched the same entity between our read and
  this write, the UPDATE affects 0 rows instead of clobbering it. Columns
  are introspected so a schema missing a provider's columns is skipped.
- reconcile_track_row(): per-track orchestration (id extraction, plan ->
  apply, keeping the in-memory parent maps fresh for sibling tracks).

Job hardening: paged track scan (bounded memory), per-page commits (don't
starve concurrent workers), per-file try/finally (one bad file can't abort
the run), counters from real rowcount.

Scope: 19 column-fills across 8 providers. MB *recording* (track) id is
left out (UFID frame the reader doesn't surface; Vorbis key ambiguous) —
MB album+artist are covered. Amazon/ASIN deliberately excluded (ASIN is a
different namespace than the worker's amazon_id). All target columns
verified against the live schema.

Purely additive: new module, two new endpoints, one new Tools card —
no existing behavior changed. 20 unit tests (incl. the concurrency guard).
Full suite clean (only pre-existing soundcloud /app env failures remain).
2026-06-05 17:52:29 -07:00
..
__init__.py Lift _execute_retag to core/library/retag.py 2026-04-29 09:03:42 -07:00
artist_image.py Write artist.jpg to artist folder so Navidrome shows real photos 2026-05-13 11:48:09 -07:00
duplicate_cleaner.py Surface silent exceptions across remaining modules — ~70 sites 2026-05-07 10:28:58 -07:00
duplicate_keep.py Fix: duplicate detector kept lossy over lossless (rank format first) 2026-06-01 12:49:34 -07:00
embedded_id_reconcile.py Add "Import IDs from File Tags" backfill — gap-fill provider IDs from embedded tags 2026-06-05 17:52:29 -07:00
file_tags.py Add per-download Audit Trail modal to Library History 2026-05-13 09:50:24 -07:00
manual_library_match.py Manual library match: accept non-numeric library track ids (#754) 2026-05-31 09:11:46 -07:00
missing_track_import.py Test missing-track existing file imports 2026-05-17 14:18:17 -07:00
path_resolver.py Album Completeness: surface diagnostic when resolver can't find album folder 2026-05-12 14:04:15 -07:00
redownload.py Lift shared download dataclasses + boot via singleton factory 2026-05-05 09:08:39 -07:00
reorganize_tag_source.py Reorganize: optional embedded-tag mode (closes #592) 2026-05-15 07:56:18 -07:00
retag_planner.py Library re-tag (1/3): pure planner — match source tracklist + per-field tag diff 2026-06-04 08:50:40 -07:00
service_search.py fix(musicbrainz): user-facing search recall + album-detail 404 2026-05-19 15:38:24 -07:00
track_identity.py Final silent-exception sweep + ruff S110 lint guardrail — ~45 sites 2026-05-07 11:16:06 -07:00