mirror of
https://github.com/beetbox/beets.git
synced 2026-01-04 23:12:51 +01:00
merge
This commit is contained in:
commit
379b998a02
6 changed files with 119 additions and 16 deletions
1
NEWS
1
NEWS
|
|
@ -38,6 +38,7 @@
|
|||
quiet mode when there is no strong recommendation. The options are
|
||||
"skip" (the default) and "asis".
|
||||
* The "version" command now lists all the loaded plugins.
|
||||
* A new plugin, called "info", just prints out audio file metadata.
|
||||
* Fix a bug where some files would be erroneously interpreted as MP4.
|
||||
* Fix permission bits applied to album art files.
|
||||
* Fix malformed MusicBrainz queries caused by null characters.
|
||||
|
|
|
|||
|
|
@ -74,6 +74,9 @@ SD_PATTERNS = [
|
|||
(r'(, )?(pt\.|part) .+', 0.2),
|
||||
]
|
||||
|
||||
# Artist signals that indicate "various artists".
|
||||
VA_ARTISTS = (u'', u'various artists', u'va', u'unknown')
|
||||
|
||||
# Autotagging exceptions.
|
||||
class AutotagError(Exception):
|
||||
pass
|
||||
|
|
@ -230,7 +233,7 @@ def _plurality(objs):
|
|||
max_freq = freq
|
||||
res = obj
|
||||
|
||||
return res
|
||||
return res, len(freqs) <= 1
|
||||
|
||||
def current_metadata(items):
|
||||
"""Returns the most likely artist and album for a set of Items.
|
||||
|
|
@ -238,10 +241,11 @@ def current_metadata(items):
|
|||
"""
|
||||
keys = 'artist', 'album'
|
||||
likelies = {}
|
||||
consensus = {}
|
||||
for key in keys:
|
||||
values = [getattr(item, key) for item in items]
|
||||
likelies[key] = _plurality(values)
|
||||
return likelies['artist'], likelies['album']
|
||||
likelies[key], consensus[key] = _plurality(values)
|
||||
return likelies['artist'], likelies['album'], consensus['artist']
|
||||
|
||||
def order_items(items, trackinfo):
|
||||
"""Orders the items based on how they match some canonical track
|
||||
|
|
@ -325,7 +329,7 @@ def distance(items, info):
|
|||
"""Determines how "significant" an album metadata change would be.
|
||||
Returns a float in [0.0,1.0]. The list of items must be ordered.
|
||||
"""
|
||||
cur_artist, cur_album = current_metadata(items)
|
||||
cur_artist, cur_album, _ = current_metadata(items)
|
||||
cur_artist = cur_artist or ''
|
||||
cur_album = cur_album or ''
|
||||
|
||||
|
|
@ -492,7 +496,7 @@ def tag_album(items, search_artist=None, search_album=None):
|
|||
May raise an AutotagError if existing metadata is insufficient.
|
||||
"""
|
||||
# Get current metadata.
|
||||
cur_artist, cur_album = current_metadata(items)
|
||||
cur_artist, cur_album, artist_consensus = current_metadata(items)
|
||||
log.debug('Tagging %s - %s' % (cur_artist, cur_album))
|
||||
|
||||
# The output result tuples (keyed by MB album ID).
|
||||
|
|
@ -525,6 +529,14 @@ def tag_album(items, search_artist=None, search_album=None):
|
|||
else:
|
||||
candidates = []
|
||||
|
||||
# Possibly add "various artists" search.
|
||||
if search_album and ((not artist_consensus) or \
|
||||
(search_artist.lower() in VA_ARTISTS) or \
|
||||
any(item.comp for item in items)):
|
||||
log.debug('Possibly Various Artists; adding matches.')
|
||||
candidates.extend(mb.match_album(None, search_album, len(items),
|
||||
MAX_CANDIDATES))
|
||||
|
||||
# Get candidates from plugins.
|
||||
candidates.extend(plugins.candidates(items))
|
||||
|
||||
|
|
|
|||
|
|
@ -226,7 +226,12 @@ def match_album(artist, album, tracks=None, limit=SEARCH_LIMIT):
|
|||
optionally, a number of tracks on the album.
|
||||
"""
|
||||
# Build search criteria.
|
||||
criteria = {'artist': artist, 'release': album}
|
||||
criteria = {'release': album}
|
||||
if artist is not None:
|
||||
criteria['artist'] = artist
|
||||
else:
|
||||
# Various Artists search.
|
||||
criteria['arid'] = VARIOUS_ARTISTS_ID
|
||||
if tracks is not None:
|
||||
criteria['tracks'] = str(tracks)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# This file is part of beets.
|
||||
# Copyright 2010, Adrian Sampson.
|
||||
# Copyright 2011, Adrian Sampson.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
|
|
@ -25,7 +25,6 @@ from beets import ui
|
|||
from beets.ui import print_
|
||||
from beets import autotag
|
||||
from beets import library
|
||||
from beets.mediafile import UnreadableFileError, FileTypeError
|
||||
import beets.autotag.art
|
||||
from beets.ui import pipeline
|
||||
from beets import plugins
|
||||
|
|
@ -51,6 +50,9 @@ DEFAULT_IMPORT_RESUME = None # "ask"
|
|||
DEFAULT_THREADED = True
|
||||
DEFAULT_COLOR = True
|
||||
|
||||
QUEUE_SIZE = 128
|
||||
VARIOUS_ARTISTS = u'Various Artists'
|
||||
|
||||
class ImportAbort(Exception):
|
||||
"""Raised when the user aborts the tagging operation.
|
||||
"""
|
||||
|
|
@ -77,16 +79,28 @@ def show_change(cur_artist, cur_album, items, info, dist, color=True):
|
|||
tags are changed from (cur_artist, cur_album, items) to info with
|
||||
distance dist.
|
||||
"""
|
||||
if cur_artist != info['artist'] or cur_album != info['album']:
|
||||
def show_album(artist, album):
|
||||
if artist:
|
||||
print_(' %s - %s' % (artist, album))
|
||||
else:
|
||||
print_(' %s' % album)
|
||||
|
||||
if cur_artist != info['artist'] or \
|
||||
(cur_album != info['album'] and info['album'] != VARIOUS_ARTISTS):
|
||||
artist_l, artist_r = cur_artist or '', info['artist']
|
||||
album_l, album_r = cur_album or '', info['album']
|
||||
if artist_r == VARIOUS_ARTISTS:
|
||||
# Hide artists for VA releases.
|
||||
artist_l, artist_r = u'', u''
|
||||
|
||||
if color:
|
||||
artist_l, artist_r = ui.colordiff(artist_l, artist_r)
|
||||
album_l, album_r = ui.colordiff(album_l, album_r)
|
||||
|
||||
print_("Correcting tags from:")
|
||||
print_(' %s - %s' % (artist_l, album_l))
|
||||
show_album(artist_l, album_l)
|
||||
print_("To:")
|
||||
print_(' %s - %s' % (artist_r, album_r))
|
||||
show_album(artist_r, album_r)
|
||||
else:
|
||||
print_("Tagging: %s - %s" % (info['artist'], info['album']))
|
||||
print_('(Distance: %s)' % dist_string(dist, color))
|
||||
|
|
@ -381,6 +395,7 @@ def initial_lookup():
|
|||
is found, all of the yielded parameters (except items) are None.
|
||||
"""
|
||||
toppath, path, items = yield
|
||||
log.debug('Looking up: %s' % path)
|
||||
while True:
|
||||
if path is DONE_SENTINEL:
|
||||
cur_artist, cur_album, candidates, rec = None, None, None, None
|
||||
|
|
@ -597,7 +612,7 @@ def import_files(lib, paths, copy, write, autot, logpath, art, threaded,
|
|||
# Run the pipeline.
|
||||
try:
|
||||
if threaded:
|
||||
pl.run_parallel()
|
||||
pl.run_parallel(QUEUE_SIZE)
|
||||
else:
|
||||
pl.run_sequential()
|
||||
except ImportAbort:
|
||||
|
|
|
|||
57
beetsplug/info.py
Normal file
57
beetsplug/info.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# This file is part of beets.
|
||||
# Copyright 2011, Adrian Sampson.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
"""Shows file metadata.
|
||||
"""
|
||||
|
||||
from beets.plugins import BeetsPlugin
|
||||
from beets import library
|
||||
from beets import ui
|
||||
from beets import mediafile
|
||||
|
||||
def info(paths):
|
||||
# Set up fields to output.
|
||||
fields = []
|
||||
for name, _, _, mffield in library.ITEM_FIELDS:
|
||||
if mffield:
|
||||
fields.append(name)
|
||||
maxwidth = max(len(name) for name in fields)
|
||||
lineformat = u'{{:>{0}}}: {{}}'.format(maxwidth)
|
||||
|
||||
first = True
|
||||
for path in paths:
|
||||
if not first:
|
||||
ui.print_()
|
||||
|
||||
path = library._normpath(path)
|
||||
ui.print_(path)
|
||||
try:
|
||||
mf = mediafile.MediaFile(path)
|
||||
except mediafile.UnreadableFileError:
|
||||
ui.print_('cannot read file')
|
||||
continue
|
||||
for name in fields:
|
||||
ui.print_(lineformat.format(name, getattr(mf, name)))
|
||||
|
||||
first = False
|
||||
|
||||
class InfoPlugin(BeetsPlugin):
|
||||
def commands(self):
|
||||
cmd = ui.Subcommand('info', help='show file metadata')
|
||||
def func(lib, config, opts, args):
|
||||
if not args:
|
||||
raise ui.UserError('no file specified')
|
||||
info(args)
|
||||
cmd.func = func
|
||||
return [cmd]
|
||||
|
|
@ -28,26 +28,39 @@ from beets.library import Item
|
|||
class PluralityTest(unittest.TestCase):
|
||||
def test_plurality_consensus(self):
|
||||
objs = [1, 1, 1, 1]
|
||||
obj = autotag._plurality(objs)
|
||||
obj, consensus = autotag._plurality(objs)
|
||||
self.assertEqual(obj, 1)
|
||||
self.assertTrue(consensus)
|
||||
|
||||
def test_plurality_near_consensus(self):
|
||||
objs = [1, 1, 2, 1]
|
||||
obj = autotag._plurality(objs)
|
||||
obj, consensus = autotag._plurality(objs)
|
||||
self.assertEqual(obj, 1)
|
||||
self.assertFalse(consensus)
|
||||
|
||||
def test_plurality_conflict(self):
|
||||
objs = [1, 1, 2, 2, 3]
|
||||
obj = autotag._plurality(objs)
|
||||
obj, consensus = autotag._plurality(objs)
|
||||
self.assert_(obj in (1, 2))
|
||||
self.assertFalse(consensus)
|
||||
|
||||
def test_current_metadata_finds_pluralities(self):
|
||||
items = [Item({'artist': 'The Beetles', 'album': 'The White Album'}),
|
||||
Item({'artist': 'The Beatles', 'album': 'The White Album'}),
|
||||
Item({'artist': 'The Beatles', 'album': 'Teh White Album'})]
|
||||
l_artist, l_album = autotag.current_metadata(items)
|
||||
l_artist, l_album, artist_consensus = autotag.current_metadata(items)
|
||||
self.assertEqual(l_artist, 'The Beatles')
|
||||
self.assertEqual(l_album, 'The White Album')
|
||||
self.assertFalse(artist_consensus)
|
||||
|
||||
def test_current_metadata_artist_consensus(self):
|
||||
items = [Item({'artist': 'The Beatles', 'album': 'The White Album'}),
|
||||
Item({'artist': 'The Beatles', 'album': 'The White Album'}),
|
||||
Item({'artist': 'The Beatles', 'album': 'Teh White Album'})]
|
||||
l_artist, l_album, artist_consensus = autotag.current_metadata(items)
|
||||
self.assertEqual(l_artist, 'The Beatles')
|
||||
self.assertEqual(l_album, 'The White Album')
|
||||
self.assertTrue(artist_consensus)
|
||||
|
||||
class AlbumDistanceTest(unittest.TestCase):
|
||||
def item(self, title, track, artist='some artist'):
|
||||
|
|
|
|||
Loading…
Reference in a new issue