Fix Deezer and iTunes normalization edge cases

- derive Deezer track album types from album metadata instead of the raw track type
- inject Deezer artist data into artist-album payloads
- keep the iTunes explicit-duplicate guard from swapping in broken releases
This commit is contained in:
Antti Kettunen 2026-05-05 08:04:50 +03:00
parent c5c085fb50
commit bf0bfe5976
No known key found for this signature in database
GPG key ID: C6B2A3D250359BD7
3 changed files with 40 additions and 3 deletions

View file

@ -230,10 +230,12 @@ def normalize_deezer_track(raw: dict[str, Any]) -> MetadataTrack:
contributors = raw.get("contributors") or [] contributors = raw.get("contributors") or []
artists = _normalize_artists(contributors if len(_as_list(contributors)) > 1 else [artist] if artist else []) artists = _normalize_artists(contributors if len(_as_list(contributors)) > 1 else [artist] if artist else [])
image_url = _first_non_empty(album.get("cover_xl"), album.get("cover_big"), album.get("cover_medium")) image_url = _first_non_empty(album.get("cover_xl"), album.get("cover_big"), album.get("cover_medium"))
album_type = raw.get("type") or album.get("type") album_type = str(album.get("type") or "").lower()
if not album_type: if not album_type or album_type == "track":
nb_tracks = int(album.get("nb_tracks", 0) or 0) nb_tracks = int(album.get("nb_tracks", 0) or 0)
album_type = _infer_album_type_from_track_count(nb_tracks) album_type = _infer_album_type_from_track_count(nb_tracks)
elif album_type == "compile":
album_type = "compilation"
return MetadataTrack( return MetadataTrack(
id=str(raw.get("id", "")), id=str(raw.get("id", "")),
name=str(raw.get("title", "")), name=str(raw.get("title", "")),

View file

@ -86,12 +86,30 @@ class DeezerMetadataAdapter(BaseMetadataAdapter):
albums: list[dict[str, Any]] = [] albums: list[dict[str, Any]] = []
offset = 0 offset = 0
page_size = 100 page_size = 100
artist_data = self.get_artist_raw(artist_id) or {}
artist_name = str(artist_data.get("name", "") or "").strip()
artist_stub: dict[str, Any] = {"id": artist_data.get("id") or artist_id}
if artist_name:
artist_stub["name"] = artist_name
while offset < limit: while offset < limit:
fetch_limit = min(page_size, limit - offset) fetch_limit = min(page_size, limit - offset)
data = self._request_api(f"artist/{artist_id}/albums", {"limit": fetch_limit, "index": offset}) data = self._request_api(f"artist/{artist_id}/albums", {"limit": fetch_limit, "index": offset})
if not data or not data.get("data"): if not data or not data.get("data"):
break break
albums.extend(list(data.get("data") or [])) for item in list(data.get("data") or []):
if not isinstance(item, dict):
continue
enriched = dict(item)
album_artist = enriched.get("artist")
if isinstance(album_artist, dict):
merged_artist = dict(artist_stub)
merged_artist.update(album_artist)
enriched["artist"] = merged_artist
else:
enriched["artist"] = dict(artist_stub)
if artist_name and not enriched.get("artist_name"):
enriched["artist_name"] = artist_name
albums.append(enriched)
if len(data.get("data") or []) < fetch_limit: if len(data.get("data") or []) < fetch_limit:
break break
offset += len(data.get("data") or []) offset += len(data.get("data") or [])

View file

@ -157,6 +157,7 @@ class ITunesMetadataAdapter(BaseMetadataAdapter):
return [] return []
seen: dict[str, dict[str, Any]] = {} seen: dict[str, dict[str, Any]] = {}
track_count_cache: dict[str, int] = {}
def _normalize_album_name(name: str) -> str: def _normalize_album_name(name: str) -> str:
normalized = (name or "").lower().strip() normalized = (name or "").lower().strip()
@ -170,12 +171,28 @@ class ITunesMetadataAdapter(BaseMetadataAdapter):
normalized = re.sub(r"\s+", " ", normalized).strip() normalized = re.sub(r"\s+", " ", normalized).strip()
return normalized return normalized
def _album_has_tracks(collection_id: str) -> bool:
if not collection_id:
return False
if collection_id not in track_count_cache:
items = self._lookup_raw(id=collection_id, entity="song", limit=200)
track_count_cache[collection_id] = sum(
1
for item in items
if item.get("wrapperType") == "track" and item.get("kind") == "song"
)
return track_count_cache[collection_id] > 0
for album_data in results: for album_data in results:
if album_data.get("wrapperType") != "collection": if album_data.get("wrapperType") != "collection":
continue continue
normalized_name = _normalize_album_name(str(album_data.get("collectionName", "") or "")) normalized_name = _normalize_album_name(str(album_data.get("collectionName", "") or ""))
collection_id = str(album_data.get("collectionId", "") or "")
current = seen.get(normalized_name) current = seen.get(normalized_name)
is_explicit = album_data.get("collectionExplicitness") == "explicit" is_explicit = album_data.get("collectionExplicitness") == "explicit"
has_tracks = not is_explicit or _album_has_tracks(collection_id)
if not has_tracks:
continue
if current is None: if current is None:
seen[normalized_name] = {"data": album_data, "is_explicit": is_explicit} seen[normalized_name] = {"data": album_data, "is_explicit": is_explicit}
continue continue