diff --git a/beets/autotag/__init__.py b/beets/autotag/__init__.py index d60f968df..7b20da3b4 100644 --- a/beets/autotag/__init__.py +++ b/beets/autotag/__init__.py @@ -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)) diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 2894aea18..35fa62131 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -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) diff --git a/test/test_autotag.py b/test/test_autotag.py index ac17abfd8..484cd5ad5 100644 --- a/test/test_autotag.py +++ b/test/test_autotag.py @@ -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'):