From f50d250c4a4046854fde40591ea7c4230f237618 Mon Sep 17 00:00:00 2001 From: Julien Cassette Date: Sun, 2 Jan 2022 17:25:30 +0100 Subject: [PATCH] Review duplicate_keys feature --- beets/importer.py | 42 +++++++++++---------------------------- beets/library.py | 14 +++++++++++++ docs/changelog.rst | 2 +- docs/reference/config.rst | 2 +- test/test_importer.py | 2 +- 5 files changed, 29 insertions(+), 33 deletions(-) diff --git a/beets/importer.py b/beets/importer.py index 6d800a1e5..a671a9d8e 100644 --- a/beets/importer.py +++ b/beets/importer.py @@ -521,28 +521,18 @@ class ImportTask(BaseImportTask): # Convenient data. - def chosen_ident(self): - """Returns identifying metadata about the current choice. For - albums, this is an (artist, album) pair. For items, this is - (artist, title). May only be called when the choice flag is ASIS - or RETAG (in which case the data comes from the files' current - metadata) or APPLY (data comes from the choice). - """ - if self.choice_flag in (action.ASIS, action.RETAG): - return (self.cur_artist, self.cur_album) - elif self.choice_flag is action.APPLY: - return (self.match.info.artist, self.match.info.album) - def chosen_info(self): - """Returns a dictionnary of metadata about the current choice. + """Return a dictionary of metadata about the current choice. May only be called when the choice flag is ASIS or RETAG (in which case the data comes from the files' current metadata) or APPLY (in which case the data comes from the choice). """ + assert(self.choice_flag in (action.ASIS, action.RETAG, action.APPLY)) if self.choice_flag in (action.ASIS, action.RETAG): - return self.cur_info + likelies, consensus = autotag.current_metadata(self.items) + return likelies elif self.choice_flag is action.APPLY: - return self.match.info + return self.match.info.copy() def imported_items(self): """Return a list of Items that should be added to the library. @@ -667,8 +657,6 @@ class ImportTask(BaseImportTask): candidate IDs are stored in self.search_ids: if present, the initial lookup is restricted to only those IDs. """ - likelies, consensus = autotag.current_metadata(self.items) - self.cur_info = likelies artist, album, prop = \ autotag.tag_album(self.items, search_ids=self.search_ids) self.cur_artist = artist @@ -680,26 +668,20 @@ class ImportTask(BaseImportTask): """Return a list of albums from `lib` with the same artist and album name as the task. """ - artist, album = self.chosen_ident() + info = self.chosen_info() - if artist is None: + if info['artist'] is None: # As-is import with no artist. Skip check. return [] duplicates = [] task_paths = {i.path for i in self.items if i} - keys = config['import']['duplicate_keys'].as_str().split() - info = self.chosen_info().copy() - info['albumartist'] = artist - album = library.Album(None, **info) - subqueries = [] - for key in keys: - value = album.get(key) - fast = key in library.Album.item_keys - subqueries.append(dbcore.MatchQuery(key, value, fast)) - duplicate_query = dbcore.AndQuery(subqueries) + keys = config['import']['duplicate_keys'].as_str_seq() + info['albumartist'] = info['artist'] + # Create an Album object so that flexible attributes can be used. + tmp_album = library.Album(lib, **info) - for album in lib.albums(duplicate_query): + for album in tmp_album.duplicates(*keys): # Check whether the album paths are all present in the task # i.e. album is being completely re-imported by the task, # in which case it is not a duplicate (will be replaced). diff --git a/beets/library.py b/beets/library.py index c8993f85b..9f4e5252a 100644 --- a/beets/library.py +++ b/beets/library.py @@ -1142,6 +1142,20 @@ class Album(LibModel): getters['albumtotal'] = Album._albumtotal return getters + @classmethod + def construct_match_queries(cls, **info): + subqueries = [] + for (key, value) in info.items(): + # Use slow queries for flexible attributes. + fast = key in cls._fields + subqueries.append(dbcore.MatchQuery(key, value, fast)) + return subqueries + + def duplicates(self, *keys): + info = {key: self.get(key) for key in keys} + subqueries = self.construct_match_queries(**info) + return self._db.albums(dbcore.AndQuery(subqueries)) + def items(self): """Return an iterable over the items associated with this album. diff --git a/docs/changelog.rst b/docs/changelog.rst index 2ce0d35e8..462567e10 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -11,7 +11,7 @@ New features: * :doc:`/plugins/kodiupdate`: Now supports multiple kodi instances :bug:`4101` * Add the item fields ``bitrate_mode``, ``encoder_info`` and ``encoder_settings``. -* Allow to configure which fields are used to find duplicates +* :doc:`/reference/config`: Allow to configure which fields are used to find duplicates Bug fixes: diff --git a/docs/reference/config.rst b/docs/reference/config.rst index 448eb8e60..d12fec648 100644 --- a/docs/reference/config.rst +++ b/docs/reference/config.rst @@ -676,7 +676,7 @@ duplicate_keys ~~~~~~~~~~~~~~ The fields used to find duplicates in import task. -If several items have the same value for each key, they will be considered duplicates. +If several albums have the same value for each key, they will be considered duplicates. Default: ``albumartist album`` diff --git a/test/test_importer.py b/test/test_importer.py index 4a2b81038..0e8fe7a61 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -1311,7 +1311,7 @@ class ImportDuplicateAlbumTest(unittest.TestCase, TestHelper, def test_twice_in_import_dir(self): self.skipTest('write me') - + def test_keep_when_extra_key_is_different(self): config['import']['duplicate_keys'] = 'albumartist album flex'