Dynamically preserve fields based on tag

Sometimes I manually fix some fields that I don’t want as appear in MB database. Later when I refresh those releases to update other tags, manually fixed fields are reverted. I want to remember those fields as fixed for the future, but still allow other fields to be updated from MB (for example prevent changing albumartist but allow changing everything else on particular album).

In options, we have preserve tags, but it is static and works globally, not per release. I tried making a plugin that consults preserve_tags tag if it exists on track - for example, if I havepreserve_tags: album albumartist, it should not allow changes to those fields but allow change on other fields.

So, do you think that this could be done as plugin, and if not, does it make sense to add it among Tag Picard options. Both “global” and “local” options to preserve make sense IMO - one can not change all MB “problems” it encounters as others may vote against it, or it is simply a preference.

1 Like

I think this would fit well as a plugin. A plugin could allow a special custom tab to store a list of tag names to preserve. The plugin should a hook when files get matched, where it checks for this special tag and sets all tags from that list to the original value from the file.

OK, finally with the help of AI made plugin that seems to work:

# -*- coding: utf-8 -*-
PLUGIN_NAME = "Dynamic Preserve Tags"
PLUGIN_AUTHOR = "majkinetor"
PLUGIN_DESCRIPTION = """
Reads a `preserve_tags` tag from your file (space- or comma-separated list)
and restores those tags from the original file AFTER MusicBrainz tagging.

Example — add this tag to your audio file:
  preserve_tags = album albumartist albumartistsort

Those tags will be kept from your original file and not overwritten by Picard.
The `preserve_tags` tag itself is also always preserved.
"""
PLUGIN_VERSION = "1.5"
PLUGIN_API_VERSIONS = ["2.2", "2.3", "2.4", "2.5", "2.6", "2.7", "2.8", "2.9"]
PLUGIN_LICENSE = "GPL-2.0-or-later"
PLUGIN_LICENSE_URL = "https://www.gnu.org/licenses/gpl-2.0.html"

from picard import log
from picard.file import register_file_post_addition_to_track_processor

PRESERVE_TAG_FIELD = "preserve_tags"


def parse_preserve_tags(value):
    log.debug("[PreserveTags] parse_preserve_tags: enter, value=%r", value)
    if not value:
        log.debug("[PreserveTags] parse_preserve_tags: exit, empty value -> []")
        return []
    result = [p.strip().lower() for p in value.replace(",", " ").split() if p.strip()]
    log.debug("[PreserveTags] parse_preserve_tags: exit, result=%s", result)
    return result


def preserve_tags_processor(track, file):
    """
    Called after a file is matched to a track and file.metadata already
    contains the merged MusicBrainz data. We overwrite selected tags
    with the original on-disk values from file.orig_metadata.
    """
    log.debug("[PreserveTags] preserve_tags_processor: enter, file=%s track=%s", file.filename, track)

    orig = file.orig_metadata

    preserve_raw = orig.get(PRESERVE_TAG_FIELD, "")
    tags_to_preserve = parse_preserve_tags(preserve_raw)

    if not tags_to_preserve:
        log.debug(
            "[PreserveTags] preserve_tags_processor: exit, no '%s' tag found in %s, skipping",
            PRESERVE_TAG_FIELD, file.filename,
        )
        return

    # Always keep the preserve_tags field itself
    tags_to_preserve.append(PRESERVE_TAG_FIELD)
    log.debug("[PreserveTags] preserve_tags_processor: tags to preserve (incl. self): %s", tags_to_preserve)

    restored = []
    for tag in tags_to_preserve:
        values = orig.getall(tag)
        if values:
            file.metadata.delete(tag)
            for v in values:
                file.metadata.add(tag, v)
            restored.append(tag)
            log.debug("[PreserveTags] preserve_tags_processor: restored '%s' = %s", tag, values)
        else:
            log.debug(
                "[PreserveTags] preserve_tags_processor: tag '%s' not found in orig_metadata, skipping",
                tag,
            )

    log.debug(
        "[PreserveTags] preserve_tags_processor: exit, file=%s restored=%s",
        file.filename, restored,
    )


register_file_post_addition_to_track_processor(preserve_tags_processor)
2 Likes