mirror of
https://github.com/beetbox/beets.git
synced 2026-03-07 05:34:40 +01:00
Enable duplicate detection for as-is (autotag: no) imports (#6280)
> **Note**: This fix was developed with assistance from Claude Code (AI). The problem was identified by me, and Claude helped investigate the codebase, trace the git history to find the original FIXME, implement the fix, and update the tests. All changes have been reviewed and tested. When importing with `autotag: no`, duplicate detection is completely bypassed. The `import_asis` stage calls `_apply_choice()` directly without first calling `_resolve_duplicates()`, meaning any configured `duplicate_keys` and `duplicate_action` settings are ignored. This was a known limitation. Commit79d1203541(Sep 2014) added a FIXME comment: ```python # FIXME We should also resolve duplicates when not # autotagging. This is currently handled in `user_query` ``` The FIXME was removed during a comment cleanup inf145e3b18(Jan 2015), but the underlying issue was never fixed. A test `test_no_autotag_keeps_duplicate_album` was added to document the existing behavior at the time. ### The Fix Add `_resolve_duplicates(session, task)` to the `import_asis` stage before `_apply_choice()`, matching the behavior of the `user_query` stage used when autotagging. ### Test Changes - Renamed `test_no_autotag_keeps_duplicate_album` → `test_no_autotag_removes_duplicate_album` - Fixed the test to use album metadata instead of item metadata for duplicate matching - Added missing `import_file.save()` call
This commit is contained in:
commit
1ecd2c5bdb
3 changed files with 47 additions and 12 deletions
|
|
@ -230,6 +230,7 @@ def import_asis(session: ImportSession, task: ImportTask):
|
|||
|
||||
log.info("{}", displayable_path(task.paths))
|
||||
task.set_choice(Action.ASIS)
|
||||
_resolve_duplicates(session, task)
|
||||
_apply_choice(session, task)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -44,15 +44,18 @@ Bug fixes
|
|||
- :doc:`plugins/zero`: When the ``omit_single_disc`` option is set,
|
||||
``disctotal`` is zeroed alongside ``disc``.
|
||||
- :doc:`plugins/fetchart`: Prevent deletion of configured fallback cover art
|
||||
- In autotagging, initialise empty multi-valued fields with ``None`` instead of
|
||||
empty list, which caused beets to overwrite existing metadata with empty list
|
||||
values instead of leaving them unchanged. :bug:`6403`
|
||||
- :ref:`import-cmd` When autotagging, initialise empty multi-valued fields with
|
||||
``None`` instead of empty list, which caused beets to overwrite existing
|
||||
metadata with empty list values instead of leaving them unchanged. :bug:`6403`
|
||||
- :doc:`plugins/fuzzy`: Improve fuzzy matching when the query is shorter than
|
||||
the field value so substring-style searches produce more useful results.
|
||||
:bug:`2043`
|
||||
- :doc:`plugins/fuzzy`: Force slow query evaluation whenever the fuzzy prefix is
|
||||
used (for example ``~foo`` or ``%%foo``), so fuzzy matching is applied
|
||||
consistently. :bug:`5638`
|
||||
- :ref:`import-cmd` Duplicate detection now works for as-is imports (when
|
||||
``autotag`` is disabled). Previously, ``duplicate_keys`` and
|
||||
``duplicate_action`` config options were silently ignored for as-is imports.
|
||||
|
||||
For plugin developers
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
|
|
|||
|
|
@ -954,29 +954,34 @@ class ImportDuplicateAlbumTest(PluginMixin, ImportTestCase):
|
|||
item = self.lib.items().get()
|
||||
assert item.title == "new title"
|
||||
|
||||
def test_no_autotag_keeps_duplicate_album(self):
|
||||
def test_no_autotag_removes_duplicate_album(self):
|
||||
config["import"]["autotag"] = False
|
||||
album = self.lib.albums().get()
|
||||
item = self.lib.items().get()
|
||||
assert item.title == "t\xeftle 0"
|
||||
assert item.filepath.exists()
|
||||
|
||||
# Imported item has the same artist and album as the one in the
|
||||
# library.
|
||||
# Imported item has the same albumartist and album as the one in the
|
||||
# library album. We use album metadata (not item metadata) since
|
||||
# duplicate detection uses album-level fields.
|
||||
import_file = os.path.join(
|
||||
self.importer.paths[0], b"album", b"track_1.mp3"
|
||||
)
|
||||
import_file = MediaFile(import_file)
|
||||
import_file.artist = item["artist"]
|
||||
import_file.albumartist = item["artist"]
|
||||
import_file.album = item["album"]
|
||||
import_file.artist = album.albumartist
|
||||
import_file.albumartist = album.albumartist
|
||||
import_file.album = album.album
|
||||
import_file.title = "new title"
|
||||
import_file.save()
|
||||
|
||||
self.importer.default_resolution = self.importer.Resolution.REMOVE
|
||||
self.importer.run()
|
||||
|
||||
assert item.filepath.exists()
|
||||
assert len(self.lib.albums()) == 2
|
||||
assert len(self.lib.items()) == 2
|
||||
# Old duplicate should be removed, new one imported
|
||||
assert len(self.lib.albums()) == 1
|
||||
assert len(self.lib.items()) == 1
|
||||
# The new item should be in the library
|
||||
assert self.lib.items().get().title == "new title"
|
||||
|
||||
def test_keep_duplicate_album(self):
|
||||
self.importer.default_resolution = self.importer.Resolution.KEEPBOTH
|
||||
|
|
@ -1105,6 +1110,32 @@ class ImportDuplicateSingletonTest(ImportTestCase):
|
|||
|
||||
assert len(self.lib.items()) == 2
|
||||
|
||||
def test_no_autotag_removes_duplicate_singleton(self):
|
||||
config["import"]["autotag"] = False
|
||||
item = self.lib.items().get()
|
||||
assert item.mb_trackid == "old trackid"
|
||||
assert item.filepath.exists()
|
||||
|
||||
# Imported item has the same artist and title as the one in the
|
||||
# library. We use item metadata since duplicate detection uses
|
||||
# item-level fields for singletons.
|
||||
import_file = os.path.join(
|
||||
self.importer.paths[0], b"album", b"track_1.mp3"
|
||||
)
|
||||
import_file = MediaFile(import_file)
|
||||
import_file.artist = item.artist
|
||||
import_file.title = item.title
|
||||
import_file.mb_trackid = "new trackid"
|
||||
import_file.save()
|
||||
|
||||
self.importer.default_resolution = self.importer.Resolution.REMOVE
|
||||
self.importer.run()
|
||||
|
||||
# Old duplicate should be removed, new one imported
|
||||
assert len(self.lib.items()) == 1
|
||||
# The new item should be in the library
|
||||
assert self.lib.items().get().mb_trackid == "new trackid"
|
||||
|
||||
def test_twice_in_import_dir(self):
|
||||
self.skipTest("write me")
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue