Tagging: write source IDs too (Write Tags button + library re-tag now complete)
write_tags_to_file wrote the core fields + cover but never the source IDs (Spotify/iTunes/MusicBrainz) the import post-process embeds. Added a focused source.embed_known_source_ids() that writes ALREADY-KNOWN ids (from db_data) via the canonical, Picard-compatible frame writer the import uses (_write_embedded_metadata) — no API re-fetch, frames correct by construction. write_tags_to_file now calls it whenever db_data carries id keys. Fed from both paths: the enhanced-library 'Write Tags' button now carries the track's spotify/itunes/musicbrainz ids, and the Library Re-tag job stamps the matched album/track source ids onto each track. So both now write the full tag set, not a subset.
This commit is contained in:
parent
50b6876d6b
commit
3ea9da1cba
4 changed files with 102 additions and 1 deletions
|
|
@ -1122,6 +1122,60 @@ def extract_source_metadata(context: dict, artist: dict, album_info: dict) -> di
|
|||
return metadata
|
||||
|
||||
|
||||
def embed_known_source_ids(audio_file, metadata: dict) -> list:
|
||||
"""Embed ALREADY-KNOWN source IDs into a file's tags, no API re-fetch.
|
||||
|
||||
For the library re-tag / "Write Tags" paths: ``metadata`` carries flat id
|
||||
keys already stored in the DB (``spotify_track_id``, ``spotify_album_id``,
|
||||
``itunes_track_id``, ``itunes_album_id``, ``musicbrainz_recording_id``,
|
||||
``musicbrainz_release_id``, …). Reuses the SAME canonical, format-specific,
|
||||
Picard-compatible frame writer as the import post-process
|
||||
(``_write_embedded_metadata``) so the frames never diverge — it just skips
|
||||
the heavy multi-source re-search that ``embed_source_ids`` does.
|
||||
|
||||
The caller is responsible for saving the file. Returns the canonical tag
|
||||
names written (for logging/diagnostics).
|
||||
"""
|
||||
cfg = get_config_manager()
|
||||
symbols = get_mutagen_symbols()
|
||||
if not symbols or audio_file is None:
|
||||
return []
|
||||
try:
|
||||
id_tags = _collect_source_ids(metadata, cfg)
|
||||
# MusicBrainz ids aren't in _collect_source_ids (they come from the MB
|
||||
# processor at import time); add them from the DB when present, using
|
||||
# the canonical names the frame map already knows.
|
||||
if cfg.get("musicbrainz.embed_tags", True) is not False:
|
||||
if metadata.get("musicbrainz_recording_id"):
|
||||
id_tags["MUSICBRAINZ_RECORDING_ID"] = metadata["musicbrainz_recording_id"]
|
||||
if metadata.get("musicbrainz_release_id"):
|
||||
id_tags["MUSICBRAINZ_RELEASE_ID"] = metadata["musicbrainz_release_id"]
|
||||
if not id_tags:
|
||||
return []
|
||||
pp = _blank_post_process_state()
|
||||
pp["id_tags"] = id_tags
|
||||
_write_embedded_metadata(audio_file, metadata, pp, cfg, symbols)
|
||||
return list(id_tags.keys())
|
||||
except Exception as exc:
|
||||
logger.debug("embed_known_source_ids failed: %s", exc)
|
||||
return []
|
||||
|
||||
|
||||
def _blank_post_process_state() -> dict:
|
||||
"""A fully-defaulted post-process state so _write_embedded_metadata can run
|
||||
with only id_tags set (every enrichment branch no-ops on the blanks)."""
|
||||
return {
|
||||
"id_tags": {}, "track_title": "", "artist_name": "", "batch_artist_name": None,
|
||||
"metadata": {}, "recording_mbid": None, "artist_mbid": None, "release_mbid": "",
|
||||
"mb_genres": [], "isrc": None, "deezer_bpm": None, "deezer_isrc": None,
|
||||
"tidal_bpm": None, "hifi_bpm": None, "hifi_copyright": None, "audiodb_mood": None,
|
||||
"audiodb_style": None, "audiodb_genre": None, "tidal_isrc": None,
|
||||
"tidal_copyright": None, "hifi_isrc": None, "qobuz_isrc": None,
|
||||
"qobuz_copyright": None, "qobuz_label": None, "lastfm_tags": [],
|
||||
"lastfm_url": None, "genius_url": None, "release_year": None,
|
||||
}
|
||||
|
||||
|
||||
def embed_source_ids(audio_file, metadata: dict, context: dict = None, runtime=None):
|
||||
cfg = get_config_manager()
|
||||
symbols = get_mutagen_symbols()
|
||||
|
|
|
|||
|
|
@ -45,6 +45,26 @@ def _read_current_tags(file_path):
|
|||
return {}
|
||||
|
||||
|
||||
def _add_source_ids(db_data, source, album_source_id, source_track):
|
||||
"""Stamp the album/track source IDs onto the write payload so the canonical
|
||||
writer embeds them too (Spotify / iTunes / MusicBrainz)."""
|
||||
album_key = {'spotify': 'spotify_album_id', 'itunes': 'itunes_album_id',
|
||||
'musicbrainz': 'musicbrainz_release_id'}.get(source)
|
||||
track_key = {'spotify': 'spotify_track_id', 'itunes': 'itunes_track_id',
|
||||
'musicbrainz': 'musicbrainz_recording_id'}.get(source)
|
||||
if album_key and album_source_id:
|
||||
db_data[album_key] = album_source_id
|
||||
if track_key:
|
||||
tid = None
|
||||
for k in ('id', 'track_id', 'source_track_id'):
|
||||
v = source_track.get(k) if isinstance(source_track, dict) else getattr(source_track, k, None)
|
||||
if v:
|
||||
tid = v
|
||||
break
|
||||
if tid:
|
||||
db_data[track_key] = tid
|
||||
|
||||
|
||||
def _track_list(result):
|
||||
"""Normalize a get_album_tracks result into a plain list of track items."""
|
||||
if result is None:
|
||||
|
|
@ -222,12 +242,14 @@ class LibraryRetagJob(RepairJob):
|
|||
# Include a track when its tags change, OR when there's a cover action
|
||||
# to apply to it (db_data may be empty — apply embeds art either way).
|
||||
if plan['changes'] or cover_action:
|
||||
db_data = plan['db_data']
|
||||
_add_source_ids(db_data, source, album_source_id, src)
|
||||
track_plans.append({
|
||||
'file_path': lib['file_path'],
|
||||
'track_id': lib['id'],
|
||||
'title': lib['title'],
|
||||
'changes': plan['changes'],
|
||||
'db_data': plan['db_data'],
|
||||
'db_data': db_data,
|
||||
})
|
||||
|
||||
tag_change_tracks = sum(1 for tp in track_plans if tp['changes'])
|
||||
|
|
|
|||
|
|
@ -294,6 +294,24 @@ def write_tags_to_file(file_path: str, db_data: Dict[str, Any],
|
|||
year, genre_str, track_num, total_tracks,
|
||||
disc_num, bpm, artists_list=artists_list)
|
||||
|
||||
# Embed already-known source IDs (Spotify / iTunes / MusicBrainz) from
|
||||
# db_data, reusing the canonical import-time frame writer — no API
|
||||
# re-fetch. Only fires when db_data carries id keys, so the plain
|
||||
# "write the core tags" callers are unaffected.
|
||||
_src_meta = {k: db_data[k] for k in (
|
||||
'source', 'source_track_id', 'source_album_id', 'source_artist_id',
|
||||
'spotify_track_id', 'spotify_album_id', 'spotify_artist_id',
|
||||
'itunes_track_id', 'itunes_album_id', 'itunes_artist_id',
|
||||
'musicbrainz_recording_id', 'musicbrainz_release_id',
|
||||
) if db_data.get(k)}
|
||||
if _src_meta:
|
||||
try:
|
||||
from core.metadata.source import embed_known_source_ids
|
||||
if embed_known_source_ids(audio, _src_meta):
|
||||
written.append('source_ids')
|
||||
except Exception as e:
|
||||
logger.debug("source-id embed skipped for %s: %s", file_path, e)
|
||||
|
||||
# Embed cover art if requested
|
||||
if embed_cover:
|
||||
art_ok = False
|
||||
|
|
|
|||
|
|
@ -9336,6 +9336,13 @@ def _build_library_tag_db_data(track_data, album_genres=None):
|
|||
if artists_list:
|
||||
db_data['artists_list'] = artists_list
|
||||
|
||||
# Carry the known source IDs through so they get embedded too (the writer
|
||||
# only acts on the ones present). These come from t.* on the track row.
|
||||
for _k in ('spotify_track_id', 'itunes_track_id', 'musicbrainz_recording_id'):
|
||||
_v = track_data.get(_k)
|
||||
if _v:
|
||||
db_data[_k] = _v
|
||||
|
||||
return db_data
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue