diff --git a/beets/importer.py b/beets/importer.py index 14478b43d5..2a7508c3c8 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -584,9 +584,9 @@ def set_fields(self, lib): displayable_path(self.paths), field, value) - self.album[field] = value + self.album.set_parse(field, format(self.album, value)) for item in items: - item[field] = value + item.set_parse(field, format(item, value)) with lib.transaction(): for item in items: item.store() @@ -963,7 +963,7 @@ def set_fields(self, lib): displayable_path(self.paths), field, value) - self.item[field] = value + self.item.set_parse(field, format(self.item, value)) self.item.store() diff --git a/beets/ui/commands.py b/beets/ui/commands.py index cbdd8e3bfb..91cee45160 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -33,7 +33,7 @@ from beets import importer from beets import util from beets.util import syspath, normpath, ancestry, displayable_path, \ - MoveOperation + MoveOperation, functemplate from beets import library from beets import config from beets import logging @@ -1477,9 +1477,6 @@ def modify_items(lib, mods, dels, query, write, move, album, confirm): # Parse key=value specifications into a dictionary. model_cls = library.Album if album else library.Item - for key, value in mods.items(): - mods[key] = model_cls._parse(key, value) - # Get the items to modify. items, albums = _do_query(lib, query, album, False) objs = albums if album else items @@ -1489,8 +1486,14 @@ def modify_items(lib, mods, dels, query, write, move, album, confirm): print_('Modifying {} {}s.' .format(len(objs), 'album' if album else 'item')) changed = [] + templates = {key: functemplate.template(value) + for key, value in mods.items()} for obj in objs: - if print_and_modify(obj, mods, dels) and obj not in changed: + obj_mods = { + 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: changed.append(obj) # Still something to do? diff --git a/docs/changelog.rst b/docs/changelog.rst index 9363ee2503..1b4669faed 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -135,6 +135,11 @@ Major new features: * An accompanying new :doc:`/plugins/albumtypes` includes some options for formatting this new ``albumtypes`` field. Thanks to :user:`edgars-supe`. +* The :ref:`modify-cmd` and :ref:`import-cmd` can now use + :doc:`/reference/pathformat` formats when setting fields. + For example, you can now do ``beet modify title='$track $title'`` to put + track numbers into songs' titles. + :bug:`488` Other new things: diff --git a/docs/reference/cli.rst b/docs/reference/cli.rst index 3726fb3732..da119d0f81 100644 --- a/docs/reference/cli.rst +++ b/docs/reference/cli.rst @@ -147,6 +147,8 @@ Optional command flags: Multiple IDs can be specified by simply repeating the option several times. * You can supply ``--set field=value`` to assign `field` to `value` on import. + Values support the same template syntax as beets' + :doc:`path formats `. These assignments will merge with (and possibly override) the :ref:`set_fields` configuration dictionary. You can use the option multiple times on the command line, like so:: @@ -265,6 +267,13 @@ artist="Tom Tom Club"`` will change the artist for the track "Genius of Love." To remove fields (which is only possible for flexible attributes), follow a field name with an exclamation point: ``field!``. +Values can also be *templates*, using the same syntax as +:doc:`path formats `. +For example, ``beet modify artist='$artist_sort'`` will copy the artist sort +name into the artist field for all your tracks, +and ``beet modify title='$track $title'`` will add track numbers to their +title metadata. + The ``-a`` switch also operates on albums in addition to the individual tracks. Without this flag, the command will only change *track-level* data, even if all the tracks belong to the same album. If you want to change an *album-level* diff --git a/docs/reference/config.rst b/docs/reference/config.rst index 26a07e643d..2afb6f0bcc 100644 --- a/docs/reference/config.rst +++ b/docs/reference/config.rst @@ -725,6 +725,9 @@ Here's an example:: Other field/value pairs supplied via the ``--set`` option on the command-line override any settings here for fields with the same name. +Values support the same template syntax as beets' +:doc:`path formats `. + Fields are set on both the album and each individual track of the album. Fields are persisted to the media files of each track. diff --git a/test/test_importer.py b/test/test_importer.py index 2bd95315fd..784c14c07a 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -554,7 +554,8 @@ def test_set_fields(self): config['import']['set_fields'] = { 'collection': collection, - 'genre': genre + 'genre': genre, + 'title': "$title - formatted", } # As-is item import. @@ -566,6 +567,7 @@ def test_set_fields(self): item.load() # TODO: Not sure this is necessary. self.assertEqual(item.genre, genre) self.assertEqual(item.collection, collection) + self.assertEqual(item.title, "Tag Title 1 - formatted") # Remove item from library to test again with APPLY choice. item.remove() @@ -579,6 +581,7 @@ def test_set_fields(self): item.load() self.assertEqual(item.genre, genre) self.assertEqual(item.collection, collection) + self.assertEqual(item.title, "Applied Title 1 - formatted") class ImportTest(_common.TestCase, ImportHelper): @@ -743,7 +746,8 @@ def test_set_fields(self): config['import']['set_fields'] = { 'genre': genre, 'collection': collection, - 'comments': comments + 'comments': comments, + 'album': "$album - formatted", } # As-is album import. @@ -765,6 +769,9 @@ def test_set_fields(self): self.assertEqual( item.get("comments", with_album=False), comments) + self.assertEqual( + item.get("album", with_album=False), + "Tag Album - formatted") # Remove album from library to test again with APPLY choice. album.remove() @@ -788,6 +795,9 @@ def test_set_fields(self): self.assertEqual( item.get("comments", with_album=False), comments) + self.assertEqual( + item.get("album", with_album=False), + "Applied Album - formatted") class ImportTracksTest(_common.TestCase, ImportHelper): diff --git a/test/test_ui.py b/test/test_ui.py index 0c7478d457..ad43870137 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -287,6 +287,18 @@ def test_selective_modify(self): self.assertEqual(len(list(original_items)), 3) self.assertEqual(len(list(new_items)), 7) + def test_modify_formatted(self): + for i in range(0, 3): + self.add_item_fixture(title=f"title{i}", + artist="artist", + album="album") + items = list(self.lib.items()) + self.modify("title=${title} - append") + for item in items: + orig_title = item.title + item.load() + self.assertEqual(item.title, f"{orig_title} - append") + # Album Tests def test_modify_album(self): @@ -318,6 +330,13 @@ def test_album_not_move(self): item.read() self.assertNotIn(b'newAlbum', item.path) + def test_modify_album_formatted(self): + item = self.lib.items().get() + orig_album = item.album + self.modify("--album", "album=${album} - append") + item.load() + self.assertEqual(item.album, f"{orig_album} - append") + # Misc def test_write_initial_key_tag(self):