Merge pull request #288 from jodal/feature/mpd-album-artist-search-results

Include albums and artists in MPD search results
This commit is contained in:
Thomas Adamcik 2012-12-23 09:52:02 -08:00
commit bbd8630d88
3 changed files with 119 additions and 4 deletions

View File

@ -70,6 +70,12 @@ v0.11.0 (in development)
- Make ``seek`` and ``seekid`` not restart the current track before seeking in - Make ``seek`` and ``seekid`` not restart the current track before seeking in
it. it.
- Include fake tracks representing albums and artists in the search results.
When these are added to the tracklist, they expand to either all tracks in
the album or all tracks by the artist. This makes it easy to play full albums
in proper order, which is a feature that have been frequently requested.
(Fixes: :issue:`67`, :issue:`148`)
**Internal changes** **Internal changes**
*Models:* *Models:*

View File

@ -1,7 +1,9 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import functools
import itertools import itertools
from mopidy.models import Track
from mopidy.frontends.mpd import translator from mopidy.frontends.mpd import translator
from mopidy.frontends.mpd.exceptions import MpdNotImplemented from mopidy.frontends.mpd.exceptions import MpdNotImplemented
from mopidy.frontends.mpd.protocol import handle_request, stored_playlists from mopidy.frontends.mpd.protocol import handle_request, stored_playlists
@ -12,8 +14,29 @@ QUERY_RE = (
r'[Tt]itle|[Aa]ny)"? "[^"]*"\s?)+)$') r'[Tt]itle|[Aa]ny)"? "[^"]*"\s?)+)$')
def _get_tracks(search_results): def _get_field(field, search_results):
return list(itertools.chain(*[r.tracks for r in search_results])) return list(itertools.chain(*[getattr(r, field) for r in search_results]))
_get_albums = functools.partial(_get_field, 'albums')
_get_artists = functools.partial(_get_field, 'artists')
_get_tracks = functools.partial(_get_field, 'tracks')
def _album_as_track(album):
return Track(
uri=album.uri,
name='Album: ' + album.name,
artists=album.artists,
album=album,
date=album.date)
def _artist_as_track(artist):
return Track(
uri=artist.uri,
name='Artist: ' + artist.name,
artists=[artist])
@handle_request(r'^count "(?P<tag>[^"]+)" "(?P<needle>[^"]*)"$') @handle_request(r'^count "(?P<tag>[^"]+)" "(?P<needle>[^"]*)"$')
@ -62,7 +85,13 @@ def find(context, mpd_query):
except ValueError: except ValueError:
return return
results = context.core.library.find_exact(**query).get() results = context.core.library.find_exact(**query).get()
return translator.tracks_to_mpd_format(_get_tracks(results)) result_tracks = []
if 'artist' not in query:
result_tracks += [_artist_as_track(a) for a in _get_artists(results)]
if 'album' not in query:
result_tracks += [_album_as_track(a) for a in _get_albums(results)]
result_tracks += _get_tracks(results)
return translator.tracks_to_mpd_format(result_tracks)
@handle_request(r'^findadd ' + QUERY_RE) @handle_request(r'^findadd ' + QUERY_RE)
@ -304,7 +333,10 @@ def search(context, mpd_query):
except ValueError: except ValueError:
return return
results = context.core.library.search(**query).get() results = context.core.library.search(**query).get()
return translator.tracks_to_mpd_format(_get_tracks(results)) artists = [_artist_as_track(a) for a in _get_artists(results)]
albums = [_album_as_track(a) for a in _get_albums(results)]
tracks = _get_tracks(results)
return translator.tracks_to_mpd_format(artists + albums + tracks)
@handle_request(r'^searchadd ' + QUERY_RE) @handle_request(r'^searchadd ' + QUERY_RE)

View File

@ -115,6 +115,66 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase):
class MusicDatabaseFindTest(protocol.BaseTestCase): class MusicDatabaseFindTest(protocol.BaseTestCase):
def test_find_includes_fake_artist_and_album_tracks(self):
self.backend.library.dummy_find_exact_result = SearchResult(
albums=[Album(uri='dummy:album:a', name='A', date='2001')],
artists=[Artist(uri='dummy:artist:b', name='B')],
tracks=[Track(uri='dummy:track:c', name='C')])
self.sendRequest('find "any" "foo"')
self.assertInResponse('file: dummy:artist:b')
self.assertInResponse('Title: Artist: B')
self.assertInResponse('file: dummy:album:a')
self.assertInResponse('Title: Album: A')
self.assertInResponse('Date: 2001')
self.assertInResponse('file: dummy:track:c')
self.assertInResponse('Title: C')
self.assertInResponse('OK')
def test_find_artist_does_not_include_fake_artist_tracks(self):
self.backend.library.dummy_find_exact_result = SearchResult(
albums=[Album(uri='dummy:album:a', name='A', date='2001')],
artists=[Artist(uri='dummy:artist:b', name='B')],
tracks=[Track(uri='dummy:track:c', name='C')])
self.sendRequest('find "artist" "foo"')
self.assertNotInResponse('file: dummy:artist:b')
self.assertNotInResponse('Title: Artist: B')
self.assertInResponse('file: dummy:album:a')
self.assertInResponse('Title: Album: A')
self.assertInResponse('Date: 2001')
self.assertInResponse('file: dummy:track:c')
self.assertInResponse('Title: C')
self.assertInResponse('OK')
def test_find_artist_and_album_does_not_include_fake_tracks(self):
self.backend.library.dummy_find_exact_result = SearchResult(
albums=[Album(uri='dummy:album:a', name='A', date='2001')],
artists=[Artist(uri='dummy:artist:b', name='B')],
tracks=[Track(uri='dummy:track:c', name='C')])
self.sendRequest('find "artist" "foo" "album" "bar"')
self.assertNotInResponse('file: dummy:artist:b')
self.assertNotInResponse('Title: Artist: B')
self.assertNotInResponse('file: dummy:album:a')
self.assertNotInResponse('Title: Album: A')
self.assertNotInResponse('Date: 2001')
self.assertInResponse('file: dummy:track:c')
self.assertInResponse('Title: C')
self.assertInResponse('OK')
def test_find_album(self): def test_find_album(self):
self.sendRequest('find "album" "what"') self.sendRequest('find "album" "what"')
self.assertInResponse('OK') self.assertInResponse('OK')
@ -420,6 +480,23 @@ class MusicDatabaseListTest(protocol.BaseTestCase):
class MusicDatabaseSearchTest(protocol.BaseTestCase): class MusicDatabaseSearchTest(protocol.BaseTestCase):
def test_search(self):
self.backend.library.dummy_search_result = SearchResult(
albums=[Album(uri='dummy:album:a', name='A')],
artists=[Artist(uri='dummy:artist:b', name='B')],
tracks=[Track(uri='dummy:track:c', name='C')])
self.sendRequest('search "any" "foo"')
self.assertInResponse('file: dummy:album:a')
self.assertInResponse('Title: Album: A')
self.assertInResponse('file: dummy:artist:b')
self.assertInResponse('Title: Artist: B')
self.assertInResponse('file: dummy:track:c')
self.assertInResponse('Title: C')
self.assertInResponse('OK')
def test_search_album(self): def test_search_album(self):
self.sendRequest('search "album" "analbum"') self.sendRequest('search "album" "analbum"')
self.assertInResponse('OK') self.assertInResponse('OK')