diff --git a/beets/ui/commands/modify.py b/beets/ui/commands/modify.py index 0dd54d550c..db0c5a2da2 100644 --- a/beets/ui/commands/modify.py +++ b/beets/ui/commands/modify.py @@ -29,12 +29,17 @@ def modify_items(lib, mods, dels, query, write, move, album, confirm, inherit): templates = { key: functemplate.template(value) for key, value in mods.items() } - for obj in objs: - obj_mods = { + + def parse_mods(obj): + # Parse the raw assignment strings into properly typed values for the + # given object (e.g. dates into timestamps). + return { key: model_cls._parse(key, obj.evaluate_template(templates[key])) for key in mods.keys() } - if print_and_modify(obj, obj_mods, dels) and obj not in changed: + + for obj in objs: + if print_and_modify(obj, parse_mods(obj), dels) and obj not in changed: changed.append(obj) # Still something to do? @@ -56,7 +61,7 @@ def modify_items(lib, mods, dels, query, write, move, album, confirm, inherit): changed = ui.input_select_objects( f"Really modify{extra}", changed, - lambda o: print_and_modify(o, mods, dels), + lambda o: print_and_modify(o, parse_mods(o), dels), ) # Apply changes to database and files diff --git a/docs/changelog.rst b/docs/changelog.rst index 2609ce8fa9..774696d536 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -34,6 +34,9 @@ Bug fixes example a multi-disc album whose cover lives in the album root rather than a per-disc directory); the missing art is skipped instead. :bug:`4692` - :doc:`plugins/tidal`: Normalize Tidal album types to lowercase. +- ``modify``: Fix a crash when choosing ``select`` at the confirmation prompt + while modifying a non-string field such as ``added``; the per-object value is + now parsed before being applied. :bug:`4880` .. For plugin developers diff --git a/test/ui/commands/test_modify.py b/test/ui/commands/test_modify.py index 2c17120ca1..71870a6b57 100644 --- a/test/ui/commands/test_modify.py +++ b/test/ui/commands/test_modify.py @@ -108,6 +108,14 @@ def test_selective_modify(self): assert len(list(original_items)) == 3 assert len(list(new_items)) == 7 + def test_selective_modify_typed_field(self): + # Regression test for a crash when selecting individual objects to + # modify a non-string (here date) field: the confirmation callback + # used to re-apply the raw string instead of the parsed value. + self.modify_inp(["s", "y"], "added=2002-06-03 00:00:00") + item = self.lib.items().get() + assert item.added == pytest.approx(1023062400, abs=24 * 60 * 60) + def test_modify_formatted(self): for i in range(3): self.add_item_fixture(