How to get artist metadata through WebService? How to iterate through tracks in album?

Hey there,

As per request of a friend, I want to develop a Picard plug-in with several features, but the first feature I would like to implement is getting the artist’s country (on a per track basis) implemented as a metadata tag. This is to have the country’s flag show up in the foobar2000 theme Georgia, right next to the artist’s name.

While I got a proof of concept working, it has two major issues preventing me from releasing it publicly for proper use:

  • Due to the rate limitations of MusicBrainz’s API, the current implementation gives an error after about 12 tracks are labelled with the artist’s country (503 error). The code is currently per track basis, while I would prefer to have it done per album basis to save API calls by using a cache.
  • I used an own implementation to get metadata from the API (using Python’s native json and urllib modules), instead of the album webservices tagger, due to its workings not being clear to me from looking at documentation and some plugins in the repository.

Could anyone point me to the right direction how to use the webservices class within the Picard module to obtain the artist’s country (from the area field) and how it could be handled per album iterating through tracks? Or set up a rate limit that would prevent the API giving a 503 error?

Edit:

To make it clear, I’m already aware of which metadata of the API I should be looking at (artist ID and the area to determine the country’s name). I’m asking questions how to use the Picard module properly with MusicBrainz query’s to not have to rely on my own implementation with the Python modules, and how to have the code run as an album action which is capable of iterating through tracks. I’m aware that the album actions can edit general album metadata, but is it possible to do what I want per album and have its tracks all separately adjusted (in case there are differing artists), instead of relying on a per track action (which causes the API to 503 on me due to the rate limits).

1 Like

Take a look at the Album Artist Website plugin I wrote - it has a caching mechanism which caches responses and groups requests to eliminate duplicates.

Also be sure to use the Picard webservice mechanism rather than making your own direct web calls in order to use the Picard rate limiter.

1 Like

Hey there, thanks for responding!

My friend got advice from you before and sent it over to me. I took a look at your plugin for the caching mechanism as I rewrote the plugin to become Object-Oriented with said caching system in place. But it does not work as intended, as the ‘track_metadata_processor’ resets itself for every track, and thus resets the caching. The problem is not with multiple artists (while I did not implement it, it would work if I did some extra work if I were to support multiple artists’ countries in the same track later down the line), but with multiple tracks. Every time the track metadata processor is called, the cache no longer exists thus the caching system does not work, sadly.

And as I stated before, the Picard webservice mechanism is not clear to me, as it is not well documented. Do you have any type of example/easy explanation how the webservice functions? Looking at your plugin, you do have:

if self.website_queue.append(artistId, (track, album)):
        host = config.setting["server_host"]
        port = config.setting["server_port"]
        path = "/ws/2/%s/%s" % ('artist', artistId)
        queryargs = {"inc": "url-rels"}
        return album.tagger.webservice.get(host, port, path,
                    partial(self.website_process, artistId),
                            parse_response_type="xml", priority=True, important=False,
                            queryargs=queryargs)

But how it is implemented confuses me. I understand the host and port, presumably referring to an internal config file that points to the database instead of doing it manually, the %s part makes sense as it fills in with 'artist' and the artistId, but I am not sure what the partial() part is for. Nor can I easily read where the obtained data is fed into.

Ok - I think I have some idea of the difficulty you are facing.

My Album Artist Website plugin is an ALBUM metadata processor - and as such when the webservice completes it then applies the metadata to all tracks in all albums associated with the request. So you will need to strip out the stuff which stores the albums that have been requested and which applies the results it to all tracks in an album and instead make it work for each track. The basic functionality should still be the same - collect the list tracks (instead of albums) which are making the same request and when the response is received apply it to all those tracks individually (instead of all the tracks in all the albums).

partial is a python thing - it creates a new function based on the self.website_process function with the first parameter(s) pre-populated with (in this case) the artistId. It is a way of passing the value of a current variable into the call-back function which will be executed later.

1 Like

The webservice.get function expects a callback as fourth parameter. The call back will be run once the request has completed, and it is expected to have this signature:

def my callback(response, reply, error):
    ...

response contains the body, reply is an instance of QNetworkReply and error is set if the request caused any kind of error.

Now in the album artist website plugin the callback looks like this:

def website_process(artistId, response, reply, error):

This method expects an additional artist is parameter. Using functools.partial is a way to get a function which has parameters already prefilled.

mycallback = partial(website_process, artistid)

Now mycallback is callable with only the three expected parameters.

1 Like

Thanks Philipp - that was a much better explanation of partial() than mine. :slight_smile: