Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Formatted modify and import --set-field. #4095

Merged
merged 10 commits into from
Aug 21, 2022
6 changes: 3 additions & 3 deletions beets/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking a little closer, I couldn't quite grok what the effect of using set_parse instead of just ordinary item assignment would be. Just out of curiosity, did you run into any situations where this behaved different?

I notice that beet modify uses print_and_modify, which in turn uses obj.update, which I assume will eventually end up using ordinary attribute assignment. Is there any chance that beet modify will now behave differently from setting attributes on import?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops; I now see that we were explicitly using _parse to get this effect in beet modify, so this is actually now more consistent than it was before. Yay!

for item in items:
item[field] = value
item.set_parse(field, format(item, value))
with lib.transaction():
for item in items:
item.store()
Expand Down Expand Up @@ -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()


Expand Down
13 changes: 8 additions & 5 deletions beets/ui/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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?
Expand Down
5 changes: 5 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
9 changes: 9 additions & 0 deletions docs/reference/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <pathformat>`.
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::
Expand Down Expand Up @@ -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 <pathformat>`.
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*
Expand Down
3 changes: 3 additions & 0 deletions docs/reference/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <pathformat>`.

Fields are set on both the album and each individual track of the album.
Fields are persisted to the media files of each track.

Expand Down
14 changes: 12 additions & 2 deletions test/test_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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()

Expand All @@ -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):
Expand Down Expand Up @@ -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.
Expand All @@ -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()

Expand All @@ -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):
Expand Down
19 changes: 19 additions & 0 deletions test/test_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down