From f8be4bb8494674b6e432ec200383d1f74c5fa948 Mon Sep 17 00:00:00 2001 From: Tommy Schnabel-Jones Date: Fri, 23 Jan 2026 15:53:24 -0500 Subject: [PATCH] Added duduping on mb_trackid to the duplicates plugin --- beetsplug/duplicates.py | 44 ++++++++++++++++++++++++++++++++++++- docs/changelog.rst | 2 ++ docs/plugins/duplicates.rst | 7 ++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/beetsplug/duplicates.py b/beetsplug/duplicates.py index 904e192628..cb0c3d3c77 100644 --- a/beetsplug/duplicates.py +++ b/beetsplug/duplicates.py @@ -17,9 +17,10 @@ import os import shlex +from beets.dbcore.query import MatchQuery from beets.library import Album, Item from beets.plugins import BeetsPlugin -from beets.ui import Subcommand, UserError, print_ +from beets.ui import Subcommand, UserError, colorize, print_ from beets.util import ( MoveOperation, bytestring_path, @@ -141,6 +142,47 @@ def __init__(self): ) self._command.parser.add_all_common_options() + self.register_listener("import_task_created", self.import_task_created) + + def import_task_created(self, task, session): + if self.config["dedupe_mb_trackid_on_import"].get(bool): + return self._dedupe_task_on_mb_trackid(task, session) + + def _dedupe_task_on_mb_trackid(self, task, session): + # Find all items that already have the same track imported + dupes = [] + for item in task.items: + if not item.mb_trackid: + continue + + # Query the library for any matching tracks + resp = session.lib.items( + query=MatchQuery("mb_trackid", item.mb_trackid) + ) + if len(resp.rows) > 0: + log_prefix = f"{item.artist} - {item.album} - {item.title}" + print_( + f"{colorize('text_warning', log_prefix)}:" + "Item already imported, skipping..." + ) + dupes.append(item) + + # Remove the dupes + album = "" + for dup in dupes: + album = f"{dup.artist} - {dup.album}" + task.items.remove(dup) + + # Get rid of the task if all items were removed + if len(task.items) == 0: + print_( + f"{colorize('text_warning', album)}:" if album != "" else "", + "All items removed due to duplicates, removing task", + ) + return [] + + return [task] + def commands(self): def _dup(lib, opts, args): self.config.set_args(opts) diff --git a/docs/changelog.rst b/docs/changelog.rst index d59c7ba1f3..f4dcd959ad 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -52,6 +52,8 @@ New features: untouched the files without. - :doc:`plugins/fish`: Filenames are now completed in more places, like after ``beet import``. +- :doc: `plugins/duplicates`: ``dedupe_mb_trackid_on_import`` option added + to deduplicate during imports based on already-imported ``mb_trackid``. Bug fixes: diff --git a/docs/plugins/duplicates.rst b/docs/plugins/duplicates.rst index 4580343dec..50d2eb35ed 100644 --- a/docs/plugins/duplicates.rst +++ b/docs/plugins/duplicates.rst @@ -36,6 +36,11 @@ duplicates or just the duplicates themselves via command-line switches -t TAG, --tag=TAG tag matched items with 'k=v' attribute -r, --remove remove items from library +Generally, beets operates on Albums or Track singletons. As a way to bridge the +gap, the ``duplicates`` plugin can be configured to duduplicate tracks on import +by checking for already-imported items with the same ``mb_trackid``. See the +``dedupe_mb_trackid_on_import`` option for details. + Configuration ------------- @@ -54,6 +59,8 @@ file. The available options mirror the command-line options: - **count**: Print a count of duplicate tracks or albums in the format ``$albumartist - $album - $title: $count`` (for tracks) or ``$albumartist - $album: $count`` (for albums). Default: ``no``. +- **dedupe_mb_trackid_on_import**: Deduplicate album tracks when an + already-imported track has the same mb_trackid. - **delete**: Remove matched items from the library and from the disk. Default: ``no`` - **format**: A specific format with which to print every track or album. This