From 0a4c8e495299e865c4f32145cbb6269ed0fd4a71 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Sun, 1 Oct 2023 00:08:27 +0200 Subject: [PATCH 1/4] Add advanced rewrite plugin This plugin allows rewriting fields based on a given library query. This can be helpful, for example, when an artist was renamed but you'd like to keep their older releases under their old name, or if you have a single track from a Various Artists release and want to have it included with the original artist. --- beetsplug/advancedrewrite.py | 77 ++++++++++++++++++++++++++++++++ docs/plugins/advancedrewrite.rst | 39 ++++++++++++++++ docs/plugins/index.rst | 4 ++ 3 files changed, 120 insertions(+) create mode 100644 beetsplug/advancedrewrite.py create mode 100644 docs/plugins/advancedrewrite.rst diff --git a/beetsplug/advancedrewrite.py b/beetsplug/advancedrewrite.py new file mode 100644 index 000000000..060d223f2 --- /dev/null +++ b/beetsplug/advancedrewrite.py @@ -0,0 +1,77 @@ +# 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. + +"""Uses user-specified rewriting rules to replace the value of specific fields +in templates and path formats. +""" + +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): + """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): + def __init__(self): + 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/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 From f809e241efb8400b0b64e90195e1857ab4645b58 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Mon, 2 Oct 2023 23:42:01 +0200 Subject: [PATCH 2/4] Fix linter warnings --- beetsplug/advancedrewrite.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/beetsplug/advancedrewrite.py b/beetsplug/advancedrewrite.py index 060d223f2..f52bc7181 100644 --- a/beetsplug/advancedrewrite.py +++ b/beetsplug/advancedrewrite.py @@ -12,10 +12,6 @@ # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -"""Uses user-specified rewriting rules to replace the value of specific fields -in templates and path formats. -""" - from collections import defaultdict import shlex @@ -27,11 +23,13 @@ from beets.plugins import BeetsPlugin def rewriter(field, rules): - """Create a template field function that rewrites the given field with the - given rewriting rules. ``rules`` must be a list of (query, replacement) - pairs. """ + 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: @@ -45,6 +43,9 @@ def rewriter(field, rules): class AdvancedRewritePlugin(BeetsPlugin): + """ + Plugin to rewrite fields based on a given query. + """ def __init__(self): super().__init__() @@ -57,12 +58,14 @@ class AdvancedRewritePlugin(BeetsPlugin): # 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'])) + 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) + 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 From 7207699b1a9923a2969a1b68d473ec2aeeda4343 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Wed, 4 Oct 2023 13:47:15 +0200 Subject: [PATCH 3/4] Add advancedrewrite plugin to changelog --- docs/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) 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: From 9660dd613061c2201240236f369e3591cddde0f3 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Sat, 14 Oct 2023 14:59:31 +0200 Subject: [PATCH 4/4] Fix remaining linter warnings --- beetsplug/advancedrewrite.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/beetsplug/advancedrewrite.py b/beetsplug/advancedrewrite.py index f52bc7181..7844b8364 100644 --- a/beetsplug/advancedrewrite.py +++ b/beetsplug/advancedrewrite.py @@ -12,6 +12,8 @@ # 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 @@ -23,8 +25,7 @@ from beets.plugins import BeetsPlugin def rewriter(field, rules): - """ - Template field function factory. + """Template field function factory. Create a template field function that rewrites the given field with the given rewriting rules. @@ -43,10 +44,10 @@ def rewriter(field, rules): class AdvancedRewritePlugin(BeetsPlugin): - """ - Plugin to rewrite fields based on a given query. - """ + """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({ @@ -63,7 +64,8 @@ class AdvancedRewritePlugin(BeetsPlugin): fieldname = rule['field'] replacement = rule['replacement'] if fieldname not in Item._fields: - raise ui.UserError("invalid field name (%s) in rewriter" % fieldname) + 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))