diff --git a/beets/ui/commands.py b/beets/ui/commands.py index f5b92ada1..74e7f7d45 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1196,19 +1196,37 @@ default_commands.append(list_cmd) # update: Update library contents according to on-disk tags. -def update_items(lib, query, album, move, pretend, fields): +def update_items(lib, query, album, move, pretend, fields, + exclude_fields=None): """For all the items matched by the query, update the library to reflect the item's embedded tags. :param fields: The fields to be stored. If not specified, all fields will be. + :param exclude_fields: The fields to not be stored. If not specified, all + fields will be. """ with lib.transaction(): + items, _ = _do_query(lib, query, album) if move and fields is not None and 'path' not in fields: # Special case: if an item needs to be moved, the path field has to # updated; otherwise the new path will not be reflected in the # database. fields.append('path') - items, _ = _do_query(lib, query, album) + if fields is None: + # no fields were provided, update all media fields + item_fields = fields or library.Item._media_fields + if move and 'path' not in item_fields: + # move is enabled, add 'path' to the list of fields to update + item_fields.add('path') + else: + # fields was provided, just update those + item_fields = fields + # get all the album fields to update + album_fields = fields or library.Album._fields.keys() + if exclude_fields: + # remove any excluded fields from the item and album sets + item_fields = [f for f in item_fields if f not in exclude_fields] + album_fields = [f for f in album_fields if f not in exclude_fields] # Walk through the items and pick up their changes. affected_albums = set() @@ -1248,7 +1266,7 @@ def update_items(lib, query, album, move, pretend, fields): # Check for and display changes. changed = ui.show_model_changes( item, - fields=fields or library.Item._media_fields) + fields=item_fields) # Save changes. if not pretend: @@ -1257,14 +1275,14 @@ def update_items(lib, query, album, move, pretend, fields): if move and lib.directory in ancestry(item.path): item.move(store=False) - item.store(fields=fields) + item.store(fields=item_fields) affected_albums.add(item.album_id) else: # The file's mtime was different, but there were no # changes to the metadata. Store the new mtime, # which is set in the call to read(), so we don't # check this again in the future. - item.store(fields=fields) + item.store(fields=item_fields) # Skip album changes while pretending. if pretend: @@ -1283,7 +1301,7 @@ def update_items(lib, query, album, move, pretend, fields): # Update album structure to reflect an item in it. for key in library.Album.item_keys: album[key] = first_item[key] - album.store(fields=fields) + album.store(fields=album_fields) # Move album art (and any inconsistent items). if move and lib.directory in ancestry(first_item.path): @@ -1293,9 +1311,9 @@ def update_items(lib, query, album, move, pretend, fields): items = list(album.items()) for item in items: item.move(store=False, with_album=False) - item.store(fields=fields) + item.store(fields=item_fields) album.move(store=False) - album.store(fields=fields) + album.store(fields=album_fields) def update_func(lib, opts, args): @@ -1306,7 +1324,7 @@ def update_func(lib, opts, args): if not ui.input_yn("Are you sure you want to continue (y/n)?", True): return update_items(lib, decargs(args), opts.album, ui.should_move(opts.move), - opts.pretend, opts.fields) + opts.pretend, opts.fields, opts.exclude_fields) update_cmd = ui.Subcommand( @@ -1330,6 +1348,11 @@ update_cmd.parser.add_option( '-F', '--field', default=None, action='append', dest='fields', help='list of fields to update' ) +update_cmd.parser.add_option( + '-e', '--exclude-field', default=None, action='append', + dest='exclude_fields', + help='list of fields to exclude from updates' +) update_cmd.func = update_func default_commands.append(update_cmd) diff --git a/docs/changelog.rst b/docs/changelog.rst index 82c0aea3d..39e51ddcb 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -11,6 +11,7 @@ for Python 3.6). New features: +* :ref:`update-cmd`: added ```-e``` flag for excluding fields from being updated. * :doc:`/plugins/deezer`: Import rank and other attributes from Deezer during import and add a function to update the rank of existing items. :bug:`4841` * resolve transl-tracklisting relations for pseudo releases and merge data with the actual release diff --git a/docs/reference/cli.rst b/docs/reference/cli.rst index 9306397a9..86c615dbb 100644 --- a/docs/reference/cli.rst +++ b/docs/reference/cli.rst @@ -329,7 +329,7 @@ update `````` :: - beet update [-F] FIELD [-aM] QUERY + beet update [-F] FIELD [-e] EXCLUDE_FIELD [-aM] QUERY Update the library (and, by default, move files) to reflect out-of-band metadata changes and file deletions. @@ -347,8 +347,9 @@ on disk. By default, all the changed metadata will be populated back to the database. If you only want certain fields to be written, specify them with the ```-F``` -flags (which can be used multiple times). For the list of supported fields, -please see ```beet fields```. +flags (which can be used multiple times). Alternatively, specify fields to *not* +write with ```-e``` flags (which can be used multiple times). For the list of +supported fields, please see ```beet fields```. When an updated track is part of an album, the album-level fields of *all* tracks from the album are also updated. (Specifically, the command copies diff --git a/test/test_ui.py b/test/test_ui.py index 9e0a67a0e..27d36c493 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -579,13 +579,13 @@ class UpdateTest(_common.TestCase): util.remove(artfile) def _update(self, query=(), album=False, move=False, reset_mtime=True, - fields=None): + fields=None, exclude_fields=None): self.io.addinput('y') if reset_mtime: self.i.mtime = 0 self.i.store() commands.update_items(self.lib, query, album, move, False, - fields=fields) + fields=fields, exclude_fields=exclude_fields) def test_delete_removes_item(self): self.assertTrue(list(self.lib.items())) @@ -729,6 +729,14 @@ class UpdateTest(_common.TestCase): self.assertEqual(album.albumtype, correct_albumtype) self.assertEqual(album.albumtypes, correct_albumtypes) + def test_modified_metadata_excluded(self): + mf = MediaFile(syspath(self.i.path)) + mf.lyrics = 'new lyrics' + mf.save() + self._update(exclude_fields=['lyrics']) + item = self.lib.items().get() + self.assertNotEqual(item.lyrics, 'new lyrics') + class PrintTest(_common.TestCase): def setUp(self):