diff --git a/beets/autotag/__init__.py b/beets/autotag/__init__.py index 09564f49d..c4ee1300e 100644 --- a/beets/autotag/__init__.py +++ b/beets/autotag/__init__.py @@ -40,6 +40,7 @@ def apply_item_metadata(item, track_info): item.artist_credit = track_info.artist_credit item.title = track_info.title item.mb_trackid = track_info.track_id + item.mb_releasetrackid = track_info.release_track_id if track_info.artist_id: item.mb_artistid = track_info.artist_id if track_info.data_source: @@ -129,6 +130,7 @@ def apply_metadata(album_info, mapping): # MusicBrainz IDs. item.mb_trackid = track_info.track_id + item.mb_releasetrackid = track_info.release_track_id item.mb_albumid = album_info.album_id if track_info.artist_id: item.mb_artistid = track_info.artist_id diff --git a/beets/autotag/hooks.py b/beets/autotag/hooks.py index 053d050c6..7df1f62f4 100644 --- a/beets/autotag/hooks.py +++ b/beets/autotag/hooks.py @@ -129,6 +129,8 @@ class TrackInfo(object): - ``title``: name of the track - ``track_id``: MusicBrainz ID; UUID fragment only + - ``release_track_id``: MusicBrainz ID respective to a track on a + particular release; UUID fragment only - ``artist``: individual track artist name - ``artist_id`` - ``length``: float: duration of the track in seconds @@ -152,14 +154,15 @@ class TrackInfo(object): may be None. The indices ``index``, ``medium``, and ``medium_index`` are all 1-based. """ - def __init__(self, title, track_id, artist=None, artist_id=None, - length=None, index=None, medium=None, medium_index=None, - medium_total=None, artist_sort=None, disctitle=None, - artist_credit=None, data_source=None, data_url=None, - media=None, lyricist=None, composer=None, composer_sort=None, - arranger=None, track_alt=None): + def __init__(self, title, track_id, release_track_id=None, artist=None, + artist_id=None, length=None, index=None, medium=None, + medium_index=None, medium_total=None, artist_sort=None, + disctitle=None, artist_credit=None, data_source=None, + data_url=None, media=None, lyricist=None, composer=None, + composer_sort=None, arranger=None, track_alt=None): self.title = title self.track_id = track_id + self.release_track_id = release_track_id self.artist = artist self.artist_id = artist_id self.length = length diff --git a/beets/autotag/mb.py b/beets/autotag/mb.py index 268baf766..2b28a5cc4 100644 --- a/beets/autotag/mb.py +++ b/beets/autotag/mb.py @@ -308,6 +308,7 @@ def album_info(release): int(track['position']), track_count, ) + ti.release_track_id = track['id'] ti.disctitle = disctitle ti.media = format ti.track_alt = track['number'] diff --git a/beets/library.py b/beets/library.py index 64035e642..ba57407d0 100644 --- a/beets/library.py +++ b/beets/library.py @@ -455,6 +455,7 @@ class Item(LibModel): 'mb_albumid': types.STRING, 'mb_artistid': types.STRING, 'mb_albumartistid': types.STRING, + 'mb_releasetrackid': types.STRING, 'albumtype': types.STRING, 'label': types.STRING, 'acoustid_fingerprint': types.STRING, diff --git a/beets/mediafile.py b/beets/mediafile.py index 34ad49af7..32a32fe1d 100644 --- a/beets/mediafile.py +++ b/beets/mediafile.py @@ -1865,6 +1865,12 @@ class MediaFile(object): StorageStyle('MUSICBRAINZ_TRACKID'), ASFStorageStyle('MusicBrainz/Track Id'), ) + mb_releasetrackid = MediaField( + MP3DescStorageStyle(u'MusicBrainz Release Track Id'), + MP4StorageStyle('----:com.apple.iTunes:MusicBrainz Release Track Id'), + StorageStyle('MUSICBRAINZ_RELEASETRACKID'), + ASFStorageStyle('MusicBrainz/Release Track Id'), + ) mb_albumid = MediaField( MP3DescStorageStyle(u'MusicBrainz Album Id'), MP4StorageStyle('----:com.apple.iTunes:MusicBrainz Album Id'), diff --git a/beetsplug/discogs.py b/beetsplug/discogs.py index 82c79a023..0e9d7c229 100644 --- a/beetsplug/discogs.py +++ b/beetsplug/discogs.py @@ -474,9 +474,10 @@ class DiscogsPlugin(BeetsPlugin): medium, medium_index, _ = self.get_track_index(track['position']) artist, artist_id = self.get_artist(track.get('artists', [])) length = self.get_track_length(track['duration']) - return TrackInfo(title, track_id, artist, artist_id, length, index, - medium, medium_index, artist_sort=None, - disctitle=None, artist_credit=None) + return TrackInfo(title, track_id, artist=artist, artist_id=artist_id, + length=length, index=index, + medium=medium, medium_index=medium_index, + artist_sort=None, disctitle=None, artist_credit=None) def get_track_index(self, position): """Returns the medium, medium index and subtrack index for a discogs diff --git a/docs/changelog.rst b/docs/changelog.rst index 8b49b97a1..86d4810d3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -28,6 +28,9 @@ New features: ``mb_releasegroupid`` as well as simulates track ids using release id and tracklist positions. Track ids are stored in ``mb_trackid``. :bug:`#2336` Thanks to :user:`dbogdanov`. +* As a first step to get :bug:`#406` implemented, beets now imports the + ``musicbrainz_releasetrackid`` field into the library and tags media files + accordingly. Thanks to :user:`Rawrmonkeys`. Fixes: diff --git a/docs/reference/pathformat.rst b/docs/reference/pathformat.rst index 667be3150..72907b6df 100644 --- a/docs/reference/pathformat.rst +++ b/docs/reference/pathformat.rst @@ -239,6 +239,7 @@ Audio information: MusicBrainz and fingerprint information: * mb_trackid +* mb_releasetrackid * mb_albumid * mb_artistid * mb_albumartistid diff --git a/test/_common.py b/test/_common.py index fc7b650b3..f5e65ca76 100644 --- a/test/_common.py +++ b/test/_common.py @@ -89,6 +89,7 @@ def item(lib=None): mb_albumid='someID-2', mb_artistid='someID-3', mb_albumartistid='someID-4', + mb_releasetrackid='someID-5', album_id=None, mtime=12345, ) diff --git a/test/rsrc/full.aiff b/test/rsrc/full.aiff index 5d791b0b7..e9606f9e7 100644 Binary files a/test/rsrc/full.aiff and b/test/rsrc/full.aiff differ diff --git a/test/rsrc/full.alac.m4a b/test/rsrc/full.alac.m4a index 8ec7d377c..6bfcf317e 100644 Binary files a/test/rsrc/full.alac.m4a and b/test/rsrc/full.alac.m4a differ diff --git a/test/rsrc/full.ape b/test/rsrc/full.ape index 5bcea98e8..e1667505c 100644 Binary files a/test/rsrc/full.ape and b/test/rsrc/full.ape differ diff --git a/test/rsrc/full.dsf b/test/rsrc/full.dsf index a90e6946f..f13ea694b 100644 Binary files a/test/rsrc/full.dsf and b/test/rsrc/full.dsf differ diff --git a/test/rsrc/full.flac b/test/rsrc/full.flac index abc18ac30..e2d4f9bb4 100644 Binary files a/test/rsrc/full.flac and b/test/rsrc/full.flac differ diff --git a/test/rsrc/full.m4a b/test/rsrc/full.m4a index 6105250e6..2ec831e32 100644 Binary files a/test/rsrc/full.m4a and b/test/rsrc/full.m4a differ diff --git a/test/rsrc/full.mp3 b/test/rsrc/full.mp3 index 9aeca5fb3..d8c638f9c 100644 Binary files a/test/rsrc/full.mp3 and b/test/rsrc/full.mp3 differ diff --git a/test/rsrc/full.mpc b/test/rsrc/full.mpc index 9027ba862..007cfe494 100644 Binary files a/test/rsrc/full.mpc and b/test/rsrc/full.mpc differ diff --git a/test/rsrc/full.ogg b/test/rsrc/full.ogg index d2598d002..54e1d71eb 100644 Binary files a/test/rsrc/full.ogg and b/test/rsrc/full.ogg differ diff --git a/test/rsrc/full.opus b/test/rsrc/full.opus index 9a5534b22..2fb362c2f 100644 Binary files a/test/rsrc/full.opus and b/test/rsrc/full.opus differ diff --git a/test/rsrc/full.wma b/test/rsrc/full.wma index 741bc5f97..4a03f02f7 100644 Binary files a/test/rsrc/full.wma and b/test/rsrc/full.wma differ diff --git a/test/rsrc/full.wv b/test/rsrc/full.wv index d0fe83864..a892cb233 100644 Binary files a/test/rsrc/full.wv and b/test/rsrc/full.wv differ diff --git a/test/test_autotag.py b/test/test_autotag.py index 932616be1..244e48ecc 100644 --- a/test/test_autotag.py +++ b/test/test_autotag.py @@ -103,9 +103,9 @@ def _make_item(title, track, artist=u'some artist'): def _make_trackinfo(): return [ - TrackInfo(u'one', None, u'some artist', length=1, index=1), - TrackInfo(u'two', None, u'some artist', length=1, index=2), - TrackInfo(u'three', None, u'some artist', length=1, index=3), + TrackInfo(u'one', None, artist=u'some artist', length=1, index=1), + TrackInfo(u'two', None, artist=u'some artist', length=1, index=2), + TrackInfo(u'three', None, artist=u'some artist', length=1, index=3), ] @@ -827,15 +827,15 @@ class ApplyCompilationTest(_common.TestCase, ApplyTestUtil): trackinfo.append(TrackInfo( u'oneNew', u'dfa939ec-118c-4d0f-84a0-60f3d1e6522c', - u'artistOneNew', - u'a05686fc-9db2-4c23-b99e-77f5db3e5282', + artist=u'artistOneNew', + artist_id=u'a05686fc-9db2-4c23-b99e-77f5db3e5282', index=1, )) trackinfo.append(TrackInfo( u'twoNew', u'40130ed1-a27c-42fd-a328-1ebefb6caef4', - u'artistTwoNew', - u'80b3cf5e-18fe-4c59-98c7-e5bb87210710', + artist=u'artistTwoNew', + artist_id=u'80b3cf5e-18fe-4c59-98c7-e5bb87210710', index=2, )) self.info = AlbumInfo( diff --git a/test/test_importer.py b/test/test_importer.py index e30f5609c..6721f0dc2 100644 --- a/test/test_importer.py +++ b/test/test_importer.py @@ -1819,6 +1819,7 @@ def mocked_get_release_by_id(id_, includes=[], release_status=[], 'id': id_, 'medium-list': [{ 'track-list': [{ + 'id': 'baz', 'recording': { 'title': 'foo', 'id': 'bar', diff --git a/test/test_mb.py b/test/test_mb.py index 55df22944..b61ebe59a 100644 --- a/test/test_mb.py +++ b/test/test_mb.py @@ -63,12 +63,15 @@ class MBAlbumInfoTest(_common.TestCase): 'country': 'COUNTRY', 'status': 'STATUS', } + i = 0 track_list = [] if tracks: - for i, recording in enumerate(tracks): + for recording in tracks: + i += 1 track = { + 'id': 'RELEASE TRACK ID %d' % i, 'recording': recording, - 'position': i + 1, + 'position': i, 'number': 'A1', } if track_length: @@ -90,10 +93,12 @@ class MBAlbumInfoTest(_common.TestCase): track_list.append(track) data_track_list = [] if data_tracks: - for i, recording in enumerate(data_tracks): + for recording in data_tracks: + i += 1 data_track = { + 'id': 'RELEASE TRACK ID %d' % i, 'recording': recording, - 'position': len(track_list) + i + 1, + 'position': i, 'number': 'A1', } data_track_list.append(data_track) @@ -194,6 +199,7 @@ class MBAlbumInfoTest(_common.TestCase): self._make_track('TITLE TWO', 'ID TWO', 200.0 * 1000.0)] release = self._make_release(tracks=[tracks[0]]) second_track_list = [{ + 'id': 'RELEASE TRACK ID 2', 'recording': tracks[1], 'position': '1', 'number': 'A1', @@ -551,6 +557,7 @@ class MBLibraryTest(unittest.TestCase): 'id': mbid, 'medium-list': [{ 'track-list': [{ + 'id': 'baz', 'recording': { 'title': 'foo', 'id': 'bar', diff --git a/test/test_mediafile.py b/test/test_mediafile.py index 5a004b7e8..36a2c53ac 100644 --- a/test/test_mediafile.py +++ b/test/test_mediafile.py @@ -318,29 +318,30 @@ class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin, """ full_initial_tags = { - 'title': u'full', - 'artist': u'the artist', - 'album': u'the album', - 'genre': u'the genre', - 'composer': u'the composer', - 'grouping': u'the grouping', - 'year': 2001, - 'month': None, - 'day': None, - 'date': datetime.date(2001, 1, 1), - 'track': 2, - 'tracktotal': 3, - 'disc': 4, - 'disctotal': 5, - 'lyrics': u'the lyrics', - 'comments': u'the comments', - 'bpm': 6, - 'comp': True, - 'mb_trackid': '8b882575-08a5-4452-a7a7-cbb8a1531f9e', - 'mb_albumid': '9e873859-8aa4-4790-b985-5a953e8ef628', - 'mb_artistid': '7cf0ea9d-86b9-4dad-ba9e-2355a64899ea', - 'art': None, - 'label': u'the label', + 'title': u'full', + 'artist': u'the artist', + 'album': u'the album', + 'genre': u'the genre', + 'composer': u'the composer', + 'grouping': u'the grouping', + 'year': 2001, + 'month': None, + 'day': None, + 'date': datetime.date(2001, 1, 1), + 'track': 2, + 'tracktotal': 3, + 'disc': 4, + 'disctotal': 5, + 'lyrics': u'the lyrics', + 'comments': u'the comments', + 'bpm': 6, + 'comp': True, + 'mb_trackid': '8b882575-08a5-4452-a7a7-cbb8a1531f9e', + 'mb_releasetrackid': 'c29f3a57-b439-46fd-a2e2-93776b1371e0', + 'mb_albumid': '9e873859-8aa4-4790-b985-5a953e8ef628', + 'mb_artistid': '7cf0ea9d-86b9-4dad-ba9e-2355a64899ea', + 'art': None, + 'label': u'the label', } tag_fields = [ @@ -366,6 +367,7 @@ class ReadWriteTestBase(ArtTestMixin, GenreListTestMixin, 'bpm', 'comp', 'mb_trackid', + 'mb_releasetrackid', 'mb_albumid', 'mb_artistid', 'art', @@ -773,7 +775,7 @@ class MusepackTest(ReadWriteTestBase, unittest.TestCase): extension = 'mpc' audio_properties = { 'length': 1.0, - 'bitrate': 23458, + 'bitrate': 24023, 'format': u'Musepack', 'samplerate': 44100, 'bitdepth': 0, @@ -871,7 +873,7 @@ class ApeTest(ReadWriteTestBase, ExtendedImageStructureTestMixin, extension = 'ape' audio_properties = { 'length': 1.0, - 'bitrate': 112040, + 'bitrate': 112608, 'format': u'APE', 'samplerate': 44100, 'bitdepth': 16, @@ -883,7 +885,7 @@ class WavpackTest(ReadWriteTestBase, unittest.TestCase): extension = 'wv' audio_properties = { 'length': 1.0, - 'bitrate': 108744, + 'bitrate': 109312, 'format': u'WavPack', 'samplerate': 44100, 'bitdepth': 0, @@ -895,7 +897,7 @@ class OpusTest(ReadWriteTestBase, unittest.TestCase): extension = 'opus' audio_properties = { 'length': 1.0, - 'bitrate': 57984, + 'bitrate': 66792, 'format': u'Opus', 'samplerate': 48000, 'bitdepth': 0,