diff --git a/beets/autotag/hooks.py b/beets/autotag/hooks.py index 7f400e4ed..57537a36d 100644 --- a/beets/autotag/hooks.py +++ b/beets/autotag/hooks.py @@ -69,12 +69,13 @@ class TrackInfo(object): may be None. """ def __init__(self, title, track_id, artist=None, artist_id=None, - length=None): + length=None, medium_index=None): self.title = title self.track_id = track_id self.artist = artist self.artist_id = artist_id self.length = length + self.medium_index = medium_index # Aggregation of sources. diff --git a/beets/autotag/match.py b/beets/autotag/match.py index e63770b13..a3295a36a 100644 --- a/beets/autotag/match.py +++ b/beets/autotag/match.py @@ -234,7 +234,7 @@ def track_distance(item, track_info, track_index=None, incl_artist=False): # Track index. if track_index and item.track: - if track_index != item.track: + if item.track not in (track_index, track_info.medium_index): dist += TRACK_INDEX_WEIGHT dist_max += TRACK_INDEX_WEIGHT diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 4f6069332..7efae92c2 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -45,12 +45,14 @@ else: _mb_release_search = musicbrainzngs.search_releases _mb_recording_search = musicbrainzngs.search_recordings -def track_info(recording): +def track_info(recording, medium_index=None): """Translates a MusicBrainz recording result dictionary into a beets - ``TrackInfo`` object. + ``TrackInfo`` object. ``medium_index``, if provided, is the track's + index (1-based) on its medium. """ info = beets.autotag.hooks.TrackInfo(recording['title'], - recording['id']) + recording['id'], + medium_index=medium_index) # Get the name of the track artist. if recording.get('artist-credit-phrase'): @@ -93,7 +95,7 @@ def album_info(release): track_infos = [] for medium in release['medium-list']: for track in medium['track-list']: - ti = track_info(track['recording']) + ti = track_info(track['recording'], int(track['position'])) if track.get('title'): # Track title may be distinct from underling recording # title. diff --git a/docs/changelog.rst b/docs/changelog.rst index 2cbb81307..a2ae20b17 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,6 +6,10 @@ Changelog * The :doc:`/plugins/lyrics`, originally by `Peter Brunner`_, is revamped and included with beets, making it easy to fetch **song lyrics**. +* The autotagger now tolerates tracks on multi-disc albums that are numbered + per-disc. For example, if track 24 on a release is the first track on the + second disc, then it is not penalized for having its track number set to 1 + instead of 24. * Fix a bug in the ``rewrite`` plugin that broke the use of multiple rules for a single field. diff --git a/test/test_autotag.py b/test/test_autotag.py index f98e1feb3..696cf7347 100644 --- a/test/test_autotag.py +++ b/test/test_autotag.py @@ -173,6 +173,57 @@ class AlbumDistanceTest(unittest.TestCase): ) self.assertNotEqual(match.distance(items, info), 0) + def test_tracks_out_of_order(self): + items = [] + items.append(self.item('one', 1)) + items.append(self.item('three', 2)) + items.append(self.item('two', 3)) + info = AlbumInfo( + artist = 'some artist', + album = 'some album', + tracks = self.trackinfo(), + va = False, + album_id = None, artist_id = None, + ) + dist = match.distance(items, info) + self.assertTrue(0 < dist < 0.2) + + def test_two_medium_release(self): + items = [] + items.append(self.item('one', 1)) + items.append(self.item('two', 2)) + 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, + ) + info.tracks[0].medium_index = 1 + info.tracks[1].medium_index = 2 + info.tracks[2].medium_index = 1 + dist = match.distance(items, info) + self.assertEqual(dist, 0) + + def test_per_medium_track_numbers(self): + items = [] + items.append(self.item('one', 1)) + items.append(self.item('two', 2)) + items.append(self.item('three', 1)) + info = AlbumInfo( + artist = 'some artist', + album = 'some album', + tracks = self.trackinfo(), + va = False, + album_id = None, artist_id = None, + ) + info.tracks[0].medium_index = 1 + info.tracks[1].medium_index = 2 + info.tracks[2].medium_index = 1 + dist = match.distance(items, info) + self.assertEqual(dist, 0) + def _mkmp3(path): shutil.copyfile(os.path.join(_common.RSRC, 'min.mp3'), path) class AlbumsInDirTest(unittest.TestCase): diff --git a/test/test_mb.py b/test/test_mb.py index b66a68d2e..fa8d097c3 100644 --- a/test/test_mb.py +++ b/test/test_mb.py @@ -35,9 +35,13 @@ class MBAlbumInfoTest(unittest.TestCase): 'medium-list': [], } if tracks: - release['medium-list'].append({ - 'track-list': [{'recording': track} for track in tracks] - }) + track_list = [] + for i, track in enumerate(tracks): + track_list.append({ + 'recording': track, + 'position': str(i+1), + }) + release['medium-list'].append({ 'track-list': track_list }) return release def _make_track(self, title, tr_id, duration): @@ -85,6 +89,16 @@ class MBAlbumInfoTest(unittest.TestCase): self.assertEqual(t[1].track_id, 'ID TWO') self.assertEqual(t[1].length, 200.0) + def test_parse_track_indices(self): + tracks = [self._make_track('TITLE ONE', 'ID ONE', 100.0 * 1000.0), + self._make_track('TITLE TWO', 'ID TWO', 200.0 * 1000.0)] + release = self._make_release(tracks=tracks) + + d = mb.album_info(release) + t = d.tracks + self.assertEqual(t[0].medium_index, 1) + self.assertEqual(t[1].medium_index, 2) + def test_parse_release_year_month_only(self): release = self._make_release('1987-03') d = mb.album_info(release)