mirror of
https://github.com/beetbox/beets.git
synced 2025-12-15 04:55:10 +01:00
make helper functions methods in plugin
This helps use the _log attribute that is now available
This commit is contained in:
parent
8feb4bdf34
commit
82ce6f054d
1 changed files with 92 additions and 95 deletions
|
|
@ -26,89 +26,6 @@ from beets.util import command_output, displayable_path, subprocess
|
||||||
PLUGIN = 'duplicates'
|
PLUGIN = 'duplicates'
|
||||||
|
|
||||||
|
|
||||||
def _process_item(item, lib, copy=False, move=False, delete=False,
|
|
||||||
tag=False, fmt=''):
|
|
||||||
"""Process Item `item` in `lib`.
|
|
||||||
"""
|
|
||||||
if copy:
|
|
||||||
item.move(basedir=copy, copy=True)
|
|
||||||
item.store()
|
|
||||||
if move:
|
|
||||||
item.move(basedir=move, copy=False)
|
|
||||||
item.store()
|
|
||||||
if delete:
|
|
||||||
item.remove(delete=True)
|
|
||||||
if tag:
|
|
||||||
try:
|
|
||||||
k, v = tag.split('=')
|
|
||||||
except:
|
|
||||||
raise UserError('%s: can\'t parse k=v tag: %s' % (PLUGIN, tag))
|
|
||||||
setattr(k, v)
|
|
||||||
item.store()
|
|
||||||
print_(format(item, fmt))
|
|
||||||
|
|
||||||
|
|
||||||
def _checksum(item, prog, log):
|
|
||||||
"""Run external `prog` on file path associated with `item`, cache
|
|
||||||
output as flexattr on a key that is the name of the program, and
|
|
||||||
return the key, checksum tuple.
|
|
||||||
"""
|
|
||||||
args = [p.format(file=item.path) for p in shlex.split(prog)]
|
|
||||||
key = args[0]
|
|
||||||
checksum = getattr(item, key, False)
|
|
||||||
if not checksum:
|
|
||||||
log.debug(u'key {0} on item {1} not cached: computing checksum',
|
|
||||||
key, displayable_path(item.path))
|
|
||||||
try:
|
|
||||||
checksum = command_output(args)
|
|
||||||
setattr(item, key, checksum)
|
|
||||||
item.store()
|
|
||||||
log.debug(u'computed checksum for {0} using {1}',
|
|
||||||
item.title, key)
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
log.debug(u'failed to checksum {0}: {1}',
|
|
||||||
displayable_path(item.path), e)
|
|
||||||
else:
|
|
||||||
log.debug(u'key {0} on item {1} cached: not computing checksum',
|
|
||||||
key, displayable_path(item.path))
|
|
||||||
return key, checksum
|
|
||||||
|
|
||||||
|
|
||||||
def _group_by(objs, keys, strict, log):
|
|
||||||
"""Return a dictionary with keys arbitrary concatenations of attributes and
|
|
||||||
values lists of objects (Albums or Items) with those keys.
|
|
||||||
|
|
||||||
If strict, all attributes must be defined for a duplicate match.
|
|
||||||
"""
|
|
||||||
import collections
|
|
||||||
counts = collections.defaultdict(list)
|
|
||||||
for obj in objs:
|
|
||||||
values = [getattr(obj, k, None) for k in keys]
|
|
||||||
values = [v for v in values if v not in (None, '')]
|
|
||||||
if strict and len(values) < len(keys):
|
|
||||||
log.debug(u'some keys {0} on item {1} are null or empty: '
|
|
||||||
'skipping',
|
|
||||||
keys, displayable_path(obj.path))
|
|
||||||
elif (not strict and not len(values)):
|
|
||||||
log.debug(u'all keys {0} on item {1} are null or empty: '
|
|
||||||
'skipping',
|
|
||||||
keys, displayable_path(obj.path))
|
|
||||||
else:
|
|
||||||
key = tuple(values)
|
|
||||||
counts[key].append(obj)
|
|
||||||
|
|
||||||
return counts
|
|
||||||
|
|
||||||
|
|
||||||
def _duplicates(objs, keys, full, strict, log):
|
|
||||||
"""Generate triples of keys, duplicate counts, and constituent objects.
|
|
||||||
"""
|
|
||||||
offset = 0 if full else 1
|
|
||||||
for k, objs in _group_by(objs, keys, strict, log).iteritems():
|
|
||||||
if len(objs) > 1:
|
|
||||||
yield (k, len(objs) - offset, objs[offset:])
|
|
||||||
|
|
||||||
|
|
||||||
class DuplicatesPlugin(BeetsPlugin):
|
class DuplicatesPlugin(BeetsPlugin):
|
||||||
"""List duplicate tracks or albums
|
"""List duplicate tracks or albums
|
||||||
"""
|
"""
|
||||||
|
|
@ -214,22 +131,102 @@ class DuplicatesPlugin(BeetsPlugin):
|
||||||
'duplicates: "checksum" option must be a command'
|
'duplicates: "checksum" option must be a command'
|
||||||
)
|
)
|
||||||
for i in items:
|
for i in items:
|
||||||
k, _ = self._checksum(i, checksum, self._log)
|
k, _ = self._checksum(i, checksum)
|
||||||
keys = [k]
|
keys = [k]
|
||||||
|
|
||||||
for obj_id, obj_count, objs in _duplicates(items,
|
for obj_id, obj_count, objs in self._duplicates(items,
|
||||||
keys=keys,
|
keys=keys,
|
||||||
full=full,
|
full=full,
|
||||||
strict=strict,
|
strict=strict):
|
||||||
log=self._log):
|
|
||||||
if obj_id: # Skip empty IDs.
|
if obj_id: # Skip empty IDs.
|
||||||
for o in objs:
|
for o in objs:
|
||||||
_process_item(o, lib,
|
self._process_item(o, lib,
|
||||||
copy=copy,
|
copy=copy,
|
||||||
move=move,
|
move=move,
|
||||||
delete=delete,
|
delete=delete,
|
||||||
tag=tag,
|
tag=tag,
|
||||||
fmt=fmt.format(obj_count))
|
fmt=fmt.format(obj_count))
|
||||||
|
|
||||||
self._command.func = _dup
|
self._command.func = _dup
|
||||||
return [self._command]
|
return [self._command]
|
||||||
|
|
||||||
|
def _process_item(self, item, lib, copy=False, move=False, delete=False,
|
||||||
|
tag=False, fmt=''):
|
||||||
|
"""Process Item `item` in `lib`.
|
||||||
|
"""
|
||||||
|
if copy:
|
||||||
|
item.move(basedir=copy, copy=True)
|
||||||
|
item.store()
|
||||||
|
if move:
|
||||||
|
item.move(basedir=move, copy=False)
|
||||||
|
item.store()
|
||||||
|
if delete:
|
||||||
|
item.remove(delete=True)
|
||||||
|
if tag:
|
||||||
|
try:
|
||||||
|
k, v = tag.split('=')
|
||||||
|
except:
|
||||||
|
raise UserError('%s: can\'t parse k=v tag: %s' % (PLUGIN, tag))
|
||||||
|
setattr(k, v)
|
||||||
|
item.store()
|
||||||
|
print_(format(item, fmt))
|
||||||
|
|
||||||
|
def _checksum(self, item, prog):
|
||||||
|
"""Run external `prog` on file path associated with `item`, cache
|
||||||
|
output as flexattr on a key that is the name of the program, and
|
||||||
|
return the key, checksum tuple.
|
||||||
|
"""
|
||||||
|
args = [p.format(file=item.path) for p in shlex.split(prog)]
|
||||||
|
key = args[0]
|
||||||
|
checksum = getattr(item, key, False)
|
||||||
|
if not checksum:
|
||||||
|
self._log.debug(u'key {0} on item {1} not cached:'
|
||||||
|
'computing checksum',
|
||||||
|
key, displayable_path(item.path))
|
||||||
|
try:
|
||||||
|
checksum = command_output(args)
|
||||||
|
setattr(item, key, checksum)
|
||||||
|
item.store()
|
||||||
|
self._log.debug(u'computed checksum for {0} using {1}',
|
||||||
|
item.title, key)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
self._log.debug(u'failed to checksum {0}: {1}',
|
||||||
|
displayable_path(item.path), e)
|
||||||
|
else:
|
||||||
|
self._log.debug(u'key {0} on item {1} cached:'
|
||||||
|
'not computing checksum',
|
||||||
|
key, displayable_path(item.path))
|
||||||
|
return key, checksum
|
||||||
|
|
||||||
|
def _group_by(self, objs, keys, strict):
|
||||||
|
"""Return a dictionary with keys arbitrary concatenations of attributes and
|
||||||
|
values lists of objects (Albums or Items) with those keys.
|
||||||
|
|
||||||
|
If strict, all attributes must be defined for a duplicate match.
|
||||||
|
"""
|
||||||
|
import collections
|
||||||
|
counts = collections.defaultdict(list)
|
||||||
|
for obj in objs:
|
||||||
|
values = [getattr(obj, k, None) for k in keys]
|
||||||
|
values = [v for v in values if v not in (None, '')]
|
||||||
|
if strict and len(values) < len(keys):
|
||||||
|
self._log.debug(u'some keys {0} on item {1} are null or empty:'
|
||||||
|
' skipping',
|
||||||
|
keys, displayable_path(obj.path))
|
||||||
|
elif (not strict and not len(values)):
|
||||||
|
self._log.debug(u'all keys {0} on item {1} are null or empty:'
|
||||||
|
' skipping',
|
||||||
|
keys, displayable_path(obj.path))
|
||||||
|
else:
|
||||||
|
key = tuple(values)
|
||||||
|
counts[key].append(obj)
|
||||||
|
|
||||||
|
return counts
|
||||||
|
|
||||||
|
def _duplicates(self, objs, keys, full, strict):
|
||||||
|
"""Generate triples of keys, duplicate counts, and constituent objects.
|
||||||
|
"""
|
||||||
|
offset = 0 if full else 1
|
||||||
|
for k, objs in self._group_by(objs, keys, strict).iteritems():
|
||||||
|
if len(objs) > 1:
|
||||||
|
yield (k, len(objs) - offset, objs[offset:])
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue