Repository: beetbox/beets

Issue:
Fetch `remixer` field from MusicBrainz
### Problem
Musicbrainz seems to have a field called `remixer` for remixes, this info isn't written to my files.

Example: https://musicbrainz.org/release/7c7f7ddf-c021-4ee8-993d-d1c330b4a36a

Using it for the artist field, similarly to the `artist_credit` option would also be nice.

According to the [Picard docs](https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html) there would even be a proper place to store it in ID3.

### Setup

* OS: Arch Linux
* Python version:  3.10
* beets version: 1.6.0
* Turning off plugins made problem go away (yes/no): no



Discussion:
Sounds like a reasonable thing to add! Hopefully it shouldn't be too hard (the place to do it is in `beets/autotag/mb.py`).
I am currently working on this. It is my first time contributing to beets so I might take some time to get familiar with the codebase and structure.
Awesome! Let us know with a Discussion post if you have any code-level questions we can answer.
So after looking at the example given by @TypicalFence I can see the remixer field on the musicbrainz website. However, looking at the [JSON](https://musicbrainz.org/ws/2/release/7c7f7ddf-c021-4ee8-993d-d1c330b4a36a?inc=aliases%2Bartist-credits%2Blabels%2Bdiscids%2Brecordings&fmt=json) or [XML](https://musicbrainz.org/ws/2/release/7c7f7ddf-c021-4ee8-993d-d1c330b4a36a?inc=aliases%2Bartist-credits%2Blabels%2Bdiscids%2Brecordings) that is available from the muiscbrainz website I can't find the remixer mentioned anywhere except for the title. I don't know how to proceed since to me it seems like this information isn't available through the api queries beets makes at the moment. Is this the case or am i missing something?
@Bootjewolf I just went over the musicbrainz api docs.

The remixer tag is a [artist relation](https://musicbrainz.org/relationship/7950be4d-13a3-48e7-906b-5af562e39544).
When we include `artist-rels` in a request of a recording from the album we get the following: [JSON](https://musicbrainz.org/ws/2/recording/c9f6caa9-2d47-4ac3-93cd-815d83091c06?fmt=json&inc=artist-rels), which includes a relation of the type `remixer`. :tada:

Now for that to work for the requests that you posted we need to include `recording-level-rels`, which just tells musicbrainz to resolve the rels on a recording (track/song) level.

This leaves us with the following url: https://musicbrainz.org/ws/2/release/7c7f7ddf-c021-4ee8-993d-d1c330b4a36a?inc=aliases%2Bartist-credits%2Blabels%2Bdiscids%2Brecordings%2Bartist-rels%2Brecording-level-rels&fmt=json

You can find this in the api docs [here](https://musicbrainz.org/doc/MusicBrainz_API#Misc_inc=_arguments). 

The api is rather complicated tbh.


Thank you! I will have another look at it this coming week.

Resolving patch:
diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py
--- a/beets/autotag/mb.py
+++ b/beets/autotag/mb.py
@@ -202,6 +202,19 @@ def _flatten_artist_credit(credit):
     )
 
 
+def _get_related_artist_names(relations, relation_type):
+    """Given a list representing the artist relationships extract the names of
+    the remixers and concatenate them.
+    """
+    related_artists = []
+
+    for relation in relations:
+        if relation['type'] == relation_type:
+            related_artists.append(relation['artist']['name'])
+
+    return ', '.join(related_artists)
+
+
 def track_info(recording, index=None, medium=None, medium_index=None,
                medium_total=None):
     """Translates a MusicBrainz recording result dictionary into a beets
@@ -231,6 +244,12 @@ def track_info(recording, index=None, medium=None, medium_index=None,
         artist = recording['artist-credit'][0]['artist']
         info.artist_id = artist['id']
 
+    if recording.get('artist-relation-list'):
+        info.remixer = _get_related_artist_names(
+            recording['artist-relation-list'],
+            relation_type='remixer'
+        )
+
     if recording.get('length'):
         info.length = int(recording['length']) / (1000.0)
 
diff --git a/beets/library.py b/beets/library.py
--- a/beets/library.py
+++ b/beets/library.py
@@ -466,6 +466,7 @@ class Item(LibModel):
         'artist': types.STRING,
         'artist_sort': types.STRING,
         'artist_credit': types.STRING,
+        'remixer': types.STRING,
         'album': types.STRING,
         'albumartist': types.STRING,
         'albumartist_sort': types.STRING,


Test patch:
diff --git a/test/test_mb.py b/test/test_mb.py
--- a/test/test_mb.py
+++ b/test/test_mb.py
@@ -109,8 +109,8 @@ def _make_release(self, date_str='2009', tracks=None, track_length=None,
         })
         return release
 
-    def _make_track(self, title, tr_id, duration, artist=False, video=False,
-                    disambiguation=None):
+    def _make_track(self, title, tr_id, duration, artist=False,
+                    video=False, disambiguation=None, remixer=False):
         track = {
             'title': title,
             'id': tr_id,
@@ -128,6 +128,22 @@ def _make_track(self, title, tr_id, duration, artist=False, video=False,
                     'name': 'RECORDING ARTIST CREDIT',
                 }
             ]
+        if remixer:
+            track['artist-relation-list'] = [
+                {
+                    'type': 'remixer',
+                    'type-id': 'RELATION TYPE ID',
+                    'target': 'RECORDING REMIXER ARTIST ID',
+                    'direction': 'RECORDING RELATION DIRECTION',
+                    'artist':
+                        {
+                            'id': 'RECORDING REMIXER ARTIST ID',
+                            'type': 'RECORDING REMIXER ARTIST TYPE',
+                            'name': 'RECORDING REMIXER ARTIST NAME',
+                            'sort-name': 'RECORDING REMIXER ARTIST SORT NAME'
+                        }
+                }
+            ]
         if video:
             track['video'] = 'true'
         if disambiguation:
@@ -339,6 +355,12 @@ def test_track_artist_overrides_recording_artist(self):
         self.assertEqual(track.artist_sort, 'TRACK ARTIST SORT NAME')
         self.assertEqual(track.artist_credit, 'TRACK ARTIST CREDIT')
 
+    def test_parse_recording_remixer(self):
+        tracks = [self._make_track('a', 'b', 1, remixer=True)]
+        release = self._make_release(None, tracks=tracks)
+        track = mb.album_info(release).tracks[0]
+        self.assertEqual(track.remixer, 'RECORDING REMIXER ARTIST NAME')
+
     def test_data_source(self):
         release = self._make_release()
         d = mb.album_info(release)