From 4f7d94257e338316d5164a6fec5a974460b4cd5a Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Fri, 8 Sep 2023 22:18:42 -0400 Subject: [PATCH 01/11] option for excluding fields from updates --- beets/ui/commands.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index f5b92ada1..add3fa406 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1196,11 +1196,13 @@ 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): """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(): if move and fields is not None and 'path' not in fields: @@ -1210,6 +1212,12 @@ def update_items(lib, query, album, move, pretend, fields): fields.append('path') items, _ = _do_query(lib, query, album) + item_fields = fields or library.Item._media_fields + album_fields = fields or library.Album._fields.keys() + if exclude_fields: + 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() for item in items: @@ -1248,7 +1256,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 +1265,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 +1291,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 +1301,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 +1314,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 +1338,10 @@ update_cmd.parser.add_option( '-F', '--field', default=None, action='append', dest='fields', help='list of fields to update' ) +update_cmd.parser.add_option( + '-f', '--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) From 4beee232cf08e73278f65763dd32794050320a91 Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Fri, 8 Sep 2023 22:40:45 -0400 Subject: [PATCH 02/11] debug --- beets/ui/commands.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index add3fa406..ed38711bb 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1338,10 +1338,10 @@ update_cmd.parser.add_option( '-F', '--field', default=None, action='append', dest='fields', help='list of fields to update' ) -update_cmd.parser.add_option( - '-f', '--exclude-field', default=None, action='append', dest='exclude_fields', - help='list of fields to exclude from updates' -) +# update_cmd.parser.add_option( +# '-f', '--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) From 7ff20c5e455de6f7b719abfbf2f1c358f4311675 Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Fri, 8 Sep 2023 22:41:56 -0400 Subject: [PATCH 03/11] fix exclude-fields argument --- beets/ui/commands.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index ed38711bb..bd9590617 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1338,10 +1338,10 @@ update_cmd.parser.add_option( '-F', '--field', default=None, action='append', dest='fields', help='list of fields to update' ) -# update_cmd.parser.add_option( -# '-f', '--exclude-field', default=None, action='append', dest='exclude_fields', -# help='list of fields to exclude from updates' -# ) +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) From aff9f6e313704b05b8149705b4407986b297d7e9 Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Sat, 9 Sep 2023 10:31:33 -0400 Subject: [PATCH 04/11] documentation --- docs/reference/cli.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 From 6b8a5cdcbc072d04a27fc554594ae201a1294f7c Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Sat, 9 Sep 2023 10:34:38 -0400 Subject: [PATCH 05/11] changelog --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0aacbf03a..1a56c3cce 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 From 65aaa96297fb57f99da6875430d28fdd2094c350 Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Sat, 9 Sep 2023 11:12:58 -0400 Subject: [PATCH 06/11] test --- beets/ui/commands.py | 2 +- test/test_ui.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index bd9590617..8f0fa0c5e 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1196,7 +1196,7 @@ default_commands.append(list_cmd) # update: Update library contents according to on-disk tags. -def update_items(lib, query, album, move, pretend, fields, exclude_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 diff --git a/test/test_ui.py b/test/test_ui.py index 9e0a67a0e..3b4ce7e3c 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, excluded_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, excluded_fields=excluded_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(excluded_fields=['lyrics']) + item = self.lib.items().get() + self.assertNotEqual(item.lyrics, 'new lyrics') + class PrintTest(_common.TestCase): def setUp(self): From 3682b272029048b7d5932ac06330a09574c22176 Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Sat, 9 Sep 2023 11:14:37 -0400 Subject: [PATCH 07/11] linted --- beets/ui/commands.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 8f0fa0c5e..c498ea2a7 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1196,13 +1196,14 @@ default_commands.append(list_cmd) # update: Update library contents according to on-disk tags. -def update_items(lib, query, album, move, pretend, fields, exclude_fields=None): +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. + :param exclude_fields: The fields to not be stored. If not specified, all + fields will be. """ with lib.transaction(): if move and fields is not None and 'path' not in fields: @@ -1339,7 +1340,8 @@ update_cmd.parser.add_option( help='list of fields to update' ) update_cmd.parser.add_option( - '-e', '--exclude-field', default=None, action='append', dest='exclude_fields', + '-e', '--exclude-field', default=None, action='append', + dest='exclude_fields', help='list of fields to exclude from updates' ) update_cmd.func = update_func From e4775235306023f4693fa13f41b0c097708bfa4b Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Sat, 9 Sep 2023 11:31:23 -0400 Subject: [PATCH 08/11] fix test --- test/test_ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_ui.py b/test/test_ui.py index 3b4ce7e3c..a183b05e7 100644 --- a/test/test_ui.py +++ b/test/test_ui.py @@ -585,7 +585,7 @@ class UpdateTest(_common.TestCase): self.i.mtime = 0 self.i.store() commands.update_items(self.lib, query, album, move, False, - fields=fields, excluded_fields=excluded_fields) + fields=fields, exclude_fields=excluded_fields) def test_delete_removes_item(self): self.assertTrue(list(self.lib.items())) From bdfed9bff0cc527bbec033416f4925501e4692ee Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Sat, 9 Sep 2023 11:40:52 -0400 Subject: [PATCH 09/11] naming consistency --- test/test_ui.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_ui.py b/test/test_ui.py index a183b05e7..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, excluded_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, exclude_fields=excluded_fields) + fields=fields, exclude_fields=exclude_fields) def test_delete_removes_item(self): self.assertTrue(list(self.lib.items())) @@ -733,7 +733,7 @@ class UpdateTest(_common.TestCase): mf = MediaFile(syspath(self.i.path)) mf.lyrics = 'new lyrics' mf.save() - self._update(excluded_fields=['lyrics']) + self._update(exclude_fields=['lyrics']) item = self.lib.items().get() self.assertNotEqual(item.lyrics, 'new lyrics') From e0424f492cafe67ee543c0cebfe248cf3a82223c Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Sun, 10 Sep 2023 21:33:19 -0400 Subject: [PATCH 10/11] fix update_items --- beets/ui/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index c498ea2a7..275927037 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1213,7 +1213,7 @@ def update_items(lib, query, album, move, pretend, fields, fields.append('path') items, _ = _do_query(lib, query, album) - item_fields = fields or library.Item._media_fields + item_fields = fields or library.Item._fields.keys() album_fields = fields or library.Album._fields.keys() if exclude_fields: item_fields = [f for f in item_fields if f not in exclude_fields] From acff509585eacc4c4735cedf725ba33bcf17ceab Mon Sep 17 00:00:00 2001 From: Arno Hautala Date: Sun, 10 Sep 2023 22:24:36 -0400 Subject: [PATCH 11/11] smaller set of fields to update; comments --- beets/ui/commands.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/beets/ui/commands.py b/beets/ui/commands.py index 275927037..74e7f7d45 100755 --- a/beets/ui/commands.py +++ b/beets/ui/commands.py @@ -1206,16 +1206,25 @@ def update_items(lib, query, album, move, pretend, fields, 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) - - item_fields = fields or library.Item._fields.keys() + 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]