mirror of
https://github.com/beetbox/beets.git
synced 2026-01-20 15:14:13 +01:00
Simplify building artist
This commit is contained in:
parent
59e7c59172
commit
b3183a73e0
2 changed files with 110 additions and 141 deletions
|
|
@ -26,9 +26,9 @@ import socket
|
|||
import time
|
||||
import traceback
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from functools import cache
|
||||
from functools import cache, cached_property
|
||||
from string import ascii_lowercase
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, NamedTuple
|
||||
|
||||
import confuse
|
||||
from discogs_client import Client, Master, Release
|
||||
|
|
@ -127,135 +127,111 @@ class TracklistInfo(TypedDict):
|
|||
|
||||
@dataclass
|
||||
class ArtistState:
|
||||
artist: str = ""
|
||||
artists: list[str] = field(default_factory=list)
|
||||
artist_credit: str = ""
|
||||
artists_credit: list[str] = field(default_factory=list)
|
||||
artist_id: str = ""
|
||||
artists_ids: list[str] = field(default_factory=list)
|
||||
class ValidArtist(NamedTuple):
|
||||
id: str
|
||||
name: str
|
||||
credit: str
|
||||
join: str
|
||||
is_feat: bool
|
||||
|
||||
def get_artist(self, property_name: str) -> str:
|
||||
return getattr(self, property_name) + (
|
||||
{",": ", ", "": ""}.get(self.join, f" {self.join} ")
|
||||
)
|
||||
|
||||
raw_artists: list[Artist]
|
||||
use_anv: bool
|
||||
use_credit_anv: bool
|
||||
featured_string: str
|
||||
should_strip_disambiguation: bool
|
||||
|
||||
@property
|
||||
def info(self) -> ArtistInfo:
|
||||
return asdict(self) # type: ignore[return-value]
|
||||
return {k: getattr(self, k) for k in ArtistInfo.__annotations__} # type: ignore[return-value]
|
||||
|
||||
def clone(self) -> ArtistState:
|
||||
return ArtistState(**asdict(self))
|
||||
def strip_disambiguation(self, text: str) -> str:
|
||||
"""Removes discogs specific disambiguations from a string.
|
||||
Turns 'Label Name (5)' to 'Label Name' or 'Artist (1) & Another Artist (2)'
|
||||
to 'Artist & Another Artist'. Does nothing if strip_disambiguation is False."""
|
||||
if self.should_strip_disambiguation:
|
||||
return DISAMBIGUATION_RE.sub("", text)
|
||||
return text
|
||||
|
||||
@cached_property
|
||||
def valid_artists(self) -> list[ValidArtist]:
|
||||
va_name = config["va_name"].as_str()
|
||||
return [
|
||||
self.ValidArtist(
|
||||
str(a["id"]),
|
||||
self.strip_disambiguation(anv if self.use_anv else name),
|
||||
self.strip_disambiguation(anv if self.use_credit_anv else name),
|
||||
a["join"],
|
||||
is_feat,
|
||||
)
|
||||
for a in self.raw_artists
|
||||
if (
|
||||
(name := va_name if a["name"] == "Various" else a["name"])
|
||||
and (anv := a["anv"] or name)
|
||||
and (
|
||||
(is_feat := ("featuring" in a["role"].lower()))
|
||||
or not a["role"]
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
@property
|
||||
def artists_ids(self) -> list[str]:
|
||||
return [a.id for a in self.valid_artists]
|
||||
|
||||
@property
|
||||
def artist_id(self) -> str:
|
||||
return self.artists_ids[0]
|
||||
|
||||
@property
|
||||
def artists(self) -> list[str]:
|
||||
return [a.name for a in self.valid_artists]
|
||||
|
||||
@property
|
||||
def artists_credit(self) -> list[str]:
|
||||
return [a.credit for a in self.valid_artists]
|
||||
|
||||
@property
|
||||
def artist(self) -> str:
|
||||
return self.join_artists("name")
|
||||
|
||||
@property
|
||||
def artist_credit(self) -> str:
|
||||
return self.join_artists("credit")
|
||||
|
||||
def join_artists(self, property_name: str) -> str:
|
||||
non_featured = [a for a in self.valid_artists if not a.is_feat]
|
||||
featured = [a for a in self.valid_artists if a.is_feat]
|
||||
|
||||
artist = "".join(a.get_artist(property_name) for a in non_featured)
|
||||
if featured:
|
||||
if "feat" not in artist:
|
||||
artist += f" {self.featured_string} "
|
||||
|
||||
artist += ", ".join(a.get_artist(property_name) for a in featured)
|
||||
|
||||
return artist
|
||||
|
||||
@classmethod
|
||||
def build(
|
||||
def from_plugin(
|
||||
cls,
|
||||
plugin: DiscogsPlugin,
|
||||
given_artists: list[Artist],
|
||||
given_state: ArtistState | None = None,
|
||||
artists: list[Artist],
|
||||
for_album_artist: bool = False,
|
||||
) -> ArtistState:
|
||||
"""Iterates through a discogs result and builds
|
||||
up the artist fields. Does not contribute to
|
||||
artist_sort as Discogs does not define that.
|
||||
"""
|
||||
state = given_state.clone() if given_state else cls()
|
||||
|
||||
artist = ""
|
||||
artist_anv = ""
|
||||
artists: list[str] = []
|
||||
artists_anv: list[str] = []
|
||||
|
||||
feat_str: str = f" {plugin.config['featured_string'].as_str()} "
|
||||
join = ""
|
||||
featured_flag = False
|
||||
for a in given_artists:
|
||||
name = plugin.strip_disambiguation(a["name"])
|
||||
discogs_id = str(a["id"])
|
||||
anv = a.get("anv", "") or name
|
||||
role = a.get("role", "").lower()
|
||||
if name.lower() == "various":
|
||||
name = config["va_name"].as_str()
|
||||
anv = name
|
||||
if "featuring" in role:
|
||||
if not featured_flag:
|
||||
artist += feat_str
|
||||
artist_anv += feat_str
|
||||
artist += name
|
||||
artist_anv += anv
|
||||
featured_flag = True
|
||||
else:
|
||||
artist = cls.join_artist(artist, name, join)
|
||||
artist_anv = cls.join_artist(artist_anv, anv, join)
|
||||
elif role and "featuring" not in role:
|
||||
continue
|
||||
else:
|
||||
artist = cls.join_artist(artist, name, join)
|
||||
artist_anv = cls.join_artist(artist_anv, anv, join)
|
||||
artists.append(name)
|
||||
artists_anv.append(anv)
|
||||
if not state.artist_id:
|
||||
state.artist_id = discogs_id
|
||||
state.artists_ids.append(discogs_id)
|
||||
join = a.get("join", "")
|
||||
cls._assign_anv(
|
||||
plugin,
|
||||
state,
|
||||
artist,
|
||||
return cls(
|
||||
artists,
|
||||
artist_anv,
|
||||
artists_anv,
|
||||
for_album_artist,
|
||||
plugin.config["anv"][
|
||||
"album_artist" if for_album_artist else "artist"
|
||||
].get(bool),
|
||||
plugin.config["anv"]["artist_credit"].get(bool),
|
||||
plugin.config["featured_string"].as_str(),
|
||||
plugin.config["strip_disambiguation"].get(bool),
|
||||
)
|
||||
return state
|
||||
|
||||
@staticmethod
|
||||
def join_artist(base: str, artist: str, join: str) -> str:
|
||||
# Expand the artist field
|
||||
if not base:
|
||||
base = artist
|
||||
else:
|
||||
if join:
|
||||
join = join.strip()
|
||||
if join in ";,":
|
||||
base += f"{join} "
|
||||
else:
|
||||
base += f" {join} "
|
||||
else:
|
||||
base += ", "
|
||||
base += artist
|
||||
return base
|
||||
|
||||
@staticmethod
|
||||
def _assign_anv(
|
||||
plugin: DiscogsPlugin,
|
||||
state: ArtistState,
|
||||
artist: str,
|
||||
artists: list[str],
|
||||
artist_anv: str,
|
||||
artists_anv: list[str],
|
||||
for_album_artist: bool,
|
||||
) -> None:
|
||||
"""Assign artist and variation fields based on
|
||||
configuration settings.
|
||||
"""
|
||||
use_artist_anv: bool = plugin.config["anv"]["artist"].get(bool)
|
||||
use_artistcredit_anv: bool = plugin.config["anv"]["artist_credit"].get(
|
||||
bool
|
||||
)
|
||||
use_albumartist_anv: bool = plugin.config["anv"]["album_artist"].get(
|
||||
bool
|
||||
)
|
||||
|
||||
if (use_artist_anv and not for_album_artist) or (
|
||||
use_albumartist_anv and for_album_artist
|
||||
):
|
||||
state.artist += artist_anv
|
||||
state.artists += artists_anv
|
||||
else:
|
||||
state.artist += artist
|
||||
state.artists += artists
|
||||
|
||||
if use_artistcredit_anv:
|
||||
state.artist_credit += artist_anv
|
||||
state.artists_credit += artists_anv
|
||||
else:
|
||||
state.artist_credit += artist
|
||||
state.artists_credit += artists
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -554,7 +530,7 @@ class DiscogsPlugin(MetadataSourcePlugin):
|
|||
|
||||
artist_data = [a.data for a in result.artists]
|
||||
# Information for the album artist
|
||||
albumartist = ArtistState.build(
|
||||
albumartist = ArtistState.from_plugin(
|
||||
self, artist_data, for_album_artist=True
|
||||
)
|
||||
|
||||
|
|
@ -565,7 +541,7 @@ class DiscogsPlugin(MetadataSourcePlugin):
|
|||
# information and leave us with skeleton `Artist` objects that will
|
||||
# each make an API call just to get the same data back.
|
||||
tracks = self.get_tracks(
|
||||
result.data["tracklist"], ArtistState.build(self, artist_data)
|
||||
result.data["tracklist"], ArtistState.from_plugin(self, artist_data)
|
||||
)
|
||||
|
||||
# Extract information for the optional AlbumInfo fields, if possible.
|
||||
|
|
@ -851,8 +827,6 @@ class DiscogsPlugin(MetadataSourcePlugin):
|
|||
) -> tuple[TrackInfo, str | None, str | None]:
|
||||
"""Returns a TrackInfo object for a discogs track."""
|
||||
|
||||
artistinfo = albumartistinfo.clone()
|
||||
|
||||
title = track["title"]
|
||||
if self.config["index_tracks"]:
|
||||
prefix = ", ".join(divisions)
|
||||
|
|
@ -861,15 +835,15 @@ class DiscogsPlugin(MetadataSourcePlugin):
|
|||
track_id = None
|
||||
medium, medium_index, _ = self.get_track_index(track["position"])
|
||||
|
||||
# If artists are found on the track, we will use those instead
|
||||
if artists := track.get("artists", []):
|
||||
artistinfo = ArtistState.build(self, artists)
|
||||
|
||||
length = self.get_track_length(track["duration"])
|
||||
|
||||
# Add featured artists
|
||||
if extraartists := track.get("extraartists", []):
|
||||
artistinfo = ArtistState.build(self, extraartists, artistinfo)
|
||||
# If artists are found on the track, we will use those instead
|
||||
artistinfo = ArtistState.from_plugin(
|
||||
self,
|
||||
[
|
||||
*(track.get("artists") or albumartistinfo.raw_artists),
|
||||
*track.get("extraartists", []),
|
||||
],
|
||||
)
|
||||
|
||||
return (
|
||||
TrackInfo(
|
||||
|
|
|
|||
|
|
@ -699,15 +699,9 @@ def test_anv_album_artist():
|
|||
def test_parse_featured_artists(track, expected_artist, expected_artists):
|
||||
"""Tests the plugins ability to parse a featured artist.
|
||||
Ignores artists that are not listed as featured."""
|
||||
artistinfo = ArtistState(
|
||||
artist="ARTIST",
|
||||
artist_id="1",
|
||||
artists=["ARTIST"],
|
||||
artists_ids=["1"],
|
||||
artist_credit="ARTIST",
|
||||
artists_credit=["ARTIST"],
|
||||
)
|
||||
t, _, _ = DiscogsPlugin().get_track_info(track, 1, 1, artistinfo)
|
||||
plugin = DiscogsPlugin()
|
||||
artistinfo = ArtistState.from_plugin(plugin, [_artist("ARTIST")])
|
||||
t, _, _ = plugin.get_track_info(track, 1, 1, artistinfo)
|
||||
assert t.artist == expected_artist
|
||||
assert t.artists == expected_artists
|
||||
|
||||
|
|
@ -756,7 +750,8 @@ def test_get_media_and_albumtype(formats, expected_media, expected_albumtype):
|
|||
def test_va_buildartistinfo(given_artists, expected_info, config_va_name):
|
||||
config["va_name"] = config_va_name
|
||||
assert (
|
||||
ArtistState.build(DiscogsPlugin(), given_artists).info == expected_info
|
||||
ArtistState.from_plugin(DiscogsPlugin(), given_artists).info
|
||||
== expected_info
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue