From f9f47f978e8f68a39de1f8d5d466a750d30473d9 Mon Sep 17 00:00:00 2001 From: elmerohueso Date: Fri, 1 May 2026 12:56:44 -0600 Subject: [PATCH] fix post-download tagging, and enable tagging for hifi --- core/hifi_client.py | 8 +++++ core/imports/context.py | 4 +++ core/metadata/enrichment.py | 2 ++ core/metadata/source.py | 44 ++++++++++++++++++++++++++- tests/metadata/test_runtime_bundle.py | 2 ++ web_server.py | 39 ++++++++++++++++++++++-- 6 files changed, 95 insertions(+), 4 deletions(-) diff --git a/core/hifi_client.py b/core/hifi_client.py index 7b3a8823..df676abe 100644 --- a/core/hifi_client.py +++ b/core/hifi_client.py @@ -282,24 +282,30 @@ class HiFiClient: def _parse_track(self, item: dict) -> Dict: artist_name = 'Unknown Artist' + artist_id = None artists_raw = item.get('artists', item.get('artist')) if isinstance(artists_raw, list): names = [] for a in artists_raw: if isinstance(a, dict): names.append(a.get('name', '')) + if artist_id is None: + artist_id = a.get('id') elif isinstance(a, str): names.append(a) artist_name = ', '.join(n for n in names if n) or 'Unknown Artist' elif isinstance(artists_raw, dict): artist_name = artists_raw.get('name', 'Unknown Artist') + artist_id = artists_raw.get('id') elif isinstance(artists_raw, str): artist_name = artists_raw album_raw = item.get('album', {}) album_name = '' + album_id = None if isinstance(album_raw, dict): album_name = album_raw.get('title', album_raw.get('name', '')) + album_id = album_raw.get('id') elif isinstance(album_raw, str): album_name = album_raw @@ -308,6 +314,8 @@ class HiFiClient: return { 'id': item.get('id'), + 'artist_id': artist_id, + 'album_id': album_id, 'title': item.get('title', item.get('name', 'Unknown')), 'artist': artist_name, 'album': album_name, diff --git a/core/imports/context.py b/core/imports/context.py index 4ed44b56..a3e4a4c3 100644 --- a/core/imports/context.py +++ b/core/imports/context.py @@ -257,6 +257,8 @@ def get_source_tag_names(source: str) -> Dict[str, Optional[str]]: return {"track": None, "artist": None, "album": None} if source_name == "discogs": return {"track": None, "artist": None, "album": None} + if source_name == "hifi": + return {"track": "HIFI_TRACK_ID", "artist": "HIFI_ARTIST_ID", "album": None} return {"track": None, "artist": None, "album": None} @@ -272,6 +274,8 @@ def get_library_source_id_columns(source: str) -> Dict[str, Optional[str]]: return {"artist": "soul_id", "album": "soul_id", "track": "soul_id", "track_album": "album_soul_id"} if source_name == "discogs": return {"artist": "discogs_id", "album": "discogs_id", "track": None} + if source_name == "hifi": + return {"artist": "hifi_artist_id", "album": None, "track": "hifi_track_id"} return {} diff --git a/core/metadata/enrichment.py b/core/metadata/enrichment.py index 82f462d6..6317cedf 100644 --- a/core/metadata/enrichment.py +++ b/core/metadata/enrichment.py @@ -37,6 +37,7 @@ def build_metadata_enrichment_runtime( deezer_worker: Any | None = None, audiodb_worker: Any | None = None, tidal_client: Any | None = None, + hifi_client: Any | None = None, qobuz_enrichment_worker: Any | None = None, lastfm_worker: Any | None = None, genius_worker: Any | None = None, @@ -49,6 +50,7 @@ def build_metadata_enrichment_runtime( deezer_worker=deezer_worker, audiodb_worker=audiodb_worker, tidal_client=tidal_client, + hifi_client=hifi_client, qobuz_enrichment_worker=qobuz_enrichment_worker, lastfm_worker=lastfm_worker, genius_worker=genius_worker, diff --git a/core/metadata/source.py b/core/metadata/source.py index 478eeb02..9c6cc261 100644 --- a/core/metadata/source.py +++ b/core/metadata/source.py @@ -124,12 +124,14 @@ SOURCE_TAG_CONFIG = { "AUDIODB_TRACK_ID": "audiodb.tags.track_id", "TIDAL_TRACK_ID": "tidal.tags.track_id", "TIDAL_ARTIST_ID": "tidal.tags.artist_id", + "HIFI_TRACK_ID": "hifi.tags.track_id", + "HIFI_ARTIST_ID": "hifi.tags.artist_id", "QOBUZ_TRACK_ID": "qobuz.tags.track_id", "QOBUZ_ARTIST_ID": "qobuz.tags.artist_id", "GENIUS_TRACK_ID": "genius.tags.track_id", } -DEFAULT_SOURCE_ORDER = ["musicbrainz", "deezer", "audiodb", "tidal", "qobuz", "lastfm", "genius"] +DEFAULT_SOURCE_ORDER = ["musicbrainz", "deezer", "audiodb", "tidal", "hifi", "qobuz", "lastfm", "genius"] ID3_TAG_MAP = { "MUSICBRAINZ_RECORDING_ID": ("UFID", "http://musicbrainz.org"), @@ -465,6 +467,41 @@ def _process_tidal_source(pp: dict, metadata: dict, cfg, runtime, track_title: s pp["release_year"] = td_release[:4] +def _process_hifi_source(pp: dict, metadata: dict, cfg, runtime, track_title: str, artist_name: str) -> None: + if cfg.get("hifi.embed_tags", True) is False: + return + if not track_title or not artist_name: + return + + hifi_client = getattr(runtime, "hifi_client", None) + if not hifi_client: + return + hifi_results = _call_source_lookup("HiFi track", hifi_client.search_tracks, track_title, artist_name) + if hifi_results and len(hifi_results) > 0: + hifi_track = hifi_results[0] + if _names_match(hifi_track.get("title", ""), track_title): + hifi_track_id = hifi_track.get("id") + if hifi_track_id: + pp["id_tags"]["HIFI_TRACK_ID"] = str(hifi_track_id) + hifi_artist_id = hifi_track.get("artist_id") + if hifi_artist_id: + pp["id_tags"]["HIFI_ARTIST_ID"] = str(hifi_artist_id) + if hifi_track_id: + hifi_details = _call_source_lookup("HiFi track details", hifi_client.get_track_info, hifi_track_id) + if hifi_details: + hifi_isrc = hifi_details.get("isrc") + if hifi_isrc: + pp["hifi_isrc"] = hifi_isrc + if not pp["release_year"]: + hifi_album_id = hifi_track.get("album_id") + if hifi_album_id: + hifi_album = _call_source_lookup("HiFi album", hifi_client.get_album, hifi_album_id) + if hifi_album: + hifi_release = str(hifi_album.get("release_date", "") or "") + if len(hifi_release) >= 4 and hifi_release[:4].isdigit(): + pp["release_year"] = hifi_release[:4] + + def _process_qobuz_source(pp: dict, metadata: dict, cfg, runtime, track_title: str, artist_name: str) -> None: if cfg.get("qobuz.embed_tags", True) is False: return @@ -572,6 +609,8 @@ def _process_source_enrichment(source_name: str, pp: dict, metadata: dict, cfg, _process_audiodb_source(pp, metadata, cfg, runtime, track_title, artist_name) elif source_name == "tidal": _process_tidal_source(pp, metadata, cfg, runtime, track_title, artist_name) + elif source_name == "hifi": + _process_hifi_source(pp, metadata, cfg, runtime, track_title, artist_name) elif source_name == "qobuz": _process_qobuz_source(pp, metadata, cfg, runtime, track_title, artist_name) elif source_name == "lastfm": @@ -699,6 +738,8 @@ def _write_embedded_metadata(audio_file, metadata: dict, pp: dict, cfg, symbols) isrc_candidates.append(("Deezer", pp["deezer_isrc"])) if pp["tidal_isrc"] and _tag_enabled(cfg, "tidal.tags.isrc"): isrc_candidates.append(("Tidal", pp["tidal_isrc"])) + if pp["hifi_isrc"] and _tag_enabled(cfg, "hifi.tags.isrc"): + isrc_candidates.append(("HiFi", pp["hifi_isrc"])) if pp["qobuz_isrc"] and _tag_enabled(cfg, "qobuz.tags.isrc"): isrc_candidates.append(("Qobuz", pp["qobuz_isrc"])) if isrc_candidates: @@ -968,6 +1009,7 @@ def embed_source_ids(audio_file, metadata: dict, context: dict = None, runtime=N "audiodb_genre": None, "tidal_isrc": None, "tidal_copyright": None, + "hifi_isrc": None, "qobuz_isrc": None, "qobuz_copyright": None, "qobuz_label": None, diff --git a/tests/metadata/test_runtime_bundle.py b/tests/metadata/test_runtime_bundle.py index 415944af..8b6bed3e 100644 --- a/tests/metadata/test_runtime_bundle.py +++ b/tests/metadata/test_runtime_bundle.py @@ -23,6 +23,7 @@ def test_build_import_pipeline_runtime_exposes_expected_contract(): "deezer_worker", "audiodb_worker", "tidal_client", + "hifi_client", "qobuz_enrichment_worker", "lastfm_worker", "genius_worker", @@ -38,6 +39,7 @@ def test_build_metadata_enrichment_runtime_exposes_expected_contract(): "deezer_worker": object(), "audiodb_worker": object(), "tidal_client": object(), + "hifi_client": object(), "qobuz_enrichment_worker": object(), "lastfm_worker": object(), "genius_worker": object(), diff --git a/web_server.py b/web_server.py index 60314e6d..2b4be31d 100644 --- a/web_server.py +++ b/web_server.py @@ -14313,7 +14313,18 @@ def _enhance_file_metadata(file_path: str, context: dict, artist: dict, album_in context, artist, album_info, - runtime=metadata_runtime or _build_metadata_enrichment_runtime(), + runtime=metadata_runtime or _build_metadata_enrichment_runtime( + mb_worker=mb_worker, + deezer_worker=deezer_worker, + audiodb_worker=audiodb_worker, + tidal_client=tidal_client, + qobuz_enrichment_worker=qobuz_enrichment_worker, + lastfm_worker=lastfm_worker, + genius_worker=genius_worker, + spotify_enrichment_worker=spotify_enrichment_worker, + itunes_enrichment_worker=itunes_enrichment_worker, + hifi_client=soulseek_client.hifi if soulseek_client else None, + ), ) @@ -14407,7 +14418,18 @@ def _post_process_matched_download_with_verification(context_key, context, file_ task_id, batch_id, _build_import_pipeline_runtime(), - _build_metadata_enrichment_runtime(), + _build_metadata_enrichment_runtime( + mb_worker=mb_worker, + deezer_worker=deezer_worker, + audiodb_worker=audiodb_worker, + tidal_client=tidal_client, + qobuz_enrichment_worker=qobuz_enrichment_worker, + lastfm_worker=lastfm_worker, + genius_worker=genius_worker, + spotify_enrichment_worker=spotify_enrichment_worker, + itunes_enrichment_worker=itunes_enrichment_worker, + hifi_client=soulseek_client.hifi if soulseek_client else None, + ), ) @@ -14520,7 +14542,18 @@ def _post_process_matched_download(context_key, context, file_path): context, file_path, _build_import_pipeline_runtime(), - metadata_runtime=_build_metadata_enrichment_runtime(), + metadata_runtime=_build_metadata_enrichment_runtime( + mb_worker=mb_worker, + deezer_worker=deezer_worker, + audiodb_worker=audiodb_worker, + tidal_client=tidal_client, + qobuz_enrichment_worker=qobuz_enrichment_worker, + lastfm_worker=lastfm_worker, + genius_worker=genius_worker, + spotify_enrichment_worker=spotify_enrichment_worker, + itunes_enrichment_worker=itunes_enrichment_worker, + hifi_client=soulseek_client.hifi if soulseek_client else None, + ), ) # Track stale transfer keys (completed in slskd but no context — e.g., from before app restart)