diff --git a/beetsplug/edit.py b/beetsplug/edit.py index 05c074532..0ee6adc25 100644 --- a/beetsplug/edit.py +++ b/beetsplug/edit.py @@ -16,6 +16,7 @@ from beets import plugins, ui, util from beets.dbcore import types from beets.exceptions import UserError from beets.importer import Action +from beets.library import Album from beets.ui.commands.utils import do_query from beets.util import PromptChoice @@ -348,6 +349,21 @@ class EditPlugin(plugins.BeetsPlugin): if not album_fields: return None + # Restrict to fields that actually exist on the Album model. Item + # has every Album field plus track-only ones (title, track, path, + # ...); since the header is built from a single item and then + # applied to every item, an item-only field here would silently + # stamp that one item's value onto the whole album. + invalid_fields = album_fields - Album._field_names + if invalid_fields: + self._log.warning( + "ignoring albumfields not valid for albums: {}", + ", ".join(sorted(invalid_fields)), + ) + album_fields &= Album._field_names + if not album_fields: + return None + first_item = task.items[0] header = flatten(first_item, album_fields) return header if header else None diff --git a/test/plugins/test_edit.py b/test/plugins/test_edit.py index aedfb5598..d96735eab 100644 --- a/test/plugins/test_edit.py +++ b/test/plugins/test_edit.py @@ -455,6 +455,30 @@ class EditDuringImporterNonSingletonTest(EditDuringImporterTestCase): assert all("Edited Track" in i.title for i in self.lib.items()) + def test_importer_edit_album_header_ignores_item_only_fields(self): + """`albumfields` may be misconfigured with item-only fields (e.g. + `track`, `title`, `path`) that vary per track. Those must not end + up in the header, since the header gets applied to every item and + would otherwise stamp one track's values onto the whole album. + """ + self.prepare_album_for_import(3) + self.items_orig = [Item.from_path(f.path) for f in self.import_media] + + self.config["edit"]["itemfields"] = "track title artist album" + self.config["edit"]["albumfields"] = "album albumartist track title" + + self.run_mocked_interpreter( + {"replacements": {"Tag Album": "Modified Album"}}, + # eDit, Apply changes. + ["d", "a"], + ) + + titles = [i.title for i in self.lib.items()] + tracks = [i.track for i in self.lib.items()] + assert len(set(titles)) == len(titles) + assert len(set(tracks)) == len(tracks) + assert all(i.album == "Modified Album" for i in self.lib.items()) + def test_importer_edit_album_header_albumartist(self): """Edit albumartist in the header (default albumfields).""" self.run_mocked_interpreter(