Merge pull request #13 from laarmen/feature/incomplete_albums

Tag incomplete albums (#260 on Google Code)
This commit is contained in:
Adrian Sampson 2011-12-01 13:04:11 -08:00
commit 0b5a47a745
5 changed files with 89 additions and 16 deletions

View file

@ -119,6 +119,8 @@ def apply_metadata(items, album_info):
"""
for index, (item, track_info) in enumerate(zip(items, album_info.tracks)):
# Album, artist, track count.
if not item:
continue
if track_info.artist:
item.artist = track_info.artist
else:

View file

@ -17,6 +17,7 @@ releases and tracks.
"""
import logging
import re
import copy
from munkres import Munkres
from unidecode import unidecode
@ -31,6 +32,8 @@ ARTIST_WEIGHT = 3.0
ALBUM_WEIGHT = 3.0
# The weight of the entire distance calculated for a given track.
TRACK_WEIGHT = 1.0
# The weight of a missing track.
MISSING_WEIGHT = 0.3
# These distances are components of the track distance (that is, they
# compete against each other but not ARTIST_WEIGHT and ALBUM_WEIGHT;
# the overall TRACK_WEIGHT does that).
@ -161,7 +164,7 @@ def current_metadata(items):
likelies = {}
consensus = {}
for key in keys:
values = [getattr(item, key) for item in items]
values = [getattr(item, key) for item in items if item]
likelies[key], freq = plurality(values)
consensus[key] = (freq == len(values))
return likelies['artist'], likelies['album'], consensus['artist']
@ -171,8 +174,9 @@ def order_items(items, trackinfo):
information. This always produces a result if the numbers of tracks
match.
"""
# Make sure lengths match.
if len(items) != len(trackinfo):
# Make sure lengths match: If there is less items, it might just be that
# there is some tracks missing.
if len(items) > len(trackinfo):
return None
# Construct the cost matrix.
@ -187,7 +191,7 @@ def order_items(items, trackinfo):
matching = Munkres().compute(costs)
# Order items based on the matching.
ordered_items = [None]*len(items)
ordered_items = [None]*len(trackinfo)
for cur_idx, canon_idx in matching:
ordered_items[canon_idx] = items[cur_idx]
return ordered_items
@ -269,12 +273,25 @@ def distance(items, album_info):
# Track distances.
for i, (item, track_info) in enumerate(zip(items, album_info.tracks)):
dist += track_distance(item, track_info, i+1, album_info.va) * \
TRACK_WEIGHT
dist_max += TRACK_WEIGHT
if item:
dist += track_distance(item, track_info, i+1, album_info.va) * \
TRACK_WEIGHT
dist_max += TRACK_WEIGHT
else:
dist += MISSING_WEIGHT
dist_max += MISSING_WEIGHT
# Plugin distances.
plugin_d, plugin_dm = plugins.album_distance(items, album_info)
# In order not to break compatibility, send purged lists
purged_items, purged_tracks = [], []
for i, t in zip(items, album_info.tracks):
if i:
purged_items.append(i)
purged_tracks.append(t)
purged_album_info = copy.copy(album_info)
purged_album_info.tracks = purged_tracks
plugin_d, plugin_dm = plugins.album_distance(purged_items, purged_album_info)
dist += plugin_d
dist_max += plugin_dm
@ -348,7 +365,7 @@ def validate_candidate(items, tuple_dict, info):
return
# Make sure the album has the correct number of tracks.
if len(items) != len(info.tracks):
if len(items) > len(info.tracks):
log.debug('Track count mismatch.')
return

View file

@ -107,7 +107,7 @@ def _duplicate_check(lib, task, recent=None):
recent.add((artist, album))
# Look in the library.
cur_paths = set(i.path for i in task.items)
cur_paths = set(i.path for i in task.items if i)
for album_cand in lib.albums(artist=artist):
if album_cand.album == album:
# Check whether the album is identical in contents, in which
@ -585,7 +585,7 @@ def apply_choices(config):
if task.should_skip():
continue
items = task.items if task.is_album else [task.item]
items = [i for i in task.items if i] if task.is_album else [task.item]
# Clear IDs in case the items are being re-tagged.
for item in items:
item.id = None
@ -637,7 +637,7 @@ def apply_choices(config):
# Add new ones.
if task.is_album:
# Add an album.
album = lib.add_album(task.items)
album = lib.add_album([i for i in task.items if i])
task.album_id = album.id
else:
# Add tracks.

View file

@ -101,6 +101,8 @@ DEFAULT_IGNORE = ['.AppleDouble', '._*', '*~', '.DS_Store']
VARIOUS_ARTISTS = u'Various Artists'
PARTIAL_MATCH_STRING = ui.colorize('green', u'(Partial match !)')
# Importer utilities and support.
def dist_string(dist, color):
@ -122,13 +124,22 @@ 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.
"""
def show_album(artist, album):
def show_album(artist, album, partial=False):
if artist:
print_(' %s - %s' % (artist, album))
album_description = ' %s - %s' % (artist, album)
elif album:
print_(' %s' % album)
album_description = ' %s' % album
else:
print_(' (unknown album)')
album_description = ' (unknown album)'
# Add a suffix indicating a partial match
if partial:
print_('%s %s' % (album_description, PARTIAL_MATCH_STRING))
else:
print_(album_description)
# Record if the match is partial or not.
partial_match = None in items
# Identify the album in question.
if cur_artist != info.artist or \
@ -147,6 +158,9 @@ def show_change(cur_artist, cur_album, items, info, dist, color=True):
show_album(artist_l, album_l)
print_("To:")
show_album(artist_r, album_r)
elif partial_match:
print_("Tagging: %s - %s %s" % (info.artist, info.album,
PARTIAL_MATCH_STRING))
else:
print_("Tagging: %s - %s" % (info.artist, info.album))
@ -154,7 +168,11 @@ def show_change(cur_artist, cur_album, items, info, dist, color=True):
print_('(Similarity: %s)' % dist_string(dist, color))
# Tracks.
missing_tracks = []
for i, (item, track_info) in enumerate(zip(items, info.tracks)):
if not item:
missing_tracks.append((i, track_info))
continue
cur_track = unicode(item.track)
new_track = unicode(i+1)
cur_title = item.title
@ -179,6 +197,9 @@ def show_change(cur_artist, cur_album, items, info, dist, color=True):
print_(u" * %s -> %s" % (cur_title, new_title))
elif cur_track != new_track:
print_(u" * %s (%s -> %s)" % (item.title, cur_track, new_track))
for i, track_info in missing_tracks:
print_(ui.colorize('red', u' * Missing track: %s (%d)' % \
(track_info.title, i+1)))
def show_item_change(item, info, dist, color):
"""Print out the change that would occur by tagging `item` with the
@ -305,6 +326,11 @@ def choose_candidate(candidates, singleton, rec, color, timid,
line += u' [%s]' % year
line += ' (%s)' % dist_string(dist, color)
# Pointing out the partial matches.
if None in items:
line += ' %s' % PARTIAL_MATCH_STRING
print_(line)
# Ask the user for a choice.

View file

@ -98,6 +98,21 @@ class AlbumDistanceTest(unittest.TestCase):
)
self.assertEqual(match.distance(items, info), 0)
def test_incomplete_album(self):
items = []
items.append(self.item('one', 1))
items.append(self.item('three', 3))
info = AlbumInfo(
artist = 'some artist',
album = 'some album',
tracks = self.trackinfo(),
va = False,
album_id = None, artist_id = None,
)
self.assertNotEqual(match.distance(items, info), 0)
# Make sure the distance is not too great
self.assertTrue(match.distance(items, info) < 0.2)
def test_global_artists_differ(self):
items = []
items.append(self.item('one', 1))
@ -291,11 +306,24 @@ class OrderingTest(unittest.TestCase):
items = []
items.append(self.item('one', 1))
items.append(self.item('two', 2))
items.append(self.item('three', 3))
items.append(self.item('four',4))
trackinfo = []
trackinfo.append(TrackInfo('one', None))
ordered = match.order_items(items, trackinfo)
self.assertEqual(ordered, None)
def test_order_works_with_missing_tracks(self)
items = []
items.append(self.item('one', 1))
items.append(self.item('two', 2))
trackinfo = []
trackinfo.append(TrackInfo('one', None))
ordered = match.order_items(items, trackinfo)
self.assertEqual(ordered[0].title, 'one')
self.assertEqual(ordered[1].title, 'two')
self.assertEqual(ordered[2], None)
def test_order_corrects_when_track_names_are_entirely_wrong(self):
# A real-world test case contributed by a user.
def item(i, length):