return (org_str.strip(),)
-def split_artists(org_artists: str | tuple[str, ...]) -> tuple[str, ...]:
+def split_artists(
+ org_artists: str | tuple[str, ...], allow_ampersand: bool = False
+) -> tuple[str, ...]:
"""Parse all artists from a string."""
final_artists = set()
# when not using the multi artist tag, the artist string may contain
# multiple artists in freeform, even featuring artists may be included in this
# string. Try to parse the featuring artists and separate them.
- splitters = ("featuring", " feat. ", " feat ", "feat.")
+ splitters = ("featuring", " feat. ", " feat ", "feat.", " & ")
+ if allow_ampersand:
+ splitters = (*splitters, " & ")
for item in split_items(org_artists):
for splitter in splitters:
for subitem in item.split(splitter):
if tag := self.tags.get("artist"):
if TAG_SPLITTER in tag:
return split_items(tag)
+ if len(self.musicbrainz_artistids) > 1:
+ # special case: artist noted as 2 artists with ampersand
+ # but with 2 mb ids so they should be treated as 2 artists
+ return split_artists(tag, allow_ampersand=True)
return split_artists(tag)
# fallback to parsing from filename
title = self.filename.rsplit(os.sep, 1)[-1].split(".")[0]
if tag := self.tags.get("albumartist"):
if TAG_SPLITTER in tag:
return split_items(tag)
+ if len(self.musicbrainz_albumartistids) > 1:
+ # special case: album artist noted as 2 artists with ampersand
+ # but with 2 mb ids so they should be treated as 2 artists
+ # example: John Travolta & Olivia Newton John on the Grease album
+ return split_artists(tag, allow_ampersand=True)
return split_artists(tag)
return ()
except ResourceTemporarilyUnavailable as e:
if e.backoff_time:
backoff_time = e.backoff_time
- LOGGER.info(f"Attempt {attempt + 1}/{self.retry_attempts} failed: {e}")
+ level = logging.DEBUG if attempt > 1 else logging.INFO
+ LOGGER.log(level, f"Attempt {attempt + 1}/{self.retry_attempts} failed: {e}")
if attempt < self.retry_attempts - 1:
- LOGGER.info(f"Retrying in {backoff_time} seconds...")
+ LOGGER.log(level, f"Retrying in {backoff_time} seconds...")
await asyncio.sleep(backoff_time)
backoff_time *= 2
else: # noqa: PLW0120
import datetime
import hashlib
import time
+from contextlib import suppress
from typing import TYPE_CHECKING
from music_assistant.common.helpers.json import json_loads
]
if "label" in album_obj:
album.metadata.label = album_obj["label"]["name"]
- if (released_at := album_obj.get("released_at")) and released_at != 0:
- album.year = datetime.datetime.fromtimestamp(album_obj["released_at"]).year
+ if released_at := album_obj.get("released_at"):
+ with suppress(ValueError):
+ album.year = datetime.datetime.fromtimestamp(released_at).year
if album_obj.get("copyright"):
album.metadata.copyright = album_obj["copyright"]
if album_obj.get("description"):