diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 3e06583170..12dd6a0a0d 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -78,7 +78,7 @@ def get_message(self): BROWSE_INCLUDES.append("work-level-rels") BROWSE_CHUNKSIZE = 100 BROWSE_MAXTRACKS = 500 -TRACK_INCLUDES = ['artists', 'aliases', 'isrcs'] +TRACK_INCLUDES = ['artists', 'aliases', 'isrcs', 'artist-rels'] if 'work-level-rels' in musicbrainzngs.VALID_INCLUDES['recording']: TRACK_INCLUDES += ['work-level-rels', 'artist-rels'] if 'genres' in musicbrainzngs.VALID_INCLUDES['recording']: @@ -270,8 +270,8 @@ def track_info(recording, index=None, medium=None, medium_index=None, info.arranger = u', '.join(arranger) # Supplementary fields provided by plugins - extra_trackdatas = plugins.send('mb_track_extract', data=recording) - for extra_trackdata in extra_trackdatas: + extra_trackdatasets = plugins.send('mb_track_extract', data=recording) + for extra_trackdata in extra_trackdatasets: info.update(extra_trackdata) info.decode() @@ -462,8 +462,8 @@ def album_info(release): if config['musicbrainz']['genres'] and genres: info.genre = ';'.join(g['name'] for g in genres) - extra_albumdatas = plugins.send('mb_album_extract', data=release) - for extra_albumdata in extra_albumdatas: + extra_albumdatasets = plugins.send('mb_album_extract', data=release) + for extra_albumdata in extra_albumdatasets: info.update(extra_albumdata) info.decode() diff --git a/beetsplug/mbsync.py b/beetsplug/mbsync.py index ee2c4b5bd6..aa6d5b19c1 100644 --- a/beetsplug/mbsync.py +++ b/beetsplug/mbsync.py @@ -27,10 +27,105 @@ MBID_REGEX = r"(\d|\w){8}-(\d|\w){4}-(\d|\w){4}-(\d|\w){4}-(\d|\w){12}" +def track_performers(data): + """ + Gets the data dict (track info) from MusicBrainz and extracts + performer names and roles, puts them in the artists dict. + Fetches both names and sort names, adapts the roles to fit as + single understandable strings, with the mbsync_ prefix. + input: data (dict from MusicBrainz) + output: artists (dict with roles, names and sort names) + """ + artists = {} + for artist_relation in data.get('artist-relation-list', ()): + if 'type' in artist_relation: + role = 'mbsync ' + role += artist_relation['type'] + if 'balance' in role or 'recording' in role or 'sound' in role: + role += ' engineer' + if 'performing orchestra' in role: + role = 'mbsync orchestra' + role_sort = role + ' sort' + if 'attribute-list' in artist_relation: + role += ' - ' + role_sort += ' - ' + role += ', '.join(artist_relation['attribute-list']) + role_sort += ', '.join(artist_relation['attribute-list']) + if 'attributes' in artist_relation: + for attribute in artist_relation['attributes']: + if 'credited-as' in attribute: + role += ' (' + attribute['credited-as'] + ')' + role_sort += ' (' + attribute['credited-as'] + ')' + role = role.replace(" ", "_") + role_sort = role_sort.replace(' ', '_') + if role in artists: + artists[role].append(artist_relation['artist']['name']) + artists[role_sort].append( + artist_relation['artist']['sort-name']) + else: + artists[role] = [artist_relation['artist']['name']] + artists[role_sort] = [artist_relation[ + 'artist']['sort-name']] + for key in artists: + artists[key] = u'; '.join(artists[key]) + return artists + + +def album_performers(data): + """ + Similar to track_performers but with album performers, + data is the album_info dict from MusicBrainz. + """ + + artists = {} + for artist_relation in data.get('artist-relation-list', ()): + if 'type' in artist_relation: + role = 'mbsync album ' + role += artist_relation['type'] + if 'balance' in role or 'recording' in role or 'sound' in role: + role += ' engineer' + if 'performing orchestra' in role: + role = 'mbsync album orchestra' + role_sort = role + ' sort' + if 'attribute-list' in artist_relation: + role += ' - ' + role_sort += ' - ' + role += ', '.join(artist_relation['attribute-list']) + role_sort += ', '.join(artist_relation['attribute-list']) + if 'attributes' in artist_relation: + for attribute in artist_relation['attributes']: + if 'credited-as' in attribute: + role += ' (' + attribute['credited-as'] + ')' + role_sort += ' (' + attribute['credited-as'] + ')' + role = role.replace(" ", "_") + role_sort = role_sort.replace(' ', '_') + if role in artists: + artists[role].append(artist_relation['artist']['name']) + artists[role_sort].append( + artist_relation['artist']['sort-name']) + else: + artists[role] = [artist_relation['artist']['name']] + artists[role_sort] = [artist_relation[ + 'artist']['sort-name']] + for key in artists: + artists[key] = u'; '.join(artists[key]) + + return artists + + class MBSyncPlugin(BeetsPlugin): def __init__(self): super(MBSyncPlugin, self).__init__() + self.config.add({ + u'bin': u'mbsync', + u'performer_info': False, + }) + + if self.config['performer_info'].get(bool): + self.register_listener('mb_track_extract', track_performers) + self.register_listener('mb_album_extract', album_performers) + def commands(self): cmd = ui.Subcommand('mbsync', help=u'update metadata from musicbrainz') @@ -47,6 +142,11 @@ def commands(self): u'-W', u'--nowrite', action='store_false', default=None, dest='write', help=u"don't write updated metadata to files") + cmd.parser.add_option( + u'-P', u'--performer_info', action='store_true', + dest='performer_info', + default=self.config['performer_info'].get(bool), + help=u"Fetch performer info") cmd.parser.add_format_option() cmd.func = self.func return [cmd] @@ -57,12 +157,13 @@ def func(self, lib, opts, args): move = ui.should_move(opts.move) pretend = opts.pretend write = ui.should_write(opts.write) + performer_info = opts.performer_info query = ui.decargs(args) - self.singletons(lib, query, move, pretend, write) - self.albums(lib, query, move, pretend, write) + self.singletons(lib, query, move, pretend, write, performer_info) + self.albums(lib, query, move, pretend, write, performer_info) - def singletons(self, lib, query, move, pretend, write): + def singletons(self, lib, query, move, pretend, write, performer_info): """Retrieve and apply info from the autotagger for items matched by query. """ @@ -86,13 +187,18 @@ def singletons(self, lib, query, move, pretend, write): item.mb_trackid, item_formatted) continue - + # Clean up obsolete flexible fields + if performer_info: + for tag in item: + if tag.startswith('mbsync_') and tag not in track_info: + del item[tag] # Apply. with lib.transaction(): autotag.apply_item_metadata(item, track_info) + ui.show_model_changes(item) apply_item_changes(lib, item, move, pretend, write) - def albums(self, lib, query, move, pretend, write): + def albums(self, lib, query, move, pretend, write, performer_info): """Retrieve and apply info from the autotagger for albums matched by query and their items. """ @@ -134,6 +240,12 @@ def albums(self, lib, query, move, pretend, write): # work for albums that have missing or extra tracks. mapping = {} for item in items: + # Clean up obsolete flexible fields + if performer_info: + for tag in item: + if (tag.startswith('mbsync_') and + (tag not in track_info or tag not in album_info)): + del item[tag] if item.mb_releasetrackid and \ item.mb_releasetrackid in releasetrack_index: mapping[item] = releasetrack_index[item.mb_releasetrackid] diff --git a/docs/changelog.rst b/docs/changelog.rst index 25b09ff80a..b5dbf87ae8 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -211,10 +211,15 @@ Other new things: Thanks to :user:`arogl`. :bug:`3906` * Get ISRC identifiers from musicbrainz Thanks to :user:`aereaux`. -* :doc:`/plugins/metasync`: The ``metasync`` plugin now also fetches the ``Date Added`` field from iTunes databases and stores it in the``itunes_dateadded`` field.Thanks to :user:`sandersantema`. +* :doc:`/plugins/metasync`: The ``metasync`` plugin now also fetches the + ``Date Added`` field from iTunes databases and stores it in the + ``itunes_dateadded`` field.Thanks to :user:`sandersantema`. * :doc:`/plugins/lyrics`: Added Tekstowo.pl lyrics provider. Thanks to various people for the implementation and for reporting issues with the initial version. :bug:`3344` :bug:`3904` :bug:`3905` :bug:`3994` +* :doc:`/plugins/mbsync`: The ``mbsync`` plugin now also fetches performers, + if the ``performer_info`` option is enabled. + Fixes :bug:`1547`, thanks to :user:`dosoe`. .. _py7zr: https://pypi.org/project/py7zr/ diff --git a/docs/plugins/mbsync.rst b/docs/plugins/mbsync.rst index 1c8663dcae..0c84050242 100644 --- a/docs/plugins/mbsync.rst +++ b/docs/plugins/mbsync.rst @@ -36,3 +36,8 @@ The command has a few command-line options: * To customize the output of unrecognized items, use the ``-f`` (``--format``) option. The default output is ``format_item`` or ``format_album`` for items and albums, respectively. +* To also get performer data from MusicBrainz, use the ``-P`` + (``--performer_info``) option. This will add all track and album performers + as additional tags (recognisable by the ``mbsync_`` prefix). To automatically + fetch performer info, enable the ``performer_info`` option in the + configuration. Default: ``no``.