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
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**
*Models:*

View File

@ -1,7 +1,9 @@
from __future__ import unicode_literals
import functools
import itertools
from mopidy.models import Track
from mopidy.frontends.mpd import translator
from mopidy.frontends.mpd.exceptions import MpdNotImplemented
from mopidy.frontends.mpd.protocol import handle_request, stored_playlists
@ -12,8 +14,29 @@ QUERY_RE = (
r'[Tt]itle|[Aa]ny)"? "[^"]*"\s?)+)$')
def _get_tracks(search_results):
return list(itertools.chain(*[r.tracks for r in search_results]))
def _get_field(field, 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>[^"]*)"$')
@ -62,7 +85,13 @@ def find(context, mpd_query):
except ValueError:
return
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)
@ -304,7 +333,10 @@ def search(context, mpd_query):
except ValueError:
return
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)

View File

@ -115,6 +115,66 @@ class MusicDatabaseHandlerTest(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):
self.sendRequest('find "album" "what"')
self.assertInResponse('OK')
@ -420,6 +480,23 @@ class MusicDatabaseListTest(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):
self.sendRequest('search "album" "analbum"')
self.assertInResponse('OK')