Merge pull request #4823 from JOJ0/album_flex_streamline

Streamline album attributes modification behaviour and allow override via CLI
This commit is contained in:
J0J0 Todos 2023-08-23 09:14:26 +02:00 committed by GitHub
commit 62859f4389
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 43 additions and 17 deletions

View file

@ -1369,22 +1369,27 @@ class Album(LibModel):
plugins.send('art_set', album=self) plugins.send('art_set', album=self)
def store(self, fields=None): def store(self, fields=None, inherit=True):
"""Update the database with the album information. """Update the database with the album information.
The album's tracks are also updated.
`fields` represents the fields to be stored. If not specified, `fields` represents the fields to be stored. If not specified,
all fields will be. all fields will be.
The album's tracks are also updated when the `inherit` flag is enabled.
This applies to fixed attributes as well as flexible ones. The `id`
attribute of the album will never be inherited.
""" """
# Get modified track fields. # Get modified track fields.
track_updates = {} track_updates = {}
track_deletes = set() track_deletes = set()
for key in self._dirty: for key in self._dirty:
if key in self.item_keys: if inherit:
if key in self.item_keys: # is a fixed attribute
track_updates[key] = self[key] track_updates[key] = self[key]
elif key not in self: elif key not in self: # is a fixed or a flexible attribute
track_deletes.add(key) track_deletes.add(key)
elif key != 'id': # is a flexible attribute
track_updates[key] = self[key]
with self._db.transaction(): with self._db.transaction():
super().store(fields) super().store(fields)
@ -1400,7 +1405,7 @@ class Album(LibModel):
del item[key] del item[key]
item.store() item.store()
def try_sync(self, write, move): def try_sync(self, write, move, inherit=True):
"""Synchronize the album and its items with the database. """Synchronize the album and its items with the database.
Optionally, also write any new tags into the files and update Optionally, also write any new tags into the files and update
their paths. their paths.
@ -1409,7 +1414,7 @@ class Album(LibModel):
`move` controls whether files (both audio and album art) are `move` controls whether files (both audio and album art) are
moved. moved.
""" """
self.store() self.store(inherit=inherit)
for item in self.items(): for item in self.items():
item.try_sync(write, move) item.try_sync(write, move)

View file

@ -1498,7 +1498,7 @@ default_commands.append(version_cmd)
# modify: Declaratively change metadata. # modify: Declaratively change metadata.
def modify_items(lib, mods, dels, query, write, move, album, confirm): def modify_items(lib, mods, dels, query, write, move, album, confirm, inherit):
"""Modifies matching items according to user-specified assignments and """Modifies matching items according to user-specified assignments and
deletions. deletions.
@ -1551,7 +1551,7 @@ def modify_items(lib, mods, dels, query, write, move, album, confirm):
# Apply changes to database and files # Apply changes to database and files
with lib.transaction(): with lib.transaction():
for obj in changed: for obj in changed:
obj.try_sync(write, move) obj.try_sync(write, move, inherit)
def print_and_modify(obj, mods, dels): def print_and_modify(obj, mods, dels):
@ -1594,7 +1594,8 @@ def modify_func(lib, opts, args):
if not mods and not dels: if not mods and not dels:
raise ui.UserError('no modifications specified') raise ui.UserError('no modifications specified')
modify_items(lib, mods, dels, query, ui.should_write(opts.write), modify_items(lib, mods, dels, query, ui.should_write(opts.write),
ui.should_move(opts.move), opts.album, not opts.yes) ui.should_move(opts.move), opts.album, not opts.yes,
opts.inherit)
modify_cmd = ui.Subcommand( modify_cmd = ui.Subcommand(
@ -1622,6 +1623,10 @@ modify_cmd.parser.add_option(
'-y', '--yes', action='store_true', '-y', '--yes', action='store_true',
help='skip confirmation' help='skip confirmation'
) )
modify_cmd.parser.add_option(
'-I', '--noinherit', action='store_false', dest='inherit', default=True,
help="when modifying albums, don't also change item data"
)
modify_cmd.func = modify_func modify_cmd.func = modify_func
default_commands.append(modify_cmd) default_commands.append(modify_cmd)

View file

@ -296,4 +296,4 @@ class IPFSPlugin(BeetsPlugin):
self._log.info("Adding '{0}' to temporary library", album) self._log.info("Adding '{0}' to temporary library", album)
new_album = tmplib.add_album(items) new_album = tmplib.add_album(items)
new_album.ipfs = album.ipfs new_album.ipfs = album.ipfs
new_album.store() new_album.store(inherit=False)

View file

@ -214,6 +214,11 @@ Bug fixes:
the highest number of likes the highest number of likes
* :doc:`/plugins/lyrics`: Fix a crash with the Google backend when processing * :doc:`/plugins/lyrics`: Fix a crash with the Google backend when processing
some web pages. :bug:`4875` some web pages. :bug:`4875`
* Modifying flexible attributes of albums now cascade to the individual album
tracks, similar to how fixed album attributes have been cascading to tracks
already. A new option ``--noinherit/-I`` to :ref:`modify <modify-cmd>`
allows changing this behaviour.
:bug:`4822`
For packagers: For packagers:

View file

@ -257,7 +257,7 @@ modify
`````` ``````
:: ::
beet modify [-MWay] [-f FORMAT] QUERY [FIELD=VALUE...] [FIELD!...] beet modify [-IMWay] [-f FORMAT] QUERY [FIELD=VALUE...] [FIELD!...]
Change the metadata for items or albums in the database. Change the metadata for items or albums in the database.
@ -274,13 +274,17 @@ name into the artist field for all your tracks,
and ``beet modify title='$track $title'`` will add track numbers to their and ``beet modify title='$track $title'`` will add track numbers to their
title metadata. title metadata.
The ``-a`` switch also operates on albums in addition to the individual tracks. The ``-a`` option changes to querying album fields instead of track fields and
also enables to operate on albums in addition to the individual tracks.
Without this flag, the command will only change *track-level* data, even if all 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* the tracks belong to the same album. If you want to change an *album-level*
field, such as ``year`` or ``albumartist``, you'll want to use the ``-a`` flag field, such as ``year`` or ``albumartist``, you'll want to use the ``-a`` flag
to avoid a confusing situation where the data for individual tracks conflicts to avoid a confusing situation where the data for individual tracks conflicts
with the data for the whole album. with the data for the whole album.
Modifications issued using ``-a`` by default cascade to individual tracks. To
prevent this behavior, use ``-I``/``--noinherit``.
Items will automatically be moved around when necessary if they're in your Items will automatically be moved around when necessary if they're in your
library directory, but you can disable that with ``-M``. Tags will be written library directory, but you can disable that with ``-M``. Tags will be written
to the files according to the settings you have for imports, but these can be to the files according to the settings you have for imports, but these can be

View file

@ -87,7 +87,7 @@ class IPFSPluginTest(unittest.TestCase, TestHelper):
album = self.lib.add_album(items) album = self.lib.add_album(items)
album.ipfs = "QmfM9ic5LJj7V6ecozFx1MkSoaaiq3PXfhJoFvyqzpLXSf" album.ipfs = "QmfM9ic5LJj7V6ecozFx1MkSoaaiq3PXfhJoFvyqzpLXSf"
album.store() album.store(inherit=False)
return album return album

View file

@ -1022,10 +1022,17 @@ class AlbumInfoTest(_common.TestCase):
self.assertEqual(i.albumartist, 'myNewArtist') self.assertEqual(i.albumartist, 'myNewArtist')
self.assertNotEqual(i.artist, 'myNewArtist') self.assertNotEqual(i.artist, 'myNewArtist')
def test_albuminfo_change_artist_does_change_items(self):
ai = self.lib.get_album(self.i)
ai.artist = 'myNewArtist'
ai.store(inherit=True)
i = self.lib.items()[0]
self.assertEqual(i.artist, 'myNewArtist')
def test_albuminfo_change_artist_does_not_change_items(self): def test_albuminfo_change_artist_does_not_change_items(self):
ai = self.lib.get_album(self.i) ai = self.lib.get_album(self.i)
ai.artist = 'myNewArtist' ai.artist = 'myNewArtist'
ai.store() ai.store(inherit=False)
i = self.lib.items()[0] i = self.lib.items()[0]
self.assertNotEqual(i.artist, 'myNewArtist') self.assertNotEqual(i.artist, 'myNewArtist')