diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index d6d8c05f5..c1c5eeefa 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -59,23 +59,41 @@ else: def _flatten_artist_credit(credit): """Given a list representing an ``artist-credit`` block, flatten the - data into a pair of strings: the "canonical" joined artist name and - the specific "credit" joined artist name. + data into a triple of joined artist name strings: canonical, sort, and + credit. """ artist_parts = [] + artist_sort_parts = [] artist_credit_parts = [] for el in credit: if isinstance(el, basestring): + # Join phrase. artist_parts.append(el) artist_credit_parts.append(el) + artist_sort_parts.append(el) + else: + # An artist. cur_artist_name = el['artist']['name'] artist_parts.append(cur_artist_name) - if 'name' in el: # Special artist credit. + + # Artist sort name. + if 'sort-name' in el['artist']: + artist_sort_parts.append(el['artist']['sort-name']) + else: + artist_sort_parts.append(cur_artist_name) + + # Artist credit. + if 'name' in el: artist_credit_parts.append(el['name']) else: artist_credit_parts.append(cur_artist_name) - return ''.join(artist_parts), ''.join(artist_credit_parts) + + return ( + ''.join(artist_parts), + ''.join(artist_sort_parts), + ''.join(artist_credit_parts), + ) def track_info(recording, medium=None, medium_index=None): """Translates a MusicBrainz recording result dictionary into a beets @@ -89,13 +107,12 @@ def track_info(recording, medium=None, medium_index=None): if recording.get('artist-credit'): # Get the artist names. - info.artist, info.artist_credit = \ + info.artist, info.artist_sort, info.artist_credit = \ _flatten_artist_credit(recording['artist-credit']) # Get the ID and sort name of the first artist. artist = recording['artist-credit'][0]['artist'] info.artist_id = artist['id'] - info.artist_sort = artist['sort-name'] if recording.get('length'): info.length = int(recording['length'])/(1000.0) @@ -117,7 +134,7 @@ def album_info(release): AlbumInfo object containing the interesting data about that release. """ # Get artist name using join phrases. - artist_name, artist_credit_name = \ + artist_name, artist_sort_name, artist_credit_name = \ _flatten_artist_credit(release['artist-credit']) # Basic info. @@ -141,7 +158,7 @@ def album_info(release): release['artist-credit'][0]['artist']['id'], track_infos, mediums=len(release['medium-list']), - artist_sort=release['artist-credit'][0]['artist']['sort-name'], + artist_sort=artist_sort_name, artist_credit=artist_credit_name, ) info.va = info.artist_id == VARIOUS_ARTISTS_ID diff --git a/docs/changelog.rst b/docs/changelog.rst index c487818a8..e1a61c6b4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -29,6 +29,8 @@ Changelog copied. This solves a problem (introduced in 1.0b14) where beets could crash after adding files to the library but before finishing copying them; during the next import, the (external) files would be moved instead of copied. +* Artist sort names are now populated correctly for multi-artist tracks and + releases. (Previously, they only reflected the first artist.) * Fix ID3 tag name for the catalog number field. * :doc:`/plugins/chroma`: Fix occasional crash at end of fingerprint submission and give more context to "failed fingerprint generation" errors. diff --git a/test/test_mb.py b/test/test_mb.py index 3e7282ab7..510006b6b 100644 --- a/test/test_mb.py +++ b/test/test_mb.py @@ -269,6 +269,30 @@ class MBAlbumInfoTest(unittest.TestCase): self.assertEqual(track.artist_sort, 'TRACK ARTIST SORT NAME') self.assertEqual(track.artist_credit, 'TRACK ARTIST CREDIT') +class ArtistFlatteningTest(unittest.TestCase): + def _credit_dict(self, suffix=''): + return { + 'artist': { + 'name': 'NAME' + suffix, + 'sort-name': 'SORT' + suffix, + }, + 'name': 'CREDIT' + suffix, + } + + def test_single_artist(self): + a, s, c = mb._flatten_artist_credit([self._credit_dict()]) + self.assertEqual(a, 'NAME') + self.assertEqual(s, 'SORT') + self.assertEqual(c, 'CREDIT') + + def test_two_artists(self): + a, s, c = mb._flatten_artist_credit( + [self._credit_dict('a'), ' AND ', self._credit_dict('b')] + ) + self.assertEqual(a, 'NAMEa AND NAMEb') + self.assertEqual(s, 'SORTa AND SORTb') + self.assertEqual(c, 'CREDITa AND CREDITb') + def suite(): return unittest.TestLoader().loadTestsFromName(__name__)