Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Capture "primary" artist as a separate field #3176

Open
dicolanl opened this issue Mar 5, 2019 · 13 comments
Labels

Comments

@dicolanl
Copy link

@dicolanl dicolanl commented Mar 5, 2019

In beets when you have a CD that is "artist featuring artist" there is no way to name the folder as the primary artist. This creates multiple folders and then they dont get imported into tools like lidarr.

Example
Artist - Album - Album Artist
As Is: 2 Chainz Featuring Drake - Big Amount - 2 Chainz Featuring Drake
Proposed: 2 Chainz Featuring Drake - Big Amount - 2 Chainz

If you look at the tags the albumartist MBid is for 2 Chainz.
The MBid for the album is as is
the MBid for release-group is as is.

Beets is handling "Album Artist" for Folder naming from the album artist property. But the Album artist should be the pirmary artist if you want other tools like lidarr to manage this better.

It also creates a bunch of extra artist folders.
Possible option would be to use the FtinTitle plugin and mirror to FtinAlbumArtist

tag
artist
release

@sampsyo sampsyo added the needinfo label Mar 5, 2019
@sampsyo

This comment has been minimized.

Copy link
Member

@sampsyo sampsyo commented Mar 5, 2019

Interesting! So would you want a new field called something like $primary_artist that only includes the name of the first artist on a track? Even better would be for $artist itself to be treated as a list so you can just index the first one if you want, but that's a pretty complicated change.

@dicolanl

This comment has been minimized.

Copy link
Author

@dicolanl dicolanl commented Mar 5, 2019

I see two options.

  1. add a primary artist field, which could then be used by your folder naming config.
    Pro: simple.
    Con: other tools like media monkey, lidarr, etc wont understand this field (https://help.mp3tag.de/main_tags.html)

  2. allow a config that sets the albumartist field based the MBid artist.
    pro: other tools would understand this.
    con: harder to implement. I think i saw antother issue indicating Artist MBid should support multiple artists like {artistid}/{artistid}. The code i assume would pick the 1st.

@sampsyo

This comment has been minimized.

Copy link
Member

@sampsyo sampsyo commented Mar 6, 2019

I think we would do the first option. You could then combine this with an orthogonal feature like #488 to get the behavior you want where this affects the "real" artist field.

However, I have to admit that I'm not wild about the idea of a $primary_artist field, because it implies that we'd also want a $secondary_artist and $tertiary_artist—where will the madness end?? Not to mention $albumartist versions, and then also $artist_credit and $albumartist_credit… finding some sort of a clearer way to access artists as "lists" would be much nicer if we can figure it out.

@sampsyo sampsyo added feature and removed needinfo labels Mar 6, 2019
@sampsyo sampsyo changed the title Support for handling Primary Album Artist Capture "primary" artist as a separate field Mar 6, 2019
@GuilhermeHideki

This comment has been minimized.

Copy link
Member

@GuilhermeHideki GuilhermeHideki commented Apr 30, 2019

For the 1st option, I think it would be better to implement #1824 (new multi-value artist tag) and use the inline plugin to get the first artist (it's my manual workflow, using the default artists tag)

@tehniemer

This comment has been minimized.

Copy link

@tehniemer tehniemer commented May 16, 2019

I've been asked to post this here.

There is a plugin called AlbumArtist Extension for musicbrainz picard that allows different formats for the albumartist field. In particular _aaeStdPrimaryAlbumArtist or _aaeCredPrimaryAlbumArtist so the first (primary) album artist would only be used instead of all credited artists. It would be very useful to avoid having a folder structure like this:

Image 1558005638

Ideally they would all fall under Xavier Rudd

@ESaller

This comment has been minimized.

Copy link

@ESaller ESaller commented Oct 21, 2019

As I understand it the core of the question is about directory names.

I wanted to do something similar, mainly to keep the number of artist directories in check.
As no out of the box solution was available I resorted back to the inline plugin.

As described above, the MBID for the album is correct

Here is part of my config:

...
paths:
    default: %the{$primary_album_artist}/$original_year-$original_month-$original_day - $album%aunique{} [%upper{$albumtype}] (%upper{$format})/$disc-$track - $artist - $title ($bitrate)

item_fields:
    primary_album_artist: |
        import json
        import requests
        query_url="https://musicbrainz.org/ws/2/artist/{0}?inc=aliases&fmt=json".format(mb_albumartistid)
        r = requests.get(query_url)
        return r.json()["sort-name"]
...

Example:

As an example take the artist Aiobahn

Almost all of his tracks are some kind of collaboration (e.g. Aiobahn feat. nayuta)

The inline function looks at the track MBID album artist and pulls the sort-name.

This results in all of his singles/albums/etc. being placed in a single folder, while keeping the metadata intact. 🎉🎉🎉

I hope this will help someone in the future 😸

@scruloose

This comment has been minimized.

Copy link

@scruloose scruloose commented Oct 24, 2019

Hi @ESaller, I am very interested in your inline snippet for this!

I have tried pasting your item_fields: block in my config.yaml file as-is, and referencing $primary_album_artist in the following paths:

singleton: $primary_album_artist/00 - Loose Tracks/$artist - $title
default: $primary_album_artist/$original_year - $album [${format}]/$track $title

When I run a beet move to apply the change, I get a bunch of error output which concludes in the following:

  • beetsplug.inline.InlineError: error in inline path field code:
  • Here it quotes the whole "primary_album_artist" code block.
  • KeyError: 'sort-name'

Keeping in mind that I know a very little bit of python, do you have any suggestion where I should start looking for why this is not working?

@scruloose

This comment has been minimized.

Copy link

@scruloose scruloose commented Oct 25, 2019

I've done a bit of playing around with this, and I encountered two difficulties with @ESaller's snippet as-is:

  • If the lookup fails, beets crashes.
  • Singletons don't have an associated mb_albumartistid, so they need different handling.

Since my use case includes trying to salvage a lot of mp3s with incorrect/missing tags, many of which are singletons, I have tried to address both of those difficulties by expanding his snippet to this:

  item_fields:
      primary_album_artist: |
          import json
          import requests
          query_url="https://musicbrainz.org/ws/2/artist/{0}?inc=aliases&fmt=json".format(mb_albumartistid)
          try:
              primary_albumartist= requests.get(query_url).json()["sort-name"]
          except:
              if albumartist_sort:
                  print("MusicBrainz lookup for primary albumartist failed! Falling back on local sort-name.")
                  return albumartist_sort
              else:
                  print("MusicBrainz lookup for primary albumartist failed, and local sort-name not found! Using local artist name as-is.")
                  return albumartist
          else:
              return primary_albumartist

      primary_artist: |
          import json
          import requests
          query_url="https://musicbrainz.org/ws/2/artist/{0}?inc=aliases&fmt=json".format(mb_artistid)
          try:
              primary_artist= requests.get(query_url).json()["sort-name"]
          except:
              if artist_sort:
                  print("MusicBrainz lookup for primary artist failed! Falling back on local sort-name.")
                  return artist_sort
              else:
                  print("MusicBrainz lookup for primary artist failed, and local sort-name not found! Using local artist name as-is.")
                  return artist
          else:
              return primary_artist

It takes the same essential idea, but adds a non-album-dependent version for singletons, and if the lookup fails, it falls back on the local database's sort-name if available, and the local artist name if all else fails. Unless I've messed up (which is entirely possible) this should never cause beets to crash, always use the best available source, and at worst leave things in the same state it found them.

It's kind of a big mess, it's essentially untested, and I couldn't figure out a way to do it without repeating the whole function, but there it is in case it's helpful to anyone!

@sampsyo

This comment has been minimized.

Copy link
Member

@sampsyo sampsyo commented Oct 25, 2019

Cool! If anyone's interested, a fun next step might be to package this code up as a plugin.

@scruloose

This comment has been minimized.

Copy link

@scruloose scruloose commented Oct 26, 2019

That would be cool! I suspect I'd be in over my head with that myself, but I'll do some reading and see if it seems at all realistic. In the meantime, I have a warning for anyone using this snippet, and an idea for anyone who wants to turn it into a plugin.

Warning: this snippet as written simply hammers the MB server, making a request for every single track, which will cause failed requests as their flood-protection kicks in. As a crude rate-limiting workaround, add import time to the top of both chunks, and time.sleep(1) in the try blocks right after the query.

Idea: While I don't think I can do this in the context of an 'Inline' function, I think ideally we would also want to prevent unnecessary repetitive requests by defining a dictionary that lives for the duration of one whole beet import run, and then:

  • just before returning the name retrieved from MusicBrainz, add the ID — name pair to the dictionary
  • before querying MusicBrainz, check the ID against the local dictionary and retrieve the name from there instead if we've already got it.
@ESaller

This comment has been minimized.

Copy link

@ESaller ESaller commented Oct 28, 2019

@scruloose Thanks for the updates. Did not think about singeltons as I have none currently. I also have noticed the timeouts for the API and added a sleep there.

The API "abuse" is something I am not really happy with. I will probably look into more closely this or next weekend.

Idea: I am not familiar with architecture just yet. Either beets is already requesting the data somewhere and we can extend that functionality to include additional field information as path parameter OR package it in a plugin and add all other fields from the request.

@scruloose

This comment has been minimized.

Copy link

@scruloose scruloose commented Oct 28, 2019

@ESaller And thank you for the initial snippet. I would never have thought to query MB with the artist_id.

I am playing around with trying to make a plugin out of this... so far all I've made is a mess, but whether it turns into something usable or not, I'm learning lots. ☺

Other architecture questions:

  • Can a plugin send queries through beets' main network thread? If so, that would seem like a way more elegant solution if we do need to get stuff from the MB database. (It would prevent different components hitting MB at unpredictable times and maybe tripping the rate-limiting, and I assume the main network thread already has some sort of backoff-retry implementation.)
  • If we refer to the beets value artist_sort (or albumartist_sort) in a plugin, do we get the automatic fallback to artist (or albumartist), and vice-versa, or would the plugin have to implement that itself?

I don't have nearly enough knowledge to figure these out from reading the beets source code.

@scruloose

This comment has been minimized.

Copy link

@scruloose scruloose commented Nov 4, 2019

I have a very rough prototype of a plugin that adds path variables for primary_artist, primary_artist_sort, primary_albumartist, and primary_albumartist_sort. I'm pretty sure it's terrible Python, but I got it to load and run on my beets install without burning the house down, which I thought was pretty exciting.

I switched from using the requests and json libraries to musicbrainzngs (built-in rate limiting!), added some logic to get the 'Artist name' alias that corresponds to the user's language pref, and added caching of IDs that have already been looked up. I'm sure @sampsyo is right that ultimately, the best solution would be for beets to store the artists in a collaboration as a list, if that can be figured out, but hopefully this is better than nothing for now.

One thing I know needs to be fixed is to use proper logging, instead of all the print statements scattered through the thing right now. I still haven't figured out whether there's some sort of beets-mbngs layer I should be calling instead of calling musicbrainzngs directly.

If anyone wants to pick that up and make something worthwhile out of it, please do! I'm also open to comments and pointers. I took a look at @GuilhermeHideki's WIP but only have a vague idea how it works, so my apologies if I'm just repeating what's already been done. I did totally copy the regex for sanity-checking MB ids from there, though!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants
You can’t perform that action at this time.