diff --git a/beetsplug/advancedrewrite.py b/beetsplug/advancedrewrite.py new file mode 100644 index 000000000..7844b8364 --- /dev/null +++ b/beetsplug/advancedrewrite.py @@ -0,0 +1,82 @@ +# This file is part of beets. +# Copyright 2023, Max Rumpf. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +"""Plugin to rewrite fields based on a given query.""" + +from collections import defaultdict +import shlex + +import confuse +from beets import ui +from beets.dbcore import AndQuery, query_from_strings +from beets.library import Item, Album +from beets.plugins import BeetsPlugin + + +def rewriter(field, rules): + """Template field function factory. + + Create a template field function that rewrites the given field + with the given rewriting rules. + ``rules`` must be a list of (query, replacement) pairs. + """ + def fieldfunc(item): + value = item._values_fixed[field] + for query, replacement in rules: + if query.match(item): + # Rewrite activated. + return replacement + # Not activated; return original value. + return value + + return fieldfunc + + +class AdvancedRewritePlugin(BeetsPlugin): + """Plugin to rewrite fields based on a given query.""" + + def __init__(self): + """Parse configuration and register template fields for rewriting.""" + super().__init__() + + template = confuse.Sequence({ + 'match': str, + 'field': str, + 'replacement': str, + }) + + # Gather all the rewrite rules for each field. + rules = defaultdict(list) + for rule in self.config.get(template): + query = query_from_strings(AndQuery, Item, prefixes={}, + query_parts=shlex.split(rule['match'])) + fieldname = rule['field'] + replacement = rule['replacement'] + if fieldname not in Item._fields: + raise ui.UserError( + "invalid field name (%s) in rewriter" % fieldname) + self._log.debug('adding template field {0} → {1}', + fieldname, replacement) + rules[fieldname].append((query, replacement)) + if fieldname == 'artist': + # Special case for the artist field: apply the same + # rewrite for "albumartist" as well. + rules['albumartist'].append((query, replacement)) + + # Replace each template field with the new rewriter function. + for fieldname, fieldrules in rules.items(): + getter = rewriter(fieldname, fieldrules) + self.template_fields[fieldname] = getter + if fieldname in Album._fields: + self.album_template_fields[fieldname] = getter diff --git a/docs/changelog.rst b/docs/changelog.rst index ccd318e9e..52534ea25 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -137,6 +137,8 @@ New features: * :doc:`/plugins/fetchart`: Fix the error with CoverArtArchive where no cover would be found when the `maxwidth` option matches a pre-sized thumbnail size, but no thumbnail is provided by CAA. We now fallback to the raw image. +* :doc:`/plugins/advancedrewrite`: Add an advanced version of the `rewrite` + plugin which allows to replace fields based on a given library query. Bug fixes: diff --git a/docs/plugins/advancedrewrite.rst b/docs/plugins/advancedrewrite.rst new file mode 100644 index 000000000..8ac0e277e --- /dev/null +++ b/docs/plugins/advancedrewrite.rst @@ -0,0 +1,39 @@ +Advanced Rewrite Plugin +======================= + +The ``advancedrewrite`` plugin lets you easily substitute values +in your templates and path formats, similarly to the :doc:`/plugins/rewrite`. +Please make sure to read the documentation of that plugin first. + +The *advanced* rewrite plugin doesn't match the rewritten field itself, +but instead checks if the given item matches a :doc:`query `. +Only then, the field is replaced with the given value. + +To use advanced field rewriting, first enable the ``advancedrewrite`` plugin +(see :ref:`using-plugins`). +Then, make a ``advancedrewrite:`` section in your config file to contain +your rewrite rules. + +In contrast to the normal ``rewrite`` plugin, you need to provide a list +of replacement rule objects, each consisting of a query, a field name, +and the replacement value. + +For example, to credit all songs of ODD EYE CIRCLE before 2023 +to their original group name, you can use the following rule:: + + advancedrewrite: + - match: "mb_artistid:dec0f331-cb08-4c8e-9c9f-aeb1f0f6d88c year:..2022" + field: artist + replacement: "이달의 소녀 오드아이써클" + +As a convenience, the plugin applies patterns for the ``artist`` field to the +``albumartist`` field as well. (Otherwise, you would probably want to duplicate +every rule for ``artist`` and ``albumartist``.) + +A word of warning: This plugin theoretically only applies to templates and path +formats; it initially does not modify files' metadata tags or the values +tracked by beets' library database, but since it *rewrites all field lookups*, +it modifies the file's metadata anyway. See comments in issue :bug:`2786`. + +As an alternative to this plugin the simpler :doc:`/plugins/rewrite` or +similar :doc:`/plugins/substitute` can be used. diff --git a/docs/plugins/index.rst b/docs/plugins/index.rst index b56c50225..b38a298a4 100644 --- a/docs/plugins/index.rst +++ b/docs/plugins/index.rst @@ -61,6 +61,7 @@ following to your configuration:: absubmit acousticbrainz + advancedrewrite albumtypes aura autobpm @@ -246,6 +247,9 @@ Path Formats :doc:`rewrite ` Substitute values in path formats. +:doc:`advancedrewrite ` + Substitute field values for items matching a query. + :doc:`substitute ` As an alternative to :doc:`rewrite `, use this plugin. The main difference between them is that this plugin never modifies the files