Skip to content

Commit

Permalink
Merge pull request beetbox#4095 from Duncaen/formatted-modify
Browse files Browse the repository at this point in the history
Formatted modify and import --set-field.
  • Loading branch information
sampsyo authored Aug 21, 2022
2 parents 4761c35 + fb9e95b commit 40d7fa6
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 10 deletions.
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))
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

0 comments on commit 40d7fa6

Please sign in to comment.