Merge branch 'feature/musicbrainz-id-support' into develop

This commit is contained in:
Thomas Adamcik 2010-11-03 00:10:42 +01:00
commit 86e2da2268
10 changed files with 291 additions and 37 deletions

View File

@ -96,33 +96,54 @@ def _convert_mpd_data(data, tracks, music_dir):
track_kwargs = {}
album_kwargs = {}
artist_kwargs = {}
albumartist_kwargs = {}
if 'track' in data:
album_kwargs['num_tracks'] = int(data['track'].split('/')[1])
track_kwargs['track_no'] = int(data['track'].split('/')[0])
if 'artist' in data:
artist = Artist(name=data['artist'])
track_kwargs['artists'] = [artist]
album_kwargs['artists'] = [artist]
artist_kwargs['name'] = data['artist']
albumartist_kwargs['name'] = data['artist']
if 'albumartist' in data:
albumartist_kwargs['name'] = data['albumartist']
# FIXME Newer mpd tag caches support albumartist names
if 'album' in data:
album_kwargs['name'] = data['album']
album = Album(**album_kwargs)
track_kwargs['album'] = album
if 'title' in data:
track_kwargs['name'] = data['title']
# FIXME what if file is uri - generated tag cache needs to allways make
# LOCAL_MUSIC_PATH relative paths or this code must handle uris
if 'musicbrainz_trackid' in data:
track_kwargs['musicbrainz_id'] = data['musicbrainz_trackid']
if 'musicbrainz_albumid' in data:
album_kwargs['musicbrainz_id'] = data['musicbrainz_albumid']
if 'musicbrainz_artistid' in data:
artist_kwargs['musicbrainz_id'] = data['musicbrainz_artistid']
if 'musicbrainz_albumartistid' in data:
albumartist_kwargs['musicbrainz_id'] = data['musicbrainz_albumartistid']
if data['file'][0] == '/':
path = data['file'][1:]
else:
path = data['file']
# FIXME newer mpd tag caches provide musicbrainz ids
if artist_kwargs:
artist = Artist(**artist_kwargs)
track_kwargs['artists'] = [artist]
if albumartist_kwargs:
albumartist = Artist(**albumartist_kwargs)
album_kwargs['artists'] = [albumartist]
if album_kwargs:
album = Album(**album_kwargs)
track_kwargs['album'] = album
track_kwargs['uri'] = path_to_uri(music_dir, path)
track_kwargs['length'] = int(data.get('time', 0)) * 1000

View File

@ -41,6 +41,20 @@ def track_to_mpd_format(track, position=None, cpid=None):
if position is not None and cpid is not None:
result.append(('Pos', position))
result.append(('Id', cpid))
if track.album is not None and track.album.musicbrainz_id is not None:
result.append(('MUSICBRAINZ_ALBUMID', track.album.musicbrainz_id))
# FIXME don't use first and best artist?
# FIXME don't duplicate following code?
if track.album is not None and track.album.artists:
artists = filter(lambda a: a.musicbrainz_id is not None, track.album.artists)
if artists:
result.append(('MUSICBRAINZ_ALBUMARTISTID', artists[0].musicbrainz_id))
if track.artists:
artists = filter(lambda a: a.musicbrainz_id is not None, track.artists)
if artists:
result.append(('MUSICBRAINZ_ARTISTID', artists[0].musicbrainz_id))
if track.musicbrainz_id is not None:
result.append(('MUSICBRAINZ_TRACKID', track.musicbrainz_id))
return result
MPD_KEY_ORDER = '''

View File

@ -38,15 +38,29 @@ class ImmutableObject(object):
def __ne__(self, other):
return not self.__eq__(other)
def copy(self, **kwargs):
def copy(self, **values):
"""
Copy the model with ``field`` updated to new value.
Examples::
# Returns a track with a new name
Track(name='foo').copy(name='bar')
# Return an album with a new number of tracks
Album(num_tracks=2).copy(num_tracks=5)
:param values: the model field to modify
:type values: dict
:rtype: new instance of the model being copied
"""
data = {}
for key in self.__dict__.keys():
public_key = key.lstrip('_')
data[public_key] = kwargs.pop(public_key, self.__dict__[key])
for key in kwargs.keys():
data[public_key] = values.pop(public_key, self.__dict__[key])
for key in values.keys():
if hasattr(self, key):
data[key] = kwargs.pop(key)
if kwargs:
data[key] = values.pop(key)
if values:
raise TypeError("copy() got an unexpected keyword argument '%s'" % key)
return self.__class__(**data)
@ -56,6 +70,8 @@ class Artist(ImmutableObject):
:type uri: string
:param name: artist name
:type name: string
:param musicbrainz_id: MusicBrainz ID
:type musicbrainz_id: string
"""
#: The artist URI. Read-only.
@ -64,6 +80,9 @@ class Artist(ImmutableObject):
#: The artist name. Read-only.
name = None
#: The MusicBrainz ID of the artist. Read-only.
musicbrainz_id = None
class Album(ImmutableObject):
"""
@ -75,6 +94,8 @@ class Album(ImmutableObject):
:type artists: list of :class:`Artist`
:param num_tracks: number of tracks in album
:type num_tracks: integer
:param musicbrainz_id: MusicBrainz ID
:type musicbrainz_id: string
"""
#: The album URI. Read-only.
@ -86,6 +107,9 @@ class Album(ImmutableObject):
#: The number of tracks in the album. Read-only.
num_tracks = 0
#: The MusicBrainz ID of the album. Read-only.
musicbrainz_id = None
def __init__(self, *args, **kwargs):
self._artists = frozenset(kwargs.pop('artists', []))
super(Album, self).__init__(*args, **kwargs)
@ -114,6 +138,8 @@ class Track(ImmutableObject):
:type length: integer
:param bitrate: bitrate in kbit/s
:type bitrate: integer
:param musicbrainz_id: MusicBrainz ID
:type musicbrainz_id: string
"""
#: The track URI. Read-only.
@ -137,6 +163,9 @@ class Track(ImmutableObject):
#: The track's bitrate in kbit/s. Read-only.
bitrate = None
#: The MusicBrainz ID of the track. Read-only.
musicbrainz_id = None
def __init__(self, *args, **kwargs):
self._artists = frozenset(kwargs.pop('artists', []))
super(Track, self).__init__(*args, **kwargs)

View File

@ -18,6 +18,8 @@ def translator(data):
artist_kwargs = {}
track_kwargs = {}
# FIXME replace with data.get('foo', None) ?
if 'album' in data:
album_kwargs['name'] = data['album']
@ -41,6 +43,18 @@ def translator(data):
if 'album-artist' in data:
albumartist_kwargs['name'] = data['album-artist']
if 'musicbrainz-trackid' in data:
track_kwargs['musicbrainz_id'] = data['musicbrainz-trackid']
if 'musicbrainz-artistid' in data:
artist_kwargs['musicbrainz_id'] = data['musicbrainz-artistid']
if 'musicbrainz-albumid' in data:
album_kwargs['musicbrainz_id'] = data['musicbrainz-albumid']
if 'musicbrainz-albumartistid' in data:
albumartist_kwargs['musicbrainz_id'] = data['musicbrainz-albumartistid']
if albumartist_kwargs:
album_kwargs['artists'] = [Artist(**albumartist_kwargs)]

View File

@ -136,3 +136,28 @@ class MPDTagCacheToTracksTest(unittest.TestCase):
data_folder(''))
uri = path_to_uri(data_folder('song1.mp3'))
self.assertEqual(set([Track(uri=uri, length=4000)]), tracks)
def test_musicbrainz_tagcache(self):
tracks = parse_mpd_tag_cache(data_folder('musicbrainz_tag_cache'),
data_folder(''))
artist = list(expected_tracks[0].artists)[0].copy(
musicbrainz_id='7364dea6-ca9a-48e3-be01-b44ad0d19897')
albumartist = list(expected_tracks[0].artists)[0].copy(
name='albumartistname',
musicbrainz_id='7364dea6-ca9a-48e3-be01-b44ad0d19897')
album = expected_tracks[0].album.copy(artists=[albumartist],
musicbrainz_id='cb5f1603-d314-4c9c-91e5-e295cfb125d2')
track = expected_tracks[0].copy(artists=[artist], album=album,
musicbrainz_id='90488461-8c1f-4a4e-826b-4c6dc70801f0')
self.assertEqual(track, list(tracks)[0])
def test_albumartist_tag_cache(self):
tracks = parse_mpd_tag_cache(data_folder('albumartist_tag_cache'),
data_folder(''))
uri = path_to_uri(data_folder('song1.mp3'))
artist = Artist(name='albumartistname')
album = expected_albums[0].copy(artists=[artist])
track = Track(name='trackname', artists=expected_artists, track_no=1,
album=album, length=4000, uri=uri)
self.assertEqual(track, list(tracks)[0])

View File

@ -0,0 +1,16 @@
info_begin
mpd_version: 0.14.2
fs_charset: UTF-8
info_end
songList begin
key: song1.mp3
file: /song1.mp3
Time: 4
Artist: name
Title: trackname
Album: albumname
AlbumArtist: albumartistname
Track: 1/2
Date: 2006
mtime: 1272319626
songList end

View File

@ -0,0 +1,20 @@
info_begin
mpd_version: 0.16.0
fs_charset: UTF-8
info_end
songList begin
key: song1.mp3
file: /song1.mp3
Time: 4
Artist: name
Title: trackname
Album: albumname
AlbumArtist: albumartistname
Track: 1/2
Date: 2006
MUSICBRAINZ_ALBUMID: cb5f1603-d314-4c9c-91e5-e295cfb125d2
MUSICBRAINZ_ALBUMARTISTID: 7364dea6-ca9a-48e3-be01-b44ad0d19897
MUSICBRAINZ_ARTISTID: 7364dea6-ca9a-48e3-be01-b44ad0d19897
MUSICBRAINZ_TRACKID: 90488461-8c1f-4a4e-826b-4c6dc70801f0
mtime: 1272319626
songList end

View File

@ -10,6 +10,17 @@ from mopidy.models import Album, Artist, Playlist, Track
from tests import data_folder, SkipTest
class TrackMpdFormatTest(unittest.TestCase):
track = Track(
uri=u'a uri',
artists=[Artist(name=u'an artist')],
name=u'a name',
album=Album(name=u'an album', num_tracks=13,
artists=[Artist(name=u'an other artist')]),
track_no=7,
date=dt.date(1977, 1, 1),
length=137000,
)
def setUp(self):
settings.LOCAL_MUSIC_PATH = '/dir/subdir'
mtime.set_fake_time(1234567)
@ -43,17 +54,7 @@ class TrackMpdFormatTest(unittest.TestCase):
self.assert_(('Id', 2) in result)
def test_track_to_mpd_format_for_nonempty_track(self):
track = Track(
uri=u'a uri',
artists=[Artist(name=u'an artist')],
name=u'a name',
album=Album(name=u'an album', num_tracks=13,
artists=[Artist(name=u'an other artist')]),
track_no=7,
date=dt.date(1977, 1, 1),
length=137000,
)
result = translator.track_to_mpd_format(track, position=9, cpid=122)
result = translator.track_to_mpd_format(self.track, position=9, cpid=122)
self.assert_(('file', 'a uri') in result)
self.assert_(('Time', 137) in result)
self.assert_(('Artist', 'an artist') in result)
@ -66,6 +67,30 @@ class TrackMpdFormatTest(unittest.TestCase):
self.assert_(('Id', 122) in result)
self.assertEqual(len(result), 10)
def test_track_to_mpd_format_musicbrainz_trackid(self):
track = self.track.copy(musicbrainz_id='foo')
result = translator.track_to_mpd_format(track)
self.assert_(('MUSICBRAINZ_TRACKID', 'foo') in result)
def test_track_to_mpd_format_musicbrainz_albumid(self):
album = self.track.album.copy(musicbrainz_id='foo')
track = self.track.copy(album=album)
result = translator.track_to_mpd_format(track)
self.assert_(('MUSICBRAINZ_ALBUMID', 'foo') in result)
def test_track_to_mpd_format_musicbrainz_albumid(self):
artist = list(self.track.artists)[0].copy(musicbrainz_id='foo')
album = self.track.album.copy(artists=[artist])
track = self.track.copy(album=album)
result = translator.track_to_mpd_format(track)
self.assert_(('MUSICBRAINZ_ALBUMARTISTID', 'foo') in result)
def test_track_to_mpd_format_musicbrainz_artistid(self):
artist = list(self.track.artists)[0].copy(musicbrainz_id='foo')
track = self.track.copy(artists=[artist])
result = translator.track_to_mpd_format(track)
self.assert_(('MUSICBRAINZ_ARTISTID', 'foo') in result)
def test_artists_to_mpd_format(self):
artists = [Artist(name=u'ABBA'), Artist(name=u'Beatles')]
translated = translator.artists_to_mpd_format(artists)

View File

@ -62,6 +62,13 @@ class ArtistTest(unittest.TestCase):
self.assertEqual(artist.name, name)
self.assertRaises(AttributeError, setattr, artist, 'name', None)
def test_musicbrainz_id(self):
mb_id = u'mb-id'
artist = Artist(musicbrainz_id=mb_id)
self.assertEqual(artist.musicbrainz_id, mb_id)
self.assertRaises(AttributeError, setattr, artist,
'musicbrainz_id', None)
def test_invalid_kwarg(self):
test = lambda: Artist(foo='baz')
self.assertRaises(TypeError, test)
@ -78,9 +85,15 @@ class ArtistTest(unittest.TestCase):
self.assertEqual(artist1, artist2)
self.assertEqual(hash(artist1), hash(artist2))
def test_eq_musibrainz_id(self):
artist1 = Artist(musicbrainz_id=u'id')
artist2 = Artist(musicbrainz_id=u'id')
self.assertEqual(artist1, artist2)
self.assertEqual(hash(artist1), hash(artist2))
def test_eq(self):
artist1 = Artist(uri=u'uri', name=u'name')
artist2 = Artist(uri=u'uri', name=u'name')
artist1 = Artist(uri=u'uri', name=u'name', musicbrainz_id='id')
artist2 = Artist(uri=u'uri', name=u'name', musicbrainz_id='id')
self.assertEqual(artist1, artist2)
self.assertEqual(hash(artist1), hash(artist2))
@ -102,9 +115,15 @@ class ArtistTest(unittest.TestCase):
self.assertNotEqual(artist1, artist2)
self.assertNotEqual(hash(artist1), hash(artist2))
def test_ne_musicbrainz_id(self):
artist1 = Artist(musicbrainz_id=u'id1')
artist2 = Artist(musicbrainz_id=u'id2')
self.assertNotEqual(artist1, artist2)
self.assertNotEqual(hash(artist1), hash(artist2))
def test_ne(self):
artist1 = Artist(uri=u'uri1', name=u'name1')
artist2 = Artist(uri=u'uri2', name=u'name2')
artist1 = Artist(uri=u'uri1', name=u'name1', musicbrainz_id='id1')
artist2 = Artist(uri=u'uri2', name=u'name2', musicbrainz_id='id2')
self.assertNotEqual(artist1, artist2)
self.assertNotEqual(hash(artist1), hash(artist2))
@ -134,6 +153,13 @@ class AlbumTest(unittest.TestCase):
self.assertEqual(album.num_tracks, num_tracks)
self.assertRaises(AttributeError, setattr, album, 'num_tracks', None)
def test_musicbrainz_id(self):
mb_id = u'mb-id'
album = Album(musicbrainz_id=mb_id)
self.assertEqual(album.musicbrainz_id, mb_id)
self.assertRaises(AttributeError, setattr, album,
'musicbrainz_id', None)
def test_invalid_kwarg(self):
test = lambda: Album(foo='baz')
self.assertRaises(TypeError, test)
@ -171,10 +197,16 @@ class AlbumTest(unittest.TestCase):
self.assertEqual(album1, album2)
self.assertEqual(hash(album1), hash(album2))
def test_eq_musibrainz_id(self):
album1 = Album(musicbrainz_id=u'id')
album2 = Album(musicbrainz_id=u'id')
self.assertEqual(album1, album2)
self.assertEqual(hash(album1), hash(album2))
def test_eq(self):
artists = [Artist()]
album1 = Album(name=u'name', uri=u'uri', artists=artists, num_tracks=2)
album2 = Album(name=u'name', uri=u'uri', artists=artists, num_tracks=2)
album1 = Album(name=u'name', uri=u'uri', artists=artists, num_tracks=2, musicbrainz_id='id')
album2 = Album(name=u'name', uri=u'uri', artists=artists, num_tracks=2, musicbrainz_id='id')
self.assertEqual(album1, album2)
self.assertEqual(hash(album1), hash(album2))
@ -208,11 +240,19 @@ class AlbumTest(unittest.TestCase):
self.assertNotEqual(album1, album2)
self.assertNotEqual(hash(album1), hash(album2))
def test_ne_musicbrainz_id(self):
album1 = Album(musicbrainz_id=u'id1')
album2 = Album(musicbrainz_id=u'id2')
self.assertNotEqual(album1, album2)
self.assertNotEqual(hash(album1), hash(album2))
def test_ne(self):
album1 = Album(name=u'name1', uri=u'uri1',
artists=[Artist(name=u'name1')], num_tracks=1)
artists=[Artist(name=u'name1')], num_tracks=1,
musicbrainz_id='id1')
album2 = Album(name=u'name2', uri=u'uri2',
artists=[Artist(name=u'name2')], num_tracks=2)
artists=[Artist(name=u'name2')], num_tracks=2,
musicbrainz_id='id2')
self.assertNotEqual(album1, album2)
self.assertNotEqual(hash(album1), hash(album2))
@ -266,6 +306,13 @@ class TrackTest(unittest.TestCase):
self.assertEqual(track.bitrate, bitrate)
self.assertRaises(AttributeError, setattr, track, 'bitrate', None)
def test_musicbrainz_id(self):
mb_id = u'mb-id'
track = Track(musicbrainz_id=mb_id)
self.assertEqual(track.musicbrainz_id, mb_id)
self.assertRaises(AttributeError, setattr, track,
'musicbrainz_id', None)
def test_invalid_kwarg(self):
test = lambda: Track(foo='baz')
self.assertRaises(TypeError, test)
@ -329,14 +376,22 @@ class TrackTest(unittest.TestCase):
self.assertEqual(track1, track2)
self.assertEqual(hash(track1), hash(track2))
def test_eq_musibrainz_id(self):
track1 = Track(musicbrainz_id=u'id')
track2 = Track(musicbrainz_id=u'id')
self.assertEqual(track1, track2)
self.assertEqual(hash(track1), hash(track2))
def test_eq(self):
date = dt.date.today()
artists = [Artist()]
album = Album()
track1 = Track(uri=u'uri', name=u'name', artists=artists, album=album,
track_no=1, date=date, length=100, bitrate=100)
track_no=1, date=date, length=100, bitrate=100,
musicbrainz_id='id')
track2 = Track(uri=u'uri', name=u'name', artists=artists, album=album,
track_no=1, date=date, length=100, bitrate=100)
track_no=1, date=date, length=100, bitrate=100,
musicbrainz_id='id')
self.assertEqual(track1, track2)
self.assertEqual(hash(track1), hash(track2))
@ -394,14 +449,21 @@ class TrackTest(unittest.TestCase):
self.assertNotEqual(track1, track2)
self.assertNotEqual(hash(track1), hash(track2))
def test_ne_musicbrainz_id(self):
track1 = Track(musicbrainz_id=u'id1')
track2 = Track(musicbrainz_id=u'id2')
self.assertNotEqual(track1, track2)
self.assertNotEqual(hash(track1), hash(track2))
def test_ne(self):
track1 = Track(uri=u'uri1', name=u'name1',
artists=[Artist(name=u'name1')], album=Album(name=u'name1'),
track_no=1, date=dt.date.today(), length=100, bitrate=100)
track_no=1, date=dt.date.today(), length=100, bitrate=100,
musicbrainz_id='id1')
track2 = Track(uri=u'uri2', name=u'name2',
artists=[Artist(name=u'name2')], album=Album(name=u'name2'),
track_no=2, date=dt.date.today()-dt.timedelta(days=1),
length=200, bitrate=200)
length=200, bitrate=200, musicbrainz_id='id2')
self.assertNotEqual(track1, track2)
self.assertNotEqual(hash(track1), hash(track2))

View File

@ -25,19 +25,26 @@ class TranslatorTest(unittest.TestCase):
'date': FakeGstDate(2006, 1, 1,),
'container-format': u'ID3 tag',
'duration': 4531,
'musicbrainz-trackid': 'mbtrackid',
'musicbrainz-albumid': 'mbalbumid',
'musicbrainz-artistid': 'mbartistid',
'musicbrainz-albumartistid': 'mbalbumartistid',
}
self.album = {
'name': 'albumname',
'num_tracks': 2,
'musicbrainz_id': 'mbalbumid',
}
self.artist = {
'name': 'name',
'musicbrainz_id': 'mbartistid',
}
self.albumartist = {
'name': 'albumartistname',
'musicbrainz_id': 'mbalbumartistid',
}
self.track = {
@ -46,6 +53,7 @@ class TranslatorTest(unittest.TestCase):
'date': date(2006, 1, 1),
'track_no': 1,
'length': 4531,
'musicbrainz_id': 'mbtrackid',
}
def build_track(self):
@ -78,21 +86,41 @@ class TranslatorTest(unittest.TestCase):
del self.track['name']
self.check()
def test_missing_track_musicbrainz_id(self):
del self.data['musicbrainz-trackid']
del self.track['musicbrainz_id']
self.check()
def test_missing_album_name(self):
del self.data['album']
del self.album['name']
self.check()
def test_missing_album_musicbrainz_id(self):
del self.data['musicbrainz-albumid']
del self.album['musicbrainz_id']
self.check()
def test_missing_artist_name(self):
del self.data['artist']
del self.artist['name']
self.check()
def test_missing_artist_musicbrainz_id(self):
del self.data['musicbrainz-artistid']
del self.artist['musicbrainz_id']
self.check()
def test_missing_album_artist(self):
del self.data['album-artist']
del self.albumartist['name']
self.check()
def test_missing_album_artist_musicbrainz_id(self):
del self.data['musicbrainz-albumartistid']
del self.albumartist['musicbrainz_id']
self.check()
def test_missing_date(self):
del self.data['date']
del self.track['date']