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
|
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):
|
def embed_source_ids(audio_file, metadata: dict, context: dict = None, runtime=None):
|
||||||
cfg = get_config_manager()
|
cfg = get_config_manager()
|
||||||
symbols = get_mutagen_symbols()
|
symbols = get_mutagen_symbols()
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,26 @@ def _read_current_tags(file_path):
|
||||||
return {}
|
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):
|
def _track_list(result):
|
||||||
"""Normalize a get_album_tracks result into a plain list of track items."""
|
"""Normalize a get_album_tracks result into a plain list of track items."""
|
||||||
if result is None:
|
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
|
# 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).
|
# to apply to it (db_data may be empty — apply embeds art either way).
|
||||||
if plan['changes'] or cover_action:
|
if plan['changes'] or cover_action:
|
||||||
|
db_data = plan['db_data']
|
||||||
|
_add_source_ids(db_data, source, album_source_id, src)
|
||||||
track_plans.append({
|
track_plans.append({
|
||||||
'file_path': lib['file_path'],
|
'file_path': lib['file_path'],
|
||||||
'track_id': lib['id'],
|
'track_id': lib['id'],
|
||||||
'title': lib['title'],
|
'title': lib['title'],
|
||||||
'changes': plan['changes'],
|
'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'])
|
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,
|
year, genre_str, track_num, total_tracks,
|
||||||
disc_num, bpm, artists_list=artists_list)
|
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
|
# Embed cover art if requested
|
||||||
if embed_cover:
|
if embed_cover:
|
||||||
art_ok = False
|
art_ok = False
|
||||||
|
|
|
||||||
|
|
@ -9336,6 +9336,13 @@ def _build_library_tag_db_data(track_data, album_genres=None):
|
||||||
if artists_list:
|
if artists_list:
|
||||||
db_data['artists_list'] = 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
|
return db_data
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue