1818
1919from __future__ import annotations
2020
21+ import copy
2122import http .client
2223import json
2324import os
@@ -105,6 +106,24 @@ class Track(TypedDict):
105106 sub_tracks : NotRequired [list [Track ]]
106107
107108
109+ class ArtistInfo (TypedDict ):
110+ artist : str
111+ artists : list [str ]
112+ artist_credit : str
113+ artists_credit : list [str ]
114+ artist_id : str
115+ artists_ids : list [str ]
116+
117+
118+ class AlbumArtistInfo (ArtistInfo ):
119+ albumartist : str
120+ albumartists : list [str ]
121+ albumartist_credit : str
122+ albumartists_credit : list [str ]
123+ albumartist_id : str
124+ albumartists_ids : list [str ]
125+
126+
108127class DiscogsPlugin (MetadataSourcePlugin ):
109128 def __init__ (self ):
110129 super ().__init__ ()
@@ -261,7 +280,6 @@ def track_for_id(self, track_id: str) -> TrackInfo | None:
261280 for track in album .tracks :
262281 if track .track_id == track_id :
263282 return track
264-
265283 return None
266284
267285 def get_albums (self , query : str ) -> Iterable [AlbumInfo ]:
@@ -346,6 +364,121 @@ def get_artist_with_anv(
346364 artist , artist_id = self .get_artist (artist_list , join_key = "join" )
347365 return self .strip_disambiguation (artist ), artist_id
348366
367+ def build_albumartistinfo (self , artists : list [Artist ]) -> AlbumArtistInfo :
368+ info = self .build_artistinfo (artists , album_artist = True )
369+ albumartist : AlbumArtistInfo = {
370+ ** info ,
371+ "albumartist" : info ["artist" ],
372+ "albumartist_id" : info ["artist_id" ],
373+ "albumartists" : info ["artists" ],
374+ "albumartists_ids" : info ["artists_ids" ],
375+ "albumartist_credit" : info ["artist_credit" ],
376+ "albumartists_credit" : info ["artists_credit" ],
377+ }
378+ return albumartist
379+
380+ def build_artistinfo (
381+ self ,
382+ given_artists : list [Artist ],
383+ given_info : ArtistInfo | None = None ,
384+ album_artist : bool = False ,
385+ ) -> ArtistInfo :
386+ """Iterates through a discogs result and builds
387+ up the artist fields. Does not contribute to
388+ artist_sort as Discogs does not define that.
389+
390+ :param artists: A list of Discogs Artist objects
391+
392+ :param album_artist: If building an album artist,
393+ we need to account for the album_artist anv parameter.
394+ :return an ArtistInfo dictionary.
395+ """
396+ info : ArtistInfo = {
397+ "artist" : "" ,
398+ "artist_id" : "" ,
399+ "artists" : [],
400+ "artists_ids" : [],
401+ "artist_credit" : "" ,
402+ "artists_credit" : [],
403+ }
404+ if given_info :
405+ info = copy .deepcopy (given_info )
406+
407+ a_anv : bool = self .config ["anv" ]["artist" ].get (bool )
408+ ac_anv : bool = self .config ["anv" ]["artist_credit" ].get (bool )
409+ aa_anv : bool = self .config ["anv" ]["album_artist" ].get (bool )
410+ feat_str : str = f" { self .config ['featured_string' ].get (str )} "
411+
412+ artist = ""
413+ artist_anv = ""
414+ artists : list [str ] = []
415+ artists_anv : list [str ] = []
416+
417+ join = ""
418+ featured_flag = False
419+ # Iterate through building the artist strings
420+ for a in given_artists :
421+ # Get the artist name
422+ name = self .strip_disambiguation (a ["name" ])
423+ discogs_id = a ["id" ]
424+ anv = a .get ("anv" , name )
425+ role = a .get ("role" , "" ).lower ()
426+ # Check if the artist is Various
427+ if name .lower () == "various" :
428+ name = config ["va_name" ].as_str ()
429+ anv = name
430+
431+ if "featuring" in role :
432+ if not featured_flag :
433+ artist += feat_str
434+ artist_anv += feat_str
435+ artist += name
436+ artist_anv += anv
437+ featured_flag = True
438+ else :
439+ artist = self ._join_artist (artist , name , join )
440+ artist_anv = self ._join_artist (artist_anv , anv , join )
441+ elif role and "featuring" not in role :
442+ continue
443+ else :
444+ artist = self ._join_artist (artist , name , join )
445+ artist_anv = self ._join_artist (artist_anv , anv , join )
446+ artists .append (name )
447+ artists_anv .append (anv )
448+ # Only the first ID is set for the singular field
449+ if not info ["artist_id" ]:
450+ info ["artist_id" ] = discogs_id
451+ info ["artists_ids" ].append (discogs_id )
452+ # Update join for the next artist
453+ join = a .get ("join" , "" )
454+ # Assign fields as necessary
455+ if (a_anv and not album_artist ) or (aa_anv and album_artist ):
456+ info ["artist" ] += artist_anv
457+ info ["artists" ] += artists_anv
458+ else :
459+ info ["artist" ] += artist
460+ info ["artists" ] += artists
461+
462+ if ac_anv :
463+ info ["artist_credit" ] += artist_anv
464+ info ["artists_credit" ] += artists_anv
465+ else :
466+ info ["artist_credit" ] += artist
467+ info ["artists_credit" ] += artists
468+ return info
469+
470+ def _join_artist (self , base : str , artist : str , join : str ) -> str :
471+ # Expand the artist field
472+ if not base :
473+ base = artist
474+ else :
475+ if join :
476+ base += f" { join } "
477+ else :
478+ base += ", "
479+ base += artist
480+ return base
481+
349482 def get_album_info (self , result : Release ) -> AlbumInfo | None :
350483 """Returns an AlbumInfo object for a discogs Release object."""
351484 # Explicitly reload the `Release` fields, as they might not be yet
@@ -375,11 +508,8 @@ def get_album_info(self, result: Release) -> AlbumInfo | None:
375508 return None
376509
377510 artist_data = [a .data for a in result .artists ]
378- album_artist , album_artist_id = self .get_artist_with_anv (artist_data )
379- album_artist_anv , _ = self .get_artist_with_anv (
380- artist_data , use_anv = True
381- )
382- artist_credit = album_artist_anv
511+ # Information for the album artist
512+ albumartist : AlbumArtistInfo = self .build_albumartistinfo (artist_data )
383513
384514 album = re .sub (r" +" , " " , result .title )
385515 album_id = result .data ["id" ]
@@ -388,18 +518,11 @@ def get_album_info(self, result: Release) -> AlbumInfo | None:
388518 # information and leave us with skeleton `Artist` objects that will
389519 # each make an API call just to get the same data back.
390520 tracks = self .get_tracks (
391- result .data ["tracklist" ],
392- (album_artist , album_artist_anv , album_artist_id ),
521+ result .data ["tracklist" ], self .build_artistinfo (artist_data )
393522 )
394523
395- # Assign ANV to the proper fields for tagging
396- if not self .config ["anv" ]["artist_credit" ]:
397- artist_credit = album_artist
398- if self .config ["anv" ]["album_artist" ]:
399- album_artist = album_artist_anv
400-
401524 # Extract information for the optional AlbumInfo fields, if possible.
402- va = result . data [ "artists" ][ 0 ]. get ( "name" , "" ). lower () == "various"
525+ va = albumartist [ "albumartist" ] == config [ "va_name" ]. as_str ()
403526 year = result .data .get ("year" )
404527 mediums = [t .medium for t in tracks ]
405528 country = result .data .get ("country" )
@@ -431,11 +554,7 @@ def get_album_info(self, result: Release) -> AlbumInfo | None:
431554 cover_art_url = self .select_cover_art (result )
432555
433556 # Additional cleanups
434- # (various artists name, catalog number, media, disambiguation).
435- if va :
436- va_name = config ["va_name" ].as_str ()
437- album_artist = va_name
438- artist_credit = va_name
557+ # (catalog number, media, disambiguation).
439558 if catalogno == "none" :
440559 catalogno = None
441560 # Explicitly set the `media` for the tracks, since it is expected by
@@ -458,9 +577,7 @@ def get_album_info(self, result: Release) -> AlbumInfo | None:
458577 return AlbumInfo (
459578 album = album ,
460579 album_id = album_id ,
461- artist = album_artist ,
462- artist_credit = artist_credit ,
463- artist_id = album_artist_id ,
580+ ** albumartist , # Unpacks values to satisfy the keyword arguments
464581 tracks = tracks ,
465582 albumtype = albumtype ,
466583 va = va ,
@@ -478,7 +595,7 @@ def get_album_info(self, result: Release) -> AlbumInfo | None:
478595 data_url = data_url ,
479596 discogs_albumid = discogs_albumid ,
480597 discogs_labelid = labelid ,
481- discogs_artistid = album_artist_id ,
598+ discogs_artistid = albumartist [ "albumartist_id" ] ,
482599 cover_art_url = cover_art_url ,
483600 )
484601
@@ -503,7 +620,7 @@ def format(self, classification: Iterable[str]) -> str | None:
503620 def _process_clean_tracklist (
504621 self ,
505622 clean_tracklist : list [Track ],
506- album_artist_data : tuple [ str , str , str | None ] ,
623+ albumartistinfo : ArtistInfo ,
507624 ) -> tuple [
508625 list [TrackInfo ],
509626 dict [int , str ],
@@ -531,7 +648,7 @@ def _process_clean_tracklist(
531648 divisions += next_divisions
532649 del next_divisions [:]
533650 track_info , medium , medium_index = self .get_track_info (
534- track , index , divisions , album_artist_data
651+ track , index , divisions , albumartistinfo
535652 )
536653 track_info .track_alt = track ["position" ]
537654 tracks .append (track_info )
@@ -565,7 +682,7 @@ def _process_clean_tracklist(
565682 def get_tracks (
566683 self ,
567684 tracklist : list [Track ],
568- album_artist_data : tuple [ str , str , str | None ] ,
685+ albumartistinfo : ArtistInfo ,
569686 ) -> list [TrackInfo ]:
570687 """Returns a list of TrackInfo objects for a discogs tracklist."""
571688 try :
@@ -578,7 +695,7 @@ def get_tracks(
578695 self ._log .error ("uncaught exception in coalesce_tracks: {}" , exc )
579696 clean_tracklist = tracklist
580697 processed = self ._process_clean_tracklist (
581- clean_tracklist , album_artist_data
698+ clean_tracklist , albumartistinfo
582699 )
583700 (
584701 tracks ,
@@ -754,16 +871,11 @@ def get_track_info(
754871 track : Track ,
755872 index : int ,
756873 divisions : list [str ],
757- album_artist_data : tuple [ str , str , str | None ] ,
874+ albumartistinfo : ArtistInfo ,
758875 ) -> tuple [TrackInfo , str | None , str | None ]:
759876 """Returns a TrackInfo object for a discogs track."""
760877
761- artist , artist_anv , artist_id = album_artist_data
762- artist_credit = artist_anv
763- if not self .config ["anv" ]["artist_credit" ]:
764- artist_credit = artist
765- if self .config ["anv" ]["artist" ]:
766- artist = artist_anv
878+ artistinfo = albumartistinfo .copy ()
767879
768880 title = track ["title" ]
769881 if self .config ["index_tracks" ]:
@@ -775,39 +887,19 @@ def get_track_info(
775887
776888 # If artists are found on the track, we will use those instead
777889 if artists := track .get ("artists" , []):
778- artist , artist_id = self .get_artist_with_anv (
779- artists , self .config ["anv" ]["artist" ]
780- )
781- artist_credit , _ = self .get_artist_with_anv (
782- artists , self .config ["anv" ]["artist_credit" ]
783- )
890+ artistinfo = self .build_artistinfo (artists )
891+
784892 length = self .get_track_length (track ["duration" ])
785893
786894 # Add featured artists
787895 if extraartists := track .get ("extraartists" , []):
788- featured_list = [
789- artist
790- for artist in extraartists
791- if "Featuring" in artist ["role" ]
792- ]
793- featured , _ = self .get_artist_with_anv (
794- featured_list , self .config ["anv" ]["artist" ]
795- )
796- featured_credit , _ = self .get_artist_with_anv (
797- featured_list , self .config ["anv" ]["artist_credit" ]
798- )
799- if featured :
800- artist += f" { self .config ['featured_string' ]} { featured } "
801- artist_credit += (
802- f" { self .config ['featured_string' ]} { featured_credit } "
803- )
896+ artistinfo = self .build_artistinfo (extraartists , artistinfo )
897+
804898 return (
805899 TrackInfo (
806900 title = title ,
807901 track_id = track_id ,
808- artist_credit = artist_credit ,
809- artist = artist ,
810- artist_id = artist_id ,
902+ ** artistinfo ,
811903 length = length ,
812904 index = index ,
813905 ),
0 commit comments