diff --git a/mopidy/backends/local/library.py b/mopidy/backends/local/library.py index 6219b267..1ba8813e 100644 --- a/mopidy/backends/local/library.py +++ b/mopidy/backends/local/library.py @@ -88,6 +88,7 @@ class LocalLibraryProvider(base.BaseLibraryProvider): track_no_filter = lambda t: q == t.track_no genre_filter = lambda t: t.genre and q == t.genre date_filter = lambda t: q == t.date + comment_filter = lambda t: q == t.comment any_filter = lambda t: ( uri_filter(t) or track_name_filter(t) or @@ -98,7 +99,8 @@ class LocalLibraryProvider(base.BaseLibraryProvider): performer_filter(t) or track_no_filter(t) or genre_filter(t) or - date_filter(t)) + date_filter(t) or + comment_filter(t)) if field == 'uri': result_tracks = filter(uri_filter, result_tracks) @@ -120,6 +122,8 @@ class LocalLibraryProvider(base.BaseLibraryProvider): result_tracks = filter(genre_filter, result_tracks) elif field == 'date': result_tracks = filter(date_filter, result_tracks) + elif field == 'comment': + result_tracks = filter(comment_filter, result_tracks) elif field == 'any': result_tracks = filter(any_filter, result_tracks) else: @@ -163,6 +167,7 @@ class LocalLibraryProvider(base.BaseLibraryProvider): track_no_filter = lambda t: q == t.track_no genre_filter = lambda t: t.genre and q in t.genre.lower() date_filter = lambda t: t.date and t.date.startswith(q) + comment_filter = lambda t: t.comment and q in t.comment.lower() any_filter = lambda t: ( uri_filter(t) or track_name_filter(t) or @@ -173,7 +178,8 @@ class LocalLibraryProvider(base.BaseLibraryProvider): performer_filter(t) or track_no_filter(t) or genre_filter(t) or - date_filter(t)) + date_filter(t) or + comment_filter(t)) if field == 'uri': result_tracks = filter(uri_filter, result_tracks) @@ -195,6 +201,8 @@ class LocalLibraryProvider(base.BaseLibraryProvider): result_tracks = filter(genre_filter, result_tracks) elif field == 'date': result_tracks = filter(date_filter, result_tracks) + elif field == 'comment': + result_tracks = filter(comment_filter, result_tracks) elif field == 'any': result_tracks = filter(any_filter, result_tracks) else: diff --git a/mopidy/backends/local/translator.py b/mopidy/backends/local/translator.py index 2134d7d1..63c46d01 100644 --- a/mopidy/backends/local/translator.py +++ b/mopidy/backends/local/translator.py @@ -142,6 +142,9 @@ def _convert_mpd_data(data, tracks): if 'date' in data: track_kwargs['date'] = data['date'] + if 'comment' in data: + track_kwargs['comment'] = data['comment'] + if 'musicbrainz_trackid' in data: track_kwargs['musicbrainz_id'] = data['musicbrainz_trackid'] diff --git a/mopidy/frontends/mpd/protocol/music_db.py b/mopidy/frontends/mpd/protocol/music_db.py index dbdb8b69..0d84171c 100644 --- a/mopidy/frontends/mpd/protocol/music_db.py +++ b/mopidy/frontends/mpd/protocol/music_db.py @@ -271,8 +271,9 @@ def _list_composer(context, query): composers = set() results = context.core.library.find_exact(**query).get() for track in _get_tracks(results): - if track.composer and track.composer.name: - composers.add(('Composer', track.composer.name)) + for composer in track.composers: + if composer.name: + composers.add(('Composer', composer.name)) return composers @@ -280,8 +281,9 @@ def _list_performer(context, query): performers = set() results = context.core.library.find_exact(**query).get() for track in _get_tracks(results): - if track.performer and track.performer.name: - performers.add(('Performer', track.performer.name)) + for performer in track.performers: + if performer.name: + performers.add(('Performer', performer.name)) return performers diff --git a/tests/backends/local/library_test.py b/tests/backends/local/library_test.py index d8085691..39980fcc 100644 --- a/tests/backends/local/library_test.py +++ b/tests/backends/local/library_test.py @@ -47,7 +47,9 @@ class LocalLibraryProviderTest(unittest.TestCase): Track( uri='local:track:path4', name='track4', artists=[artists[2]], album=albums[3], - date='2004', length=60000, track_no=4), + date='2004', length=60000, track_no=4, + comment='Music server with support for MPD/HTTP clients ' + 'and Spotify streaming http://www.mopidy.com'), Track( uri='local:track:path5', name='track5', genre='genre1', album=albums[3], length=4000, composers=[artists[4]]), @@ -145,6 +147,9 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.find_exact(track_no=['no_match']) self.assertEqual(list(result[0].tracks), []) + result = self.library.find_exact(comment=['fake comment']) + self.assertEqual(list(result[0].tracks), []) + result = self.library.find_exact(uri=['fake uri']) self.assertEqual(list(result[0].tracks), []) @@ -160,7 +165,7 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.find_exact(uri=track_2_uri) self.assertEqual(list(result[0].tracks), self.tracks[1:2]) - def test_find_exact_track(self): + def test_find_exact_track_name(self): result = self.library.find_exact(track_name=['track1']) self.assertEqual(list(result[0].tracks), self.tracks[:1]) @@ -181,10 +186,16 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.find_exact(composer=['artist5']) self.assertEqual(list(result[0].tracks), self.tracks[4:5]) + result = self.library.find_exact(composer=['artist6']) + self.assertEqual(list(result[0].tracks), []) + def test_find_exact_performer(self): result = self.library.find_exact(performer=['artist6']) self.assertEqual(list(result[0].tracks), self.tracks[5:6]) + result = self.library.find_exact(performer=['artist5']) + self.assertEqual(list(result[0].tracks), []) + def test_find_exact_album(self): result = self.library.find_exact(album=['album1']) self.assertEqual(list(result[0].tracks), self.tracks[:1]) @@ -229,6 +240,16 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.find_exact(date=['2002']) self.assertEqual(list(result[0].tracks), self.tracks[1:2]) + def test_find_exact_comment(self): + result = self.library.find_exact( + comment=['Music server with support for MPD/HTTP clients ' + 'and Spotify streaming http://www.mopidy.com']) + self.assertEqual(list(result[0].tracks), self.tracks[3:4]) + + result = self.library.find_exact( + comment=['Music server with support for MPD/HTTP clients']) + self.assertEqual(list(result[0].tracks), []) + def test_find_exact_any(self): # Matches on track artist result = self.library.find_exact(any=['artist1']) @@ -237,7 +258,7 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.find_exact(any=['artist2']) self.assertEqual(list(result[0].tracks), self.tracks[1:2]) - # Matches on track + # Matches on track name result = self.library.find_exact(any=['track1']) self.assertEqual(list(result[0].tracks), self.tracks[:1]) @@ -268,10 +289,16 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.find_exact(any=['genre2']) self.assertEqual(list(result[0].tracks), self.tracks[5:6]) - # Matches on track year + # Matches on track date result = self.library.find_exact(any=['2002']) self.assertEqual(list(result[0].tracks), self.tracks[1:2]) + # Matches on track comment + result = self.library.find_exact( + any=['Music server with support for MPD/HTTP clients ' + 'and Spotify streaming http://www.mopidy.com']) + self.assertEqual(list(result[0].tracks), self.tracks[3:4]) + # Matches on URI result = self.library.find_exact(any=['local:track:path1']) self.assertEqual(list(result[0].tracks), self.tracks[:1]) @@ -308,6 +335,9 @@ class LocalLibraryProviderTest(unittest.TestCase): test = lambda: self.library.find_exact(date=['']) self.assertRaises(LookupError, test) + test = lambda: self.library.find_exact(comment=['']) + self.assertRaises(LookupError, test) + test = lambda: self.library.find_exact(any=['']) self.assertRaises(LookupError, test) @@ -342,6 +372,9 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.search(date=['unknown date']) self.assertEqual(list(result[0].tracks), []) + result = self.library.search(comment=['unknown comment']) + self.assertEqual(list(result[0].tracks), []) + result = self.library.search(uri=['unknown uri']) self.assertEqual(list(result[0].tracks), []) @@ -355,7 +388,7 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.search(uri=['TH2']) self.assertEqual(list(result[0].tracks), self.tracks[1:2]) - def test_search_track(self): + def test_search_track_name(self): result = self.library.search(track_name=['Rack1']) self.assertEqual(list(result[0].tracks), self.tracks[:1]) @@ -424,6 +457,13 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.search(track_no=['2']) self.assertEqual(list(result[0].tracks), self.tracks[1:2]) + def test_search_comment(self): + result = self.library.search(comment=['mopidy']) + self.assertEqual(list(result[0].tracks), self.tracks[3:4]) + + result = self.library.search(comment=['Potify']) + self.assertEqual(list(result[0].tracks), self.tracks[3:4]) + def test_search_any(self): # Matches on track artist result = self.library.search(any=['Tist1']) @@ -460,6 +500,13 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.search(any=['Enre2']) self.assertEqual(list(result[0].tracks), self.tracks[5:6]) + # Matches on track comment + result = self.library.search(any=['http']) + self.assertEqual(list(result[0].tracks), self.tracks[3:4]) + + result = self.library.search(any=['streaming']) + self.assertEqual(list(result[0].tracks), self.tracks[3:4]) + # Matches on URI result = self.library.search(any=['TH1']) self.assertEqual(list(result[0].tracks), self.tracks[:1]) @@ -493,6 +540,9 @@ class LocalLibraryProviderTest(unittest.TestCase): test = lambda: self.library.search(date=['']) self.assertRaises(LookupError, test) + test = lambda: self.library.search(comment=['']) + self.assertRaises(LookupError, test) + test = lambda: self.library.search(uri=['']) self.assertRaises(LookupError, test) diff --git a/tests/backends/local/translator_test.py b/tests/backends/local/translator_test.py index 07990e47..5623c787 100644 --- a/tests/backends/local/translator_test.py +++ b/tests/backends/local/translator_test.py @@ -147,7 +147,9 @@ class MPDTagCacheToTracksTest(unittest.TestCase): album = Album(name='æøå', artists=artists) track = Track( uri='local:track:song1.mp3', name='æøå', artists=artists, - album=album, length=4000, last_modified=1272319626) + composers=artists, performers=artists, genre='æøå', + album=album, length=4000, last_modified=1272319626, + comment='æøå&^`ൂ㔶') self.assertEqual(track, list(tracks)[0]) diff --git a/tests/data/library_tag_cache b/tests/data/library_tag_cache index 7d23bddc..fb89a26d 100644 --- a/tests/data/library_tag_cache +++ b/tests/data/library_tag_cache @@ -37,6 +37,7 @@ Title: track4 Album: album4 Date: 2004 Track: 4 +Comment: Music server with support for MPD/HTTP clients and Spotify streaming http://www.mopidy.com Time: 60 key: key5 file: /path5 diff --git a/tests/data/utf8_tag_cache b/tests/data/utf8_tag_cache index 6f6abe60..83fbcad4 100644 --- a/tests/data/utf8_tag_cache +++ b/tests/data/utf8_tag_cache @@ -8,7 +8,11 @@ file: /song1.mp3 Time: 4 Artist: æøå AlbumArtist: æøå +Composer: æøå +Performer: æøå Title: æøå Album: æøå +Genre: æøå +Comment: æøå&^`ൂ㔶 mtime: 1272319626 songList end diff --git a/tests/frontends/mpd/protocol/music_db_test.py b/tests/frontends/mpd/protocol/music_db_test.py index 60c30372..0f6bbf10 100644 --- a/tests/frontends/mpd/protocol/music_db_test.py +++ b/tests/frontends/mpd/protocol/music_db_test.py @@ -261,6 +261,22 @@ class MusicDatabaseFindTest(protocol.BaseTestCase): self.sendRequest('find albumartist "what"') self.assertInResponse('OK') + def test_find_composer(self): + self.sendRequest('find "composer" "what"') + self.assertInResponse('OK') + + def test_find_composer_without_quotes(self): + self.sendRequest('find composer "what"') + self.assertInResponse('OK') + + def test_find_performer(self): + self.sendRequest('find "performer" "what"') + self.assertInResponse('OK') + + def test_find_performer_without_quotes(self): + self.sendRequest('find performer "what"') + self.assertInResponse('OK') + def test_find_filename(self): self.sendRequest('find "filename" "afilename"') self.assertInResponse('OK') @@ -297,6 +313,14 @@ class MusicDatabaseFindTest(protocol.BaseTestCase): self.sendRequest('find "track" ""') self.assertInResponse('OK') + def test_find_genre(self): + self.sendRequest('find "genre" "what"') + self.assertInResponse('OK') + + def test_find_genre_without_quotes(self): + self.sendRequest('find genre "what"') + self.assertInResponse('OK') + def test_find_date(self): self.sendRequest('find "date" "2002-01-01"') self.assertInResponse('OK') @@ -456,6 +480,135 @@ class MusicDatabaseListTest(protocol.BaseTestCase): self.sendRequest('list "albumartist"') self.assertNotInResponse('Artist: ') + self.assertNotInResponse('Albumartist: ') + self.assertNotInResponse('Composer: ') + self.assertNotInResponse('Performer: ') + self.assertInResponse('OK') + + ### Composer + + def test_list_composer_with_quotes(self): + self.sendRequest('list "composer"') + self.assertInResponse('OK') + + def test_list_composer_without_quotes(self): + self.sendRequest('list composer') + self.assertInResponse('OK') + + def test_list_composer_without_quotes_and_capitalized(self): + self.sendRequest('list Composer') + self.assertInResponse('OK') + + def test_list_composer_with_query_of_one_token(self): + self.sendRequest('list "composer" "anartist"') + self.assertEqualResponse( + 'ACK [2@0] {list} should be "Album" for 3 arguments') + + def test_list_composer_with_unknown_field_in_query_returns_ack(self): + self.sendRequest('list "composer" "foo" "bar"') + self.assertEqualResponse('ACK [2@0] {list} not able to parse args') + + def test_list_composer_by_artist(self): + self.sendRequest('list "composer" "artist" "anartist"') + self.assertInResponse('OK') + + def test_list_composer_by_album(self): + self.sendRequest('list "composer" "album" "analbum"') + self.assertInResponse('OK') + + def test_list_composer_by_full_date(self): + self.sendRequest('list "composer" "date" "2001-01-01"') + self.assertInResponse('OK') + + def test_list_composer_by_year(self): + self.sendRequest('list "composer" "date" "2001"') + self.assertInResponse('OK') + + def test_list_composer_by_genre(self): + self.sendRequest('list "composer" "genre" "agenre"') + self.assertInResponse('OK') + + def test_list_composer_by_artist_and_album(self): + self.sendRequest( + 'list "composer" "artist" "anartist" "album" "analbum"') + self.assertInResponse('OK') + + def test_list_composer_without_filter_value(self): + self.sendRequest('list "composer" "artist" ""') + self.assertInResponse('OK') + + def test_list_composer_should_not_return_artists_without_names(self): + self.backend.library.dummy_find_exact_result = SearchResult( + tracks=[Track(composers=[Artist(name='')])]) + + self.sendRequest('list "composer"') + self.assertNotInResponse('Artist: ') + self.assertNotInResponse('Albumartist: ') + self.assertNotInResponse('Composer: ') + self.assertNotInResponse('Performer: ') + self.assertInResponse('OK') + + ### Performer + + def test_list_performer_with_quotes(self): + self.sendRequest('list "performer"') + self.assertInResponse('OK') + + def test_list_performer_without_quotes(self): + self.sendRequest('list performer') + self.assertInResponse('OK') + + def test_list_performer_without_quotes_and_capitalized(self): + self.sendRequest('list Albumartist') + self.assertInResponse('OK') + + def test_list_performer_with_query_of_one_token(self): + self.sendRequest('list "performer" "anartist"') + self.assertEqualResponse( + 'ACK [2@0] {list} should be "Album" for 3 arguments') + + def test_list_performer_with_unknown_field_in_query_returns_ack(self): + self.sendRequest('list "performer" "foo" "bar"') + self.assertEqualResponse('ACK [2@0] {list} not able to parse args') + + def test_list_performer_by_artist(self): + self.sendRequest('list "performer" "artist" "anartist"') + self.assertInResponse('OK') + + def test_list_performer_by_album(self): + self.sendRequest('list "performer" "album" "analbum"') + self.assertInResponse('OK') + + def test_list_performer_by_full_date(self): + self.sendRequest('list "performer" "date" "2001-01-01"') + self.assertInResponse('OK') + + def test_list_performer_by_year(self): + self.sendRequest('list "performer" "date" "2001"') + self.assertInResponse('OK') + + def test_list_performer_by_genre(self): + self.sendRequest('list "performer" "genre" "agenre"') + self.assertInResponse('OK') + + def test_list_performer_by_artist_and_album(self): + self.sendRequest( + 'list "performer" "artist" "anartist" "album" "analbum"') + self.assertInResponse('OK') + + def test_list_performer_without_filter_value(self): + self.sendRequest('list "performer" "artist" ""') + self.assertInResponse('OK') + + def test_list_performer_should_not_return_artists_without_names(self): + self.backend.library.dummy_find_exact_result = SearchResult( + tracks=[Track(performers=[Artist(name='')])]) + + self.sendRequest('list "performer"') + self.assertNotInResponse('Artist: ') + self.assertNotInResponse('Albumartist: ') + self.assertNotInResponse('Composer: ') + self.assertNotInResponse('Performer: ') self.assertInResponse('OK') ### Album @@ -492,6 +645,14 @@ class MusicDatabaseListTest(protocol.BaseTestCase): self.sendRequest('list "album" "albumartist" "anartist"') self.assertInResponse('OK') + def test_list_album_by_composer(self): + self.sendRequest('list "album" "composer" "anartist"') + self.assertInResponse('OK') + + def test_list_album_by_performer(self): + self.sendRequest('list "album" "performer" "anartist"') + self.assertInResponse('OK') + def test_list_album_by_full_date(self): self.sendRequest('list "album" "date" "2001-01-01"') self.assertInResponse('OK') @@ -679,6 +840,30 @@ class MusicDatabaseSearchTest(protocol.BaseTestCase): self.sendRequest('search "albumartist" ""') self.assertInResponse('OK') + def test_search_composer(self): + self.sendRequest('search "composer" "acomposer"') + self.assertInResponse('OK') + + def test_search_composer_without_quotes(self): + self.sendRequest('search composer "acomposer"') + self.assertInResponse('OK') + + def test_search_composer_without_filter_value(self): + self.sendRequest('search "composer" ""') + self.assertInResponse('OK') + + def test_search_performer(self): + self.sendRequest('search "performer" "aperformer"') + self.assertInResponse('OK') + + def test_search_performer_without_quotes(self): + self.sendRequest('search performer "aperformer"') + self.assertInResponse('OK') + + def test_search_performer_without_filter_value(self): + self.sendRequest('search "performer" ""') + self.assertInResponse('OK') + def test_search_filename(self): self.sendRequest('search "filename" "afilename"') self.assertInResponse('OK') @@ -739,6 +924,18 @@ class MusicDatabaseSearchTest(protocol.BaseTestCase): self.sendRequest('search "track" ""') self.assertInResponse('OK') + def test_search_genre(self): + self.sendRequest('search "genre" "agenre"') + self.assertInResponse('OK') + + def test_search_genre_without_quotes(self): + self.sendRequest('search genre "agenre"') + self.assertInResponse('OK') + + def test_search_genre_without_filter_value(self): + self.sendRequest('search "genre" ""') + self.assertInResponse('OK') + def test_search_date(self): self.sendRequest('search "date" "2002-01-01"') self.assertInResponse('OK') diff --git a/tests/frontends/mpd/translator_test.py b/tests/frontends/mpd/translator_test.py index 96d5f316..1b89c283 100644 --- a/tests/frontends/mpd/translator_test.py +++ b/tests/frontends/mpd/translator_test.py @@ -17,7 +17,12 @@ class TrackMpdFormatTest(unittest.TestCase): album=Album(name='an album', num_tracks=13, artists=[Artist(name='an other artist')]), track_no=7, + composers=[Artist(name='a composer')], + performers=[Artist(name='a performer')], + genre='a genre', date=datetime.date(1977, 1, 1), + disc_no='1', + comment='a comment', length=137000, ) @@ -61,11 +66,16 @@ class TrackMpdFormatTest(unittest.TestCase): self.assertIn(('Title', 'a name'), result) self.assertIn(('Album', 'an album'), result) self.assertIn(('AlbumArtist', 'an other artist'), result) + self.assertIn(('Composer', 'a composer'), result) + self.assertIn(('Performer', 'a performer'), result) + self.assertIn(('Genre', 'a genre'), result) self.assertIn(('Track', '7/13'), result) self.assertIn(('Date', datetime.date(1977, 1, 1)), result) + self.assertIn(('Disc', '1'), result) + self.assertIn(('Comment', 'a comment'), result) self.assertIn(('Pos', 9), result) self.assertIn(('Id', 122), result) - self.assertEqual(len(result), 10) + self.assertEqual(len(result), 15) def test_track_to_mpd_format_musicbrainz_trackid(self): track = self.track.copy(musicbrainz_id='foo')