diff --git a/beets/ui/commands/modify.py b/beets/ui/commands/modify.py index 18ea099f58..15dbbc581c 100644 --- a/beets/ui/commands/modify.py +++ b/beets/ui/commands/modify.py @@ -72,14 +72,20 @@ def modify_items(lib, mods, dels, query, write, move, album, confirm, inherit): templates = {} for key, mod in mods.items(): templates[key] = functemplate.template(mod.value) - for obj in objs: + + def parse_mods(obj): + # Parse the raw assignment strings into properly typed values for the + # given object (e.g. dates into timestamps) and apply each mod. obj_mods = {} for key, mod in mods.items(): parsed_value = model_cls._parse( key, obj.evaluate_template(templates[key]) ) obj_mods[key] = mod.apply(obj, key, parsed_value) - if print_and_modify(obj, obj_mods, dels) and obj not in changed: + return obj_mods + + 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? @@ -101,7 +107,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 c99f7b295d..d99b6f9901 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -64,6 +64,9 @@ Bug fixes longer crashes with a ``TypeError``. Missing values are now grouped together, ordered before present ones when sorting ascending and after them when descending. :bug:`3461` +- ``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 a74f08a44a..bbd9c167de 100644 --- a/test/ui/commands/test_modify.py +++ b/test/ui/commands/test_modify.py @@ -111,6 +111,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(