mirror of
https://github.com/beetbox/beets.git
synced 2025-12-06 08:39:17 +01:00
add tiebreaking facility
This commit is contained in:
parent
132dc4c617
commit
6be98b0a36
3 changed files with 48 additions and 11 deletions
|
|
@ -22,6 +22,7 @@ import shlex
|
||||||
from beets.plugins import BeetsPlugin
|
from beets.plugins import BeetsPlugin
|
||||||
from beets.ui import decargs, print_, vararg_callback, Subcommand, UserError
|
from beets.ui import decargs, print_, vararg_callback, Subcommand, UserError
|
||||||
from beets.util import command_output, displayable_path, subprocess
|
from beets.util import command_output, displayable_path, subprocess
|
||||||
|
from beets.library import Item, Album
|
||||||
|
|
||||||
PLUGIN = 'duplicates'
|
PLUGIN = 'duplicates'
|
||||||
|
|
||||||
|
|
@ -33,17 +34,18 @@ class DuplicatesPlugin(BeetsPlugin):
|
||||||
super(DuplicatesPlugin, self).__init__()
|
super(DuplicatesPlugin, self).__init__()
|
||||||
|
|
||||||
self.config.add({
|
self.config.add({
|
||||||
'format': '',
|
|
||||||
'count': False,
|
|
||||||
'album': False,
|
'album': False,
|
||||||
'full': False,
|
|
||||||
'strict': False,
|
|
||||||
'path': False,
|
|
||||||
'keys': ['mb_trackid', 'mb_albumid'],
|
|
||||||
'checksum': '',
|
'checksum': '',
|
||||||
'copy': '',
|
'copy': '',
|
||||||
'move': '',
|
'count': False,
|
||||||
'delete': False,
|
'delete': False,
|
||||||
|
'format': '',
|
||||||
|
'full': False,
|
||||||
|
'keys': [],
|
||||||
|
'move': '',
|
||||||
|
'path': False,
|
||||||
|
'tiebreak': {},
|
||||||
|
'strict': False,
|
||||||
'tag': '',
|
'tag': '',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -91,6 +93,7 @@ class DuplicatesPlugin(BeetsPlugin):
|
||||||
action='store',
|
action='store',
|
||||||
help='tag matched items with \'k=v\''
|
help='tag matched items with \'k=v\''
|
||||||
' attribute')
|
' attribute')
|
||||||
|
|
||||||
self._command.parser.add_all_common_options()
|
self._command.parser.add_all_common_options()
|
||||||
|
|
||||||
def commands(self):
|
def commands(self):
|
||||||
|
|
@ -107,13 +110,17 @@ class DuplicatesPlugin(BeetsPlugin):
|
||||||
keys = self.config['keys'].get(list)
|
keys = self.config['keys'].get(list)
|
||||||
move = self.config['move'].get(str)
|
move = self.config['move'].get(str)
|
||||||
path = self.config['path'].get(bool)
|
path = self.config['path'].get(bool)
|
||||||
|
tiebreak = self.config['tiebreak'].get(dict)
|
||||||
strict = self.config['strict'].get(bool)
|
strict = self.config['strict'].get(bool)
|
||||||
tag = self.config['tag'].get(str)
|
tag = self.config['tag'].get(str)
|
||||||
|
|
||||||
if album:
|
if album:
|
||||||
keys = ['mb_albumid']
|
if not keys:
|
||||||
|
keys = ['mb_albumid']
|
||||||
items = lib.albums(decargs(args))
|
items = lib.albums(decargs(args))
|
||||||
else:
|
else:
|
||||||
|
if not keys:
|
||||||
|
keys = ['mb_trackid', 'mb_albumid']
|
||||||
items = lib.items(decargs(args))
|
items = lib.items(decargs(args))
|
||||||
|
|
||||||
if path:
|
if path:
|
||||||
|
|
@ -135,7 +142,8 @@ class DuplicatesPlugin(BeetsPlugin):
|
||||||
for obj_id, obj_count, objs in self._duplicates(items,
|
for obj_id, obj_count, objs in self._duplicates(items,
|
||||||
keys=keys,
|
keys=keys,
|
||||||
full=full,
|
full=full,
|
||||||
strict=strict):
|
strict=strict,
|
||||||
|
tiebreak=tiebreak):
|
||||||
if obj_id: # Skip empty IDs.
|
if obj_id: # Skip empty IDs.
|
||||||
for o in objs:
|
for o in objs:
|
||||||
self._process_item(o, lib,
|
self._process_item(o, lib,
|
||||||
|
|
@ -221,10 +229,29 @@ class DuplicatesPlugin(BeetsPlugin):
|
||||||
|
|
||||||
return counts
|
return counts
|
||||||
|
|
||||||
def _duplicates(self, objs, keys, full, strict):
|
def _order(self, objs, tiebreak=None):
|
||||||
|
"""Return objs sorted by descending order of fields in tiebreak dict.
|
||||||
|
|
||||||
|
Default ordering is based on attribute completeness.
|
||||||
|
"""
|
||||||
|
if tiebreak:
|
||||||
|
kind = 'items' if all(isinstance(o, Item)
|
||||||
|
for o in objs) else 'albums'
|
||||||
|
key = lambda x: tuple(getattr(x, k) for k in tiebreak[kind])
|
||||||
|
else:
|
||||||
|
kind = Item if all(isinstance(o, Item) for o in objs) else Album
|
||||||
|
fields = [f for sublist in kind.get_fields() for f in sublist]
|
||||||
|
key = lambda x: len([(a, getattr(x, a, None)) for a in fields
|
||||||
|
if getattr(x, a, None) not in (None, '')])
|
||||||
|
|
||||||
|
return sorted(objs, key=key, reverse=True)
|
||||||
|
|
||||||
|
def _duplicates(self, objs, keys, full, strict, tiebreak):
|
||||||
"""Generate triples of keys, duplicate counts, and constituent objects.
|
"""Generate triples of keys, duplicate counts, and constituent objects.
|
||||||
"""
|
"""
|
||||||
offset = 0 if full else 1
|
offset = 0 if full else 1
|
||||||
for k, objs in self._group_by(objs, keys, strict).iteritems():
|
for k, objs in self._group_by(objs, keys, strict).iteritems():
|
||||||
if len(objs) > 1:
|
if len(objs) > 1:
|
||||||
yield (k, len(objs) - offset, objs[offset:])
|
yield (k,
|
||||||
|
len(objs) - offset,
|
||||||
|
self._order(objs, tiebreak)[offset:])
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,16 @@ Changelog
|
||||||
|
|
||||||
New features:
|
New features:
|
||||||
|
|
||||||
|
* The :doc:`/plugins/duplicates` plugin now enforces an ordering on
|
||||||
|
duplicates: it defaults to metadata attribute set completeness,
|
||||||
|
or alternatively any list of attributes that should be favored.
|
||||||
* The :doc:`/plugins/metasync` plugin now lets you get metadata from iTunes.
|
* The :doc:`/plugins/metasync` plugin now lets you get metadata from iTunes.
|
||||||
This plugin is still in an experimental phase. :bug:`1450`
|
This plugin is still in an experimental phase. :bug:`1450`
|
||||||
|
|
||||||
|
|
||||||
Fixes:
|
Fixes:
|
||||||
|
|
||||||
|
* :doc:`/plugins/duplicates`: Avoid a crash when misconfigured. :bug:`1457`
|
||||||
* :doc:`/plugins/mpdstats`: Avoid a crash when the music played is not in the
|
* :doc:`/plugins/mpdstats`: Avoid a crash when the music played is not in the
|
||||||
beets library. Thanks to :user:`CodyReichert`. :bug:`1443`
|
beets library. Thanks to :user:`CodyReichert`. :bug:`1443`
|
||||||
* Fix a crash with ArtResizer on Windows systems (affecting
|
* Fix a crash with ArtResizer on Windows systems (affecting
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,12 @@ file. The available options mirror the command-line options:
|
||||||
- **tag**: A ``key=value`` pair. The plugin will add a new ``key`` attribute
|
- **tag**: A ``key=value`` pair. The plugin will add a new ``key`` attribute
|
||||||
with ``value`` value as a flexattr to the database for duplicate items.
|
with ``value`` value as a flexattr to the database for duplicate items.
|
||||||
Default: ``no``.
|
Default: ``no``.
|
||||||
|
- **tiebreak**: Dictionary of lists of attributes keyed by ``items``
|
||||||
|
or ``albums`` to use when choosing duplicates. By default, the
|
||||||
|
tie-breaking procedure favors the most complete metadata attribute
|
||||||
|
set. If you would like to consider the lower bitrates as duplicates,
|
||||||
|
for example, set ``tiebreak: items: [bitrate]``.
|
||||||
|
Default: ``{}``.
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
--------
|
--------
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue