diff --git a/beets/autotag/__init__.py b/beets/autotag/__init__.py index ac95b61e0..13923d034 100644 --- a/beets/autotag/__init__.py +++ b/beets/autotag/__init__.py @@ -140,6 +140,10 @@ def apply_metadata(items, album_info): # Title and track index. item.title = track_info.title item.track = index + 1 + + # Disc and disc count. + item.disc = track_info.medium + item.disctotal = album_info.mediums # MusicBrainz IDs. item.mb_trackid = track_info.track_id diff --git a/beets/autotag/hooks.py b/beets/autotag/hooks.py index 57537a36d..618cbe33b 100644 --- a/beets/autotag/hooks.py +++ b/beets/autotag/hooks.py @@ -35,13 +35,14 @@ class AlbumInfo(object): - ``month``: release month - ``day``: release day - ``label``: music label responsible for the release + - ``mediums``: the number of discs in this release The fields up through ``tracks`` are required. The others are optional and may be None. """ def __init__(self, album, album_id, artist, artist_id, tracks, asin=None, albumtype=None, va=False, year=None, month=None, day=None, - label=None): + label=None, mediums=None): self.album = album self.album_id = album_id self.artist = artist @@ -54,6 +55,7 @@ class AlbumInfo(object): self.month = month self.day = day self.label = label + self.mediums = mediums class TrackInfo(object): """Describes a canonical track present on a release. Appears as part @@ -64,17 +66,20 @@ class TrackInfo(object): - ``artist``: individual track artist name - ``artist_id`` - ``length``: float: duration of the track in seconds + - ``medium``: the disc number this track appears on in the album + - ``medium_index``: the track's position on the disc Only ``title`` and ``track_id`` are required. The rest of the fields may be None. """ def __init__(self, title, track_id, artist=None, artist_id=None, - length=None, medium_index=None): + length=None, medium=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 = medium self.medium_index = medium_index diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 3f9cf2b7d..b67f0b5a4 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -45,13 +45,14 @@ else: _mb_release_search = musicbrainzngs.search_releases _mb_recording_search = musicbrainzngs.search_recordings -def track_info(recording, medium_index=None): +def track_info(recording, medium=None, medium_index=None): """Translates a MusicBrainz recording result dictionary into a beets ``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'], + medium=medium, medium_index=medium_index) # Get the name of the track artist. @@ -95,7 +96,9 @@ def album_info(release): track_infos = [] for medium in release['medium-list']: for track in medium['track-list']: - ti = track_info(track['recording'], int(track['position'])) + ti = track_info(track['recording'], + int(medium['position']), + int(track['position'])) if track.get('title'): # Track title may be distinct from underling recording # title. @@ -107,6 +110,7 @@ def album_info(release): artist_name, release['artist-credit'][0]['artist']['id'], track_infos, + mediums=len(release['medium-list']), ) info.va = info.artist_id == VARIOUS_ARTISTS_ID if 'asin' in release: diff --git a/beets/util/__init__.py b/beets/util/__init__.py index f2b807825..9ba243050 100644 --- a/beets/util/__init__.py +++ b/beets/util/__init__.py @@ -321,11 +321,11 @@ def sanitize_for_path(value, pathmod, key=None): value = value.replace(sep, u'_') elif key in ('track', 'tracktotal', 'disc', 'disctotal'): # Pad indices with zeros. - value = u'%02i' % value + value = u'%02i' % (value or 0) elif key == 'year': - value = u'%04i' % value + value = u'%04i' % (value or 0) elif key in ('month', 'day'): - value = u'%02i' % value + value = u'%02i' % (value or 0) elif key == 'bitrate': # Bitrate gets formatted as kbps. value = u'%ikbps' % ((value or 0) / 1000) diff --git a/docs/changelog.rst b/docs/changelog.rst index ca24bb34e..f821ea13a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -13,6 +13,8 @@ Changelog 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. +* The autotagger sets the disc number and disc total fields on autotagged + albums. * The autotagger now also tolerates tracks whose track artists tags are set to "Various Artists". * Plugin-supplied template values, such as those created by ``rewrite``, are now diff --git a/test/test_autotag.py b/test/test_autotag.py index a2df64ac2..12f2e087c 100644 --- a/test/test_autotag.py +++ b/test/test_autotag.py @@ -467,9 +467,11 @@ class ApplyTest(unittest.TestCase): self.items.append(Item({})) trackinfo = [] trackinfo.append(TrackInfo('oneNew', - 'dfa939ec-118c-4d0f-84a0-60f3d1e6522c')) + 'dfa939ec-118c-4d0f-84a0-60f3d1e6522c', + medium=1)) trackinfo.append(TrackInfo('twoNew', - '40130ed1-a27c-42fd-a328-1ebefb6caef4')) + '40130ed1-a27c-42fd-a328-1ebefb6caef4', + medium=2)) self.info = AlbumInfo( tracks = trackinfo, artist = 'artistNew', @@ -478,6 +480,7 @@ class ApplyTest(unittest.TestCase): artist_id = 'a6623d39-2d8e-4f70-8242-0a9553b91e50', albumtype = 'album', va = False, + mediums = 2, ) def test_titles_applied(self): @@ -502,6 +505,16 @@ class ApplyTest(unittest.TestCase): self.assertEqual(self.items[0].tracktotal, 2) self.assertEqual(self.items[1].tracktotal, 2) + def test_disc_index_applied(self): + autotag.apply_metadata(self.items, self.info) + self.assertEqual(self.items[0].disc, 1) + self.assertEqual(self.items[1].disc, 2) + + def test_disc_total_applied(self): + autotag.apply_metadata(self.items, self.info) + self.assertEqual(self.items[0].disctotal, 2) + self.assertEqual(self.items[1].disctotal, 2) + def test_mb_trackid_applied(self): autotag.apply_metadata(self.items, self.info) self.assertEqual(self.items[0].mb_trackid, diff --git a/test/test_mb.py b/test/test_mb.py index 7ab52700d..06731f6db 100644 --- a/test/test_mb.py +++ b/test/test_mb.py @@ -39,7 +39,10 @@ class MBAlbumInfoTest(unittest.TestCase): 'recording': track, 'position': str(i+1), }) - release['medium-list'].append({ 'track-list': track_list }) + release['medium-list'].append({ + 'position': '1', + 'track-list': track_list, + }) return release def _make_track(self, title, tr_id, duration): @@ -97,6 +100,38 @@ class MBAlbumInfoTest(unittest.TestCase): self.assertEqual(t[0].medium_index, 1) self.assertEqual(t[1].medium_index, 2) + def test_parse_medium_numbers_single_medium(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) + self.assertEqual(d.mediums, 1) + t = d.tracks + self.assertEqual(t[0].medium, 1) + self.assertEqual(t[1].medium, 1) + + def test_parse_medium_numbers_two_mediums(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[0]]) + second_track_list = [{ + 'recording': tracks[1], + 'position': '1', + }] + release['medium-list'].append({ + 'position': '2', + 'track-list': second_track_list, + }) + + d = mb.album_info(release) + self.assertEqual(d.mediums, 2) + t = d.tracks + self.assertEqual(t[0].medium, 1) + self.assertEqual(t[0].medium_index, 1) + self.assertEqual(t[1].medium, 2) + self.assertEqual(t[1].medium_index, 1) + def test_parse_release_year_month_only(self): release = self._make_release('1987-03') d = mb.album_info(release)