Plugins: How to access original Metadata?

I want to write a plugin where I need the original metadata values from the local files.

Since there is not much documentation for the API (or I don’t find it), I haven’t found a way to access the original metadata.
Based on https://picard.musicbrainz.org/docs/plugin-api/#metadata_processors I wrote already a plugin (without the need to access the original metadata) and it works fine.

For my new plugin I need to read the original metadata.
With the passed arguments (https://picard.musicbrainz.org/docs/plugin-api/#metadata_processors) I only have the new metadata, from the MusicBrainz server (as far as I know and based on my poor Python knowledge).

Pls., give me a hint.

1 Like

Hello, @NeroA, welcome to MusicBrainz! Thank you for your interest. I hope you will get helpful answers here.

I take it you mean, you want to write a plugin for the Picard music file tagging application. There are many things in the MusicBrainz world, and so I want to be sure which of those things you are interested in.

Here’s a rather basic question: is Picard the right foundation for your software? Picard is a tool for tagging music files with MusicBrainz-derived metadata. It sounds like you want to read metadata out of music files. That is different than Picard’s purpose, so Picard might not be convenient for you.

Maybe consider writing a Python program which uses metadata-reading modules to get at the existing metadata. Mutagen is a pretty well-regarded Python module for exactly that purpose. If I’m not mistaken, Picard uses mutagen itself.

Good luck with your development!

3 Likes

If you want to access the original metadata first you need access to the files, than you can access File.orig_metadata. Since Picard 2.2 you can use the file_post_addition_to_track_processor event hook, which gets triggered when a file gets attached to a track.

Have a look at this. Right now I can only give you a quick pointer, but if you have further questions or need more details I can answer you in more depth tomorrow.

Before Picard 2.2 accessing the original file metadata in a plugin is difficult and not really supported. I have seen plugins accessing the file objects in the album metadata processor, but those are only available under specific circumstances and might also change later.

4 Likes

Hello @Jim_DeLaHunt!
Thanks for your replay.

Yes, of course!
Since thit topic is in category MusicBrainz Picard and the links are to Picards Plugins API, it shoul be clear.

I know. I use musicbrainz.org and Picard for a while and I know how it works.
However, for special cases I don’t want to use just the data from musicbrainz.org but I want to modify them a little bit for my needs. (It’s about dealing with missing files where I use dummy-files … )

Thx!
This seems to be a good starting point. I saw it already.
Currently I don’t know the event hierarchy in Picard.
Since I plan to use the track_metadata_processor (as I saw it in the example), I’m not sure if and how I can pass original metadata from the file_post_addition_to_track_processor to track_metadata_processor.

Maybe I should explain what I want to do: In my music library I have several albums with one or more missing files. I want to have the full number of files/tracks for my albums and so I use dummy files (silent files with 1 sec.).
For these dummy files I want to add “[MISSING]” to the track-title (after I get the data from the MusicBrainz server) fields. Therefore I need to know, if my local file is just a dummy file.

I don’t think you need the track_metadata_processort for this. What you want to do is perfectly suited for the file_post_addition_to_track_processor. Something like this should do the trick:

from picard.file import (
    register_file_post_addition_to_track_processor,
    register_file_post_removal_from_track_processor,
)


def is_dummy_file(file):
    # Do whatever you need to do to detect the dummy file and return True if it is a dummy file.
    # Some things you might need:
    # file.metadata => The file metadata as it will be saved
    # file.orig_metadata => The original file metadata as it was loaded
    # file.filename => The full path to the file
    return False


def file_post_addition_to_track_processor(track, file):
    if is_dummy_file(file):
        title = "[MISSING] %s" % track.metadata['title']
        # Set the title for both the track and file. This ensures
        # it will both be displayed and saved correctly.
        track.metadata['title'] = title
        file.metadata['title'] = title


def file_post_removal_from_track_processor(track, file):
    # You probably also want to remove the [MISSING] part again if a file gets removed from a track
    if track.metadata['title'].startswith('[MISSING] '):
        track.metadata['title'] = track.metadata['title'][10:]
        # We don't need to update the file because it gets reset to its original metadata anyway.


register_file_post_addition_to_track_processor(file_post_addition_to_track_processor)
register_file_post_removal_from_track_processor(file_post_removal_from_track_processor)

The above code is totally untested, I just wrote this down here. But it should give you an idea. What the code does not consider is that a track might have multiple files assigned, this would complicate things a bit. But if you need this you can access all linked files for a track with track.linked_files.

4 Likes

@outsidecontext, THX very much!

If I can do anything in file_post_addition_to_track_processor it’s just fine!
I thought, I don’t have the new metadata (from musicbrainz.org) already here but if they are already available, it’s quite easy.

I’ll try it later.

It works (more or less) well for me!

I could do it all in file_post_addition_to_track_processor but then I get unmatched entries (without green check-mark).
Therefore I do some tricky rename of the title (remove existing [MISSING] in file_post_load_processor and add it again in file_post_addition_to_track_processor). With this trick the internal comparison works well also for files already tagged with [MISSING].
Without changing track.metadata it might be possible to do all in one hook, but I don’t like this.
It seems to fit my need for my basic workflow, however I will get just “great matches” after refresh data from the server.
I need to do more tests and there are still ways to improve the plugin. Anyway, it looks good and helps me a lot.

PLUGIN_NAME = "[MISSING]"
PLUGIN_AUTHOR = "Nero A."
PLUGIN_DESCRIPTION = "Add [MISSING] to dummy tracks"
PLUGIN_VERSION = '0.3'
PLUGIN_API_VERSIONS = ['2.2']
PLUGIN_LICENSE = "GPL-3.0-or-later"
PLUGIN_LICENSE_URL = "https://gnu.org/licenses/gpl.html"


from picard.file import register_file_post_load_processor
from picard.file import register_file_post_addition_to_track_processor
from picard.file import register_file_post_removal_from_track_processor


MISSING = " [MISSING]"
DUMMY_TAG_ORIG_TITLE = "__PLUGIN_[MISSING]_ORIGINAL_TITLE"


def is_dummy_file(file):
    if file.orig_metadata["~length"] == "0:01":
        return True

def is_modified_dummy_file(file):
    if file.orig_metadata[DUMMY_TAG_ORIG_TITLE] != "":
        return True

def remove_missing(title):
    if title.endswith(MISSING):
        return title[:-len(MISSING)]
    return title

def add_missing(title):
    if title.endswith(MISSING):
        return title
    return (title + MISSING)



def file_post_load_processor(file):
    if is_dummy_file(file):
        if file.orig_metadata["title"].endswith(MISSING):
            file.orig_metadata[DUMMY_TAG_ORIG_TITLE] = file.orig_metadata["title"]
            file.orig_metadata["title"] = remove_missing(file.orig_metadata["title"])


def file_post_addition_to_track_processor(track, file):
    if is_dummy_file(file):
        track.metadata["title"] = add_missing(track.metadata["title"])
        
        if is_modified_dummy_file(file):
            file.orig_metadata["title"] = file.orig_metadata[DUMMY_TAG_ORIG_TITLE]
            file.orig_metadata[DUMMY_TAG_ORIG_TITLE] = ""
            
        file.metadata["title"] = add_missing(file.metadata["title"])


def file_post_removal_from_track_processor(track, file):
    if is_dummy_file(file):
        track.metadata["title"] = remove_missing(track.metadata["title"])


register_file_post_load_processor(file_post_load_processor)
register_file_post_addition_to_track_processor(file_post_addition_to_track_processor)
register_file_post_removal_from_track_processor(file_post_removal_from_track_processor)
3 Likes