Trying to add one of the two "music video for" from a Music Video entry via script

Hello,
I am attempting to add Tags to a MP4 Video file so a program (actually a combination of Jellyfin/ErsatzTV) Can display Music video credits, like this. To test this I have been using this entry for the AJR music video Bang!

Picard actually does most of this on its own, the only info that isn’t added is the album which is listed as “[standalone recordings]”

However the Album the Video originally came out for IS listed on the musicbrainz entry for the video. It is listed under “music video for” which itself is listed under “Relationships”.

I was wondering if their was a way to use a script to add that info from the site to the album tag.

The two problems I can see are:
1: I’m not sure if theirs a script function that can do that.
2: Their are actually 2 entry’s in the “music video for” catagory. The album, and the entry for the song itself.

Thanks for any help.

Picard currently can’t follow this relationship to fetch another recording and then apply the data from it.

A Picard Python plugin could do it. But it’s actually quite a few steps it will take to get the data. It would need to

  • check for the “music video for” relationship on the reording
  • fetch the linked recording(s)
  • for each recording fetch the releases this recording appears on
  • apply some filtering to select the best fitting release
  • somehow apply the data to the original recording.

The last step would depend on what should actually be taken from the release or the audio recording. But at a minimum it could set the album title.

3 Likes

Thank you, Sorry for not replying I thought I’d get like an email notification.

I might look into it. Unfortunately I can’t think of any other service to add meta data to music videos, The music video database’s lights are on but No one is home.

So I’m might actually look into that python method.

Are their any examples of it that you know of? If not that’s cool, I’ll go hunting for them either way.

EDIT: OH wait, When you say python script do you mean the built in script stuff? I didn’t realize that was python (Self taught coder >_>). In that case I’ll just scour the API I guess. Hopefully I find the right calls.

EDIT 2: Never mind, I dunno exactly what where talking about, but Imma follow this tutorial: Writing a Plugin — MusicBrainz Picard v2.9.1 documentation

1 Like

You’re on the right track :slight_smile: What you’ll need is a track metadata processor.

There are several plugins that you could use as an example.

I’m not sure if there is a good example that is similar to what you would need to do. It’s a bit more involved then most of the plugins.

The web request part is probably what is the least clear. Picard provides the webservice instance, but for the use case of querying the MB API you can use the more convenient MBAPIHelper wrapper. Below is a very rough idea on how this plugin could be structured to do that API call:

from functools import partial
from picard.metadata import register_track_metadata_processor
from picard.webservice.api_helpers import MBAPIHelper

def recording_request_finished(self, metadata, recording, http, error):
  # Callback called after the get_track_by_id webservice request finished
  # recording holds the data from the JSON response. Read album information from there
  # and fill it into metadata

def load_video_album_details(album, metadata, track, release):
    # 1. Search for the music video rel in track['relations']
    # 2. Do a request for a found recording MBID to get info about that recordings album
    api_helper = MBAPIHelper(album.tagger.webservice)
    api_helper.get_track_by_id(recording_mbid, partial(recording_request_finished, metadata))

register_track_metadata_processor(load_video_album_details)


I hope the above can get you started :smiley:
5 Likes

OK! Had some real life stuff happen. Back at it for a second.

On thing I wanted to clarify, is that I thought you could link albums to Videos in relations, you cannot. outsidecontext was saying this the whole time, and it went straight over my head.
(You actually CAN link an album to a video but only under the heading of “samples from / sampled by” in the release relations. Though it’s not what that’s for and I think using that relationship incorrectly is a big no no.)

Thusly I guess I could ask if linking Music videos to original release albums is possible. But since it’s such an edge use case, It seems unlikely that it would get far, especially since it’s just my silly idea and their a probably better additions people are campaigning for.

That being said, it should still be possible to make a python script that gets the PROBABLY correct album (I say probably because the operations I came up with are based on a lot of assumptions, which 9/10 times should be right.)

But I hit a snag again. I still haven’t figured out how the API works yet, so I wrote out the operations as text to see where I was going with this.

#if media is video
#   R = Relationship array
#   if R =! null
#       ID = music video for
#       rA = ID.Release array
#       if rA =! null
#           for i=0; i < Ra.entries; i++
#               rA[i]

In that for loop its supposed to take all the albums linked in the release array, filter out every type that isn’t “Album” (So for Bang! no “Singles” and no “Album + Compilation”)
Then it takes the remaining albums and sorts them by release date. Whichever one has the earliest release date is used, if theirs a tie whichever one of those that has the lowest position in the Array is used.

So Like I said, very inaccurate but SHOULD work most of the time?

The wall I’ve run into is I can’t seem to find the type for the albums. The best I’ve got is “type-id” which could be it?

I’m going to keep trying but I figured this topic could use an update with everything I’ve learned in case other people want to try to do this but better.

Thanks again for all the help.

Not sure if I should make a new thread or reply here? I don’t mean to stop working on these things for so long but I don’t have a huge amount of control over it…

Either way, I finally got back to working on this and actually got a small grip on the API. I added the Director reference to the Director tag.

From their I’ve been trying to use the “MBAPIHelper” to get references and metadata from other entries on the site.

TLDR Here’s my problem: when I run my code with the MBAPIHelper, picard just shuts down. I tried doing the debug mode from the command prompt thing. But not only does it not show an error, It shows the code working for the most part.

This is the last thing that shows up in the CMD window:

D: 07:33:27,914 webservice._handle_reply:571: Response received: {'video': False, 'first-release-date': '2020-02-12', 'disambiguation': '', 'length': 170000, 'id': '1d5c804e-3a68-4439-87da-8124bb266205', 'title': 'Bang!'}

And these are the snippets of code it’s executing:

def setDirector(album, metadata, track, release):
    metadata["director"] = metadata["director"]
    r = track['relations']
    for x in range(len(r)):
        if r[x]['target-type'] == 'recording':
            vID = r[x]['recording']['id']
            break
            
    api_helper = MBAPIHelper(album.tagger.webservice)
    handler = partial(recording_request_finished, metadata)
    api_helper.get_track_by_id(vID, handler)

def recording_request_finished(self, metadata, recording, http, error):
    print('berf')

The code basically does what it’s supposed to, aside from printing a nonsense word, and then the whole program shuts off.

I’m honestly not sure where to go form here.

Unless this function is part of a class the self might be what is tripping things up (as it would cause the function to have one parameter too much). Try removing the self. I still would expect some exception of invalid function parameters, but not sure.

Otherwise I don’t see anything suspicious right now. If the above does not work, please share the full script and I will try to debug myself.

2 Likes

Unless this function is part of a class the self might be what is tripping things up (as it would cause the function to have one parameter too much). Try removing the self.

That was it, that was the whole problem XD, thank you!

1 Like

OK! It’s probably not the best written or smartest or best working. But it IS done.

I was unable to figure out how to make the plugin only try to rewrite music video files, so it has to be turned off when not in use. I’ll fix that if I learn how but I think I’m done with the plugin for now.

If anyone wants to use it for something, or just make a better one, by all means do that.

So Imma throw the plugin up here and call it a day.

PLUGIN_NAME = "Music Video Plugin"
PLUGIN_AUTHOR = "Ben"
PLUGIN_DESCRIPTION = """
This is a plugin made to get Music video Metadata from the Music Brainz site.

I wrote a check so that it will only attempt to alter entries with "video = true" but that didn't work at all for some reason so I would turn this off when editing non music videos

It's designed to look for the information you would have in Music Video Credits: Band name, song title, album name, record label, director.  (It's based on this image https://s.studiobinder.com/wp-content/uploads/2018/02/The-Essential-Music-Video-Credits-Format-Guide-MTV-Chyron-Look-StudioBinder-Production-Management-Software.jpg).
The labeling style used is based on the Ersatz TV program.  To be frank, this plugin is made to get Music Video Data from the Music Brainz site so that it can be used for Ersatz TV.
For people with different use cases or who want to create a standardized Music video labeling parlance, this plugin will probably fall short But, it can be customized. I encourage people to build on and improve this plugin if they want.

The way it works is that the plugin gets the Director, Song name, label, and band from the Music video page.  The plugin then tries to get the album through some logical deductions.  First, it goes to the page of the song the music video was made for.  Then it goes through all the albums the song was featured on one by one, removing any albums that aren't the ''Album Type'' (So no Singles or Compilations).  It then checks accepted entries to see if they have the same record label attached to them.  If that fails it goes through the albums again, this time without filtering out Singles or Compilations, looking for one made by the same record label.  If that fails it simply takes the album title of the first listed album.
"""
PLUGIN_VERSION = '1.0'
PLUGIN_API_VERSIONS = ['2.2']
PLUGIN_LICENSE = "GPL-2.0-or-later"
PLUGIN_LICENSE_URL = "https://www.gnu.org/licenses/gpl-2.0.html"

import json
from picard.metadata import register_track_metadata_processor
from functools import partial
from picard.webservice.api_helpers import MBAPIHelper

api_helper = None
labelTrue = 0

def startSearch(album, metadata, track, release):
    #this didn't work at all and I don't know what else to do.
    if track['video']:
        global api_helper
        api_helper = MBAPIHelper(album.tagger.webservice)
        labelhandler = partial(checklabel, metadata)
        handler = partial(checksong, metadata)
        
        #this seemed nessicary but maybe isn't
        #metadata["director"] = metadata["director"]

        api_helper.get_track_by_id(track['id'], labelhandler, inc = ['label-rels'])
                
        r = track['relations']
        for x in range(len(r)):
            if r[x]['target-type'] == 'recording' and r[x]['direction'] == 'backward' and r[x]['type'] == 'music video':
                vID = r[x]['recording']['id']
                break
        
        if vID != None:
            api_helper.get_track_by_id(vID, handler, inc = ['releases', 'release-groups'])

def checklabel(metadata, release, http, error):
    for y in range(len(release['relations'])):
        if release['relations'][y]['type'] == 'produced for':
            metadata["label"] = release['relations'][y]['label']['name']
            metadata["studio"] = release['relations'][y]['label']['name']
            break            

def checksong(metadata, recording, http, error):
    albumhandler = partial(checkalbum, metadata)
    noAlbum = 0
    for x in range(len(recording['releases'])):
        albumOK = 1
        if recording['releases'][x]['release-group']['primary-type'] == 'Album':
            for y in range(len(recording['releases'][x]['release-group']['secondary-types'])):
                if recording['releases'][x]['release-group']['secondary-types'][y] == 'Compilation':
                    albumOK = 0
                    break
            
            if albumOK == 1:
                aID = recording['releases'][x]['id']
                api_helper.get_release_by_id(aID, albumhandler, inc = ['labels'])
                if labelTrue == 1:
                    break
            
        if x == range(len(recording['releases'])) and recording['releases'][x]['release-group']['primary-type'] != 'Album':
            noAlbum = 1
            
    if noAlbum == 1:
        noAlbum = 0
        for x2 in range(len(recording['releases'])):
            aID = recording['releases'][x]['id']
            api_helper.get_release_by_id(aID, albumhandler, inc = ['labels'])
            if labelTrue == 1:
                break
            
            if x == range(len(recording['releases'])):
                metadata['album'] = recording['releases'][x]['title']

def checkalbum(metadata, release, http, error):
    global labelTrue
    for x in range(len(release['label-info'])):
        if metadata['label'] == release['label-info'][x]['label']['name']:
            metadata['album'] = release['title']
            labelTrue = 1
            break
        if x == len(release['label-info']) and metadata['label'] != release['label-info'][x]['label']['name']:
            labelTrue = 0
            
register_track_metadata_processor(startSearch)
3 Likes

If you are still struggling with this

Maybe you could separate videos from songs by their extension; a while back I had a slightly complicated solution for something similar.

Since you can’t access file variables from metadata processors, I had a file post load processor save into a dictionary the information I needed using the titles as the keys. Then in the metadata processor you can access the dictionary and pop the value you used.

Thank you again! I actually think I figured it out.

I just wrote the statement wrong O_O. I’m not the best at this…

But this new statement appears to work!

video = track.get('video')
if video and video == True:

The first check is to see if it exists at all, the second checks if its true, that guards it from crashing :smiley:

Though what you were saying might help add a way to auto get the music videos pages from picard. I haven’t even tried to do that, I just copy the music video URL and paste it into picard.

OK, I updated the script to version 1.1.

The old one would just crash and not work (my bad, I’m not the best at this), so I fixed that.

Also I rewrote it so instead of trying to match labels on the music video page (Not something all music videos have) it just picks the earliest album release. The script still tries to go for albums first, then other kinds like singles. (theirs also a clause to ignore anything not official and if that fails, it just chooses the first album in the list)

Their are some errors.

Like how the script saves the label to “Studio” (it also saves the label to label) it does this since it was written for Ersatz TV, and I’m not sure which should be the proper term. Either way I can’t change the Ersatz TV program (though I can ask, the creator is very nice and accommodating) but I’d rather not unless I know for sure what people would use.

Also some music videos will just cause a crash with no error. like this one does for me.

But aside from that, it seems to be working pretty well. Here’s the script:

PLUGIN_NAME = "Music Video Plugin"
PLUGIN_AUTHOR = "Ben"
PLUGIN_DESCRIPTION = """
This is a plugin made to get Music video Metadata from the Music Brainz site.

I wrote a check so that it will only attempt to alter entries with "video = true" but I also don't know what I'm doing so it might be safter to turn it off when not editing music videos.

It's designed to look for the information you would have in Music Video Credits: Band name, song title, album name, record label, director.  (It's based on this image https://s.studiobinder.com/wp-content/uploads/2018/02/The-Essential-Music-Video-Credits-Format-Guide-MTV-Chyron-Look-StudioBinder-Production-Management-Software.jpg).
The labeling style used is based on the Ersatz TV program.  To be frank, this plugin is made to get Music Video Data from the Music Brainz site so that it can be used for Ersatz TV.
For people with different use cases or who want to create a standardized Music video labeling parlance, this plugin will probably fall short But, it can be customized. I encourage people to build on and improve this plugin if they want.

The way it works is that the plugin gets the Director, Song name, and band from the Music video page. It will also get Label there too, but if one isn't available it will look for one on the album page The plugin then tries to get the album through some logical deductions.  First, it goes to the page of the song the music video was made for.  Then it goes through all the albums the song was featured on one by one, removing any albums that aren't the ''Album Type'' (So no Singles or Compilations), it also filter so only [status] "Official" albums are used  It then checks the dates of accepted entries to see which was the first to release (assuming the music video was made for the first release of the song it was made for).  If that fails it goes through the albums again, this time without filtering out Singles or Compilations but, still filtering for "Official" albums.  It looks for the earliest one. I that fails it uses the first Album on the list.
"""
PLUGIN_VERSION = '1.1'
PLUGIN_API_VERSIONS = ['2.2']
PLUGIN_LICENSE = "GPL-2.0-or-later"
PLUGIN_LICENSE_URL = "https://www.gnu.org/licenses/gpl-2.0.html"

import json
from picard.metadata import register_track_metadata_processor
from functools import partial
from picard.webservice.api_helpers import MBAPIHelper

api_helper = None
hasLabel = 0

def startSearch(album, metadata, track, release):
    video = track.get('video')
    if video and video == True:
        global api_helper
        global hasLabel
        hasLabel = 0
        api_helper = MBAPIHelper(album.tagger.webservice)
        labelhandler = partial(checklabel, metadata)
        songhandler = partial(checksong, metadata)
        
        api_helper.get_track_by_id(track['id'], labelhandler, inc = ['label-rels'])
                
        r = track['relations']
        vID = None
        for x in range(len(r)):
            if r[x]['target-type'] == 'recording' and r[x]['direction'] == 'backward' and r[x]['type'] == 'music video':
                vID = r[x]['recording']['id']
                break
        
        if vID != None:
            api_helper.get_track_by_id(vID, songhandler, inc = ['releases', 'release-groups'])

def checklabel(metadata, release, http, error):
    global hasLabel
    for y in range(len(release['relations'])):
        if release['relations'][y]['target-type'] == 'label':
            metadata["label"] = release['relations'][y]['label']['name']
            metadata["studio"] = release['relations'][y]['label']['name']
            hasLabel = 1
            break            

def checksong(metadata, recording, http, error):
    albumhandler = partial(checkalbum, metadata)
    noAlbum = 0
    recordingFound = 0
    recordingValue = -1
    for x in range(len(recording['releases'])):
        albumOK = 1
        if recording['releases'][x]['release-group']['primary-type'] == 'Album' and recording['releases'][x]['status'] == 'Official':
            for y in range(len(recording['releases'][x]['release-group']['secondary-types'])):
                if recording['releases'][x]['release-group']['secondary-types'][y] == 'Compilation':
                    albumOK = 0
                    break
            
            if albumOK == 1 and recordingFound == 0:
                recordingFound = 1
                recordingValue = x
                
            if albumOK == 1 and recordingFound == 1:
                dateA = recording['releases'][recordingValue]['date']
                dateB = recording['releases'][x]['date']
                dates = [dateA, dateB]
                dates.sort
                if dateA != dates[0]:
                    recordingValue = x
                    
        if x == len(recording['releases']) -1 and recordingFound == 1:
            aID = recording['releases'][recordingValue]['id']
            api_helper.get_release_by_id(aID, albumhandler, inc = ['labels'])
            break

        if x == len(recording['releases']) - 1 and recording['releases'][x]['release-group']['primary-type'] == 'Album':
            if recording['releases'][x]['release-group']['secondary-types'][y] == 'Compilation':
                noAlbum = 1
                break
                
        if x == len(recording['releases']) - 1 and recording['releases'][x]['release-group']['primary-type'] != 'Album':
            noAlbum = 1

    if noAlbum == 1:
        for x2 in range(len(recording['releases'])):
            if recordingFound == 0 and recording['releases'][x]['status'] == 'Official':
                recordingFound = 1
                recordingValue = x2
            
            if recordingFound == 1 and recording['releases'][x]['status'] == 'Official':
                dateA = recording['releases'][recordingValue]['date']
                dateB = recording['releases'][x2]['date']
                dates = [dateA, dateB]
                dates.sort
                if dateA != dates[0]:
                    recordingValue = x2
                    
            if x == len(recording['releases']) - 1 and recordingFound == 1:
                aID = recording['releases'][recordingValue]['id']
                api_helper.get_release_by_id(aID, albumhandler, inc = ['labels'])

            if x == len(recording['releases']) - 1 and recordingFound == 0:
                aID = recording['releases'][0]['id']
                api_helper.get_release_by_id(aID, albumhandler, inc = ['labels'])
                
def checkalbum(metadata, release, http, error):
    global hasLabel
    for x in range(len(release['label-info'])):
        if hasLabel == 0 and release['label-info'][0]['label']['name'] != None:
            metadata["label"] = release['label-info'][0]['label']['name']
            metadata["studio"] = release['label-info'][0]['label']['name']
            
    metadata['album'] = release['title']

            
register_track_metadata_processor(startSearch)
2 Likes