Ejemplo n.º 1
0
def test_search_any(library, client_mock):
    client_mock.search.return_value = client_mock.SearchResult({
        "responseHeader": {
            "params": {
                "query": "album"
            }
        },
        "response": {
            "docs": [
                {
                    "identifier": "album1",
                    "title": "Album #1",
                    "mediatype": "audio",
                },
                {
                    "identifier": "album2",
                    "title": "Album #2",
                    "mediatype": "etree",
                },
            ],
            "numFound":
            2,
        },
    })
    result = library.search(dict(any=["album"]))
    assert client_mock.search.called_once()
    assert result == models.SearchResult(
        uri="internetarchive:?q=album",
        albums=[
            models.Album(name="Album #1", uri="internetarchive:album1"),
            models.Album(name="Album #2", uri="internetarchive:album2"),
        ],
    )
Ejemplo n.º 2
0
def search_mock(mopidy_album_mock, mopidy_artist_mock):
    patcher = mock.patch.object(distinct, "search", spec=search)
    search_mock = patcher.start()
    search_mock.search.return_value = models.SearchResult(
        albums=[mopidy_album_mock], artists=[mopidy_artist_mock])
    yield search_mock
    patcher.stop()
Ejemplo n.º 3
0
 def search(self, query=None, uris=None, exact=False):
     # sanitize uris
     uris = set(uris or [self.root_directory.uri])
     if self.root_directory.uri in uris:
         uris.update(translator.uri(c) for c in self.__collections)
         uris.remove(self.root_directory.uri)
     # translate query
     try:
         qs = translator.query(query, uris, exact)
     except ValueError as e:
         logger.info("Not searching %s: %s", Extension.dist_name, e)
         return None
     else:
         logger.debug("Internet Archive query: %s" % qs)
     # fetch results
     result = self.backend.client.search(
         f"{qs} AND {self.__search_filter}",
         fields=["identifier", "title", "creator", "date"],
         rows=self.__search_limit,
         sort=self.__search_order,
     )
     logger.debug("Internet Archive result: %s" % list(result))
     return models.SearchResult(
         uri=translator.uri(q=result.query),
         albums=[translator.album(item) for item in result],
     )
Ejemplo n.º 4
0
 def search(self, query=None, uris=None, exact=False):
     # sanitize uris - remove duplicates, replace root with server uris
     uris = set(uris or [self.root_directory.uri])
     if self.root_directory.uri in uris:
         uris.update(ref.uri for ref in self.__servers)
         uris.remove(self.root_directory.uri)
     # start searching - blocks only when iterating over results
     results = []
     for uri in uris:
         try:
             iterable = self.__search(uri, query, exact)
         except NotImplementedError as e:
             logger.warning("Not searching %s: %s", uri, e)
         else:
             results.append(iterable)
     if not results:
         return None
     # retrieve and merge search results - TODO: handle exceptions?
     result = collections.defaultdict(collections.OrderedDict)
     for model in itertools.chain.from_iterable(results):
         result[type(model)][model.uri] = model
     return models.SearchResult(
         albums=result[models.Album].values(),
         artists=result[models.Artist].values(),
         tracks=result[models.Track].values(),
     )
Ejemplo n.º 5
0
    def search(self, query):
        """Search Emby for a term.

        :param query: Search query
        :type query: dict
        :returns: Search results
        :rtype: mopidy.models.SearchResult
        """
        logger.debug('Searching in Emby for {}'.format(query))

        # something to store the results in
        data = []
        tracks = []
        albums = []
        artists = []

        for itemtype, term in query.items():

            for item in term:

                data.extend(self._get_search(itemtype, item))

        # walk through all items and create stuff
        for item in data:

            if item['Type'] == 'Audio':

                track_artists = [
                    models.Artist(name=artist) for artist in item['Artists']
                ]

                tracks.append(
                    models.Track(uri='emby:track:{}'.format(item['ItemId']),
                                 track_no=item.get('IndexNumber'),
                                 name=item.get('Name'),
                                 artists=track_artists,
                                 album=models.Album(name=item.get('Album'),
                                                    artists=track_artists)))

            elif item['Type'] == 'MusicAlbum':
                album_artists = [
                    models.Artist(name=artist) for artist in item['Artists']
                ]

                albums.append(
                    models.Album(uri='emby:album:{}'.format(item['ItemId']),
                                 name=item.get('Name'),
                                 artists=album_artists))

            elif item['Type'] == 'MusicArtist':
                artists.append(
                    models.Artist(uri='emby:artist:{}'.format(item['ItemId']),
                                  name=item.get('Name')))

        return models.SearchResult(uri='emby:search',
                                   tracks=tracks,
                                   artists=artists,
                                   albums=albums)
Ejemplo n.º 6
0
def _search_by_uri(config, session, query, web_client):
    tracks = []
    for uri in query['uri']:
        tracks += lookup.lookup(config, session, uri, web_client)

    uri = 'spotify:search'
    if len(query['uri']) == 1:
        uri = query['uri'][0]

    return models.SearchResult(uri=uri, tracks=tracks)
Ejemplo n.º 7
0
def _search_by_uri(config, session, web_client, query):
    tracks = []
    for uri in query["uri"]:
        tracks += lookup.lookup(config, session, web_client, uri)

    uri = "spotify:search"
    if len(query["uri"]) == 1:
        uri = query["uri"][0]

    return models.SearchResult(uri=uri, tracks=tracks)
Ejemplo n.º 8
0
 def search(self, query=None, uris=None):
     if query is None:
         return
     queries = query.get('any', []) + query.get('albums', [])
     users = [self._user_exists(query) for query in queries]
     return models.SearchResult(albums=[
         user_album for user in users
         for user_album in
         self._build_user_albums(user)
         if user is not None
     ])
Ejemplo n.º 9
0
def search(config, session, query=None, uris=None, exact=False):
    # TODO Respect `uris` argument
    # TODO Support `exact` search

    if query is None:
        logger.debug('Ignored search without query')
        return models.SearchResult(uri='spotify:search')

    if 'uri' in query:
        return _search_by_uri(config, session, query)

    sp_query = translator.sp_search_query(query)
    if not sp_query:
        logger.debug('Ignored search with empty query')
        return models.SearchResult(uri='spotify:search')

    uri = 'spotify:search:%s' % urllib.quote(sp_query.encode('utf-8'))
    logger.info('Searching Spotify for: %s', sp_query)

    if session.connection.state is not spotify.ConnectionState.LOGGED_IN:
        logger.info('Spotify search aborted: Spotify is offline')
        return models.SearchResult(uri=uri)

    sp_search = session.search(sp_query,
                               album_count=config['search_album_count'],
                               artist_count=config['search_artist_count'],
                               track_count=config['search_track_count'])
    sp_search.load()

    albums = [translator.to_album(sp_album) for sp_album in sp_search.albums]
    artists = [
        translator.to_artist(sp_artist) for sp_artist in sp_search.artists
    ]
    tracks = [translator.to_track(sp_track) for sp_track in sp_search.tracks]

    return models.SearchResult(uri=uri,
                               albums=albums,
                               artists=artists,
                               tracks=tracks)
def test_search(config, library, results):
    responses.add(responses.GET, re.compile(r'.*/search\b.*'), json=results)
    assert library.search({'any': ['foo']}) == models.SearchResult(
        albums=[
            models.Album(name='Foo', uri='podcast+http://example.com/feed1234')
        ],
        tracks=[
            models.Track(
                name='Bar',
                uri='podcast+http://example.com/feed1234#5678',
                album=models.Album(
                    name='Foo',
                    uri='podcast+http://example.com/feed1234'
                )
            )
        ]
    )
Ejemplo n.º 11
0
    def search(self, query=None, uris=None, exact=False, **kwargs):
        search_text = self._formatted_search_query(query)

        if not search_text:
            # No value provided for search query, abort.
            logger.info(f"Unsupported Pandora search query: {query}")
            return []

        search_result = self.backend.api.search(search_text,
                                                include_near_matches=False,
                                                include_genre_stations=True)

        tracks = []
        for genre in search_result.genre_stations:
            tracks.append(
                models.Track(
                    uri=SearchUri(genre.token).uri,
                    name=f"{genre.station_name} (Pandora genre)",
                    artists=[models.Artist(name=genre.station_name)],
                ))

        for song in search_result.songs:
            tracks.append(
                models.Track(
                    uri=SearchUri(song.token).uri,
                    name=f"{song.song_name} (Pandora station)",
                    artists=[models.Artist(name=song.artist)],
                ))

        artists = []
        for artist in search_result.artists:
            search_uri = SearchUri(artist.token)
            if search_uri.is_artist_search:
                station_name = f"{artist.artist} (Pandora artist)"
            else:
                station_name = f"{artist.artist} (Pandora composer)"
            artists.append(models.Artist(uri=search_uri.uri,
                                         name=station_name))

        return models.SearchResult(
            uri=f"pandora:search:{search_text}",
            tracks=tracks,
            artists=artists,
        )
Ejemplo n.º 12
0
    def search(self, query):
        """Search Jellyfin for a term.

        :param query: Search query
        :type query: dict
        :returns: Search results
        :rtype: mopidy.models.SearchResult
        """
        logger.debug('Searching in Jellyfin for {}'.format(query))

        # something to store the results in
        data = []
        tracks = []
        albums = []
        artists = []

        for itemtype, term in query.items():

            for item in term:

                data.extend(self._get_search(itemtype, item))

        # walk through all items and create stuff
        for item in data:

            if item.get('Type') == 'Audio':
                tracks.append(self.create_track(item))

            elif item.get('Type') == 'MusicAlbum':
                albums.append(
                    models.Album(name=item.get('Name'),
                                 artists=self.create_artists(
                                     name=item.get('AlbumArtist'))))

            elif item.get('Type') == 'MusicArtist':
                artists.append(self.create_artists(track=item))

        return models.SearchResult(uri='jellyfin:search',
                                   tracks=tracks,
                                   artists=artists,
                                   albums=albums)
Ejemplo n.º 13
0
def test_search(backend, server, result):
    with mock.patch.object(backend, 'client') as m:
        m.servers.return_value = Future.fromvalue([server])
        m.server.return_value = Future.fromvalue(server)
        m.search.side_effect = [
            Future.fromvalue([result[0:2], True]),
            Future.fromvalue([result[2:3], False])
        ]
        # valid search
        assert backend.library.search({'any': ['foo']}) == models.SearchResult(
            albums=[
                models.Album(name='Album #1', uri='dleyna://media/1')
            ],
            tracks=[
                models.Track(name='Track #1', uri='dleyna://media/11'),
                models.Track(name='Track #2', uri='dleyna://media/12')
            ]
        )
        # unsupported search field yields no result
        assert backend.library.search({'composer': ['foo']}) is None
        # search field not supported by device yields no result
        assert backend.library.search({'genre': ['foo']}) is None
Ejemplo n.º 14
0
 def search(self, query=None, uris=None, exact=False):
     # sanitize uris - remove duplicates, root directory
     uris = frozenset(uris or []).difference([self.root_directory.uri])
     try:
         kwargs = translator.query(query or {}, uris, exact)
     except NotImplementedError as e:
         return None  # query not supported
     except Exception as e:
         logger.error('%s', e)
     else:
         kwargs.update(self.__search_kwargs)
     results = collections.defaultdict(list)
     for item in self.backend.client.search(**kwargs):
         try:
             model = translator.model(item)
         except Exception as e:
             logger.error('Error converting iTunes search result: %s', e)
         else:
             results[type(model)].append(model)
     return models.SearchResult(albums=results[models.Album],
                                artists=results[models.Artist],
                                tracks=results[models.Track])
Ejemplo n.º 15
0
    def exact_search(self, query):
        # Variable prep
        tracks = []
        raw_artist = ''
        artist_ref = []
        albums = []

        # Check query for artist name
        if 'artist' in query:
            raw_artist = query.get('artist')
        elif 'albumartist' in query:
            raw_artist = query.get('albumartist')

        # Use if query has artist name
        if raw_artist:
            # URL encode artist string
            artist = quote(raw_artist[0].encode('utf8')).replace('/', '-')
            artist_ref = self.create_artists(name=raw_artist[0])
            url_params = {'UserId': self.user_id}
            url = self.api_url('/Artists/{}'.format(artist), url_params)

            artist_data = self.http.get(url)
            artist_id = artist_data.get('Id')

            url_params = {
                'IncludeItemTypes': 'MusicAlbum',
                'Recursive': 'true',
                'UserId': self.user_id
            }

            # Get album list
            if self.albumartistsort:
                url_params['AlbumArtistIds'] = artist_id
            else:
                url_params['ArtistIds'] = artist_id

            album_url = self.api_url('/Items', url_params)
            album_data = self.http.get(album_url)
            if album_data:
                contents = album_data.get('Items')
                for item in contents:
                    if item.get('Type') == 'MusicAlbum':
                        album_obj = models.Album(
                            name=item.get('Name'),
                            artists=self.create_artists(item),
                            uri='jellyfin:album:{}'.format(item.get('Id')))
                        if album_obj not in albums:
                            albums.append(album_obj)

            # Get artist tracks
            url_params['IncludeItemTypes'] = 'Audio'
            track_url = self.api_url('/Items', url_params)
            track_data = self.http.get(track_url)
            if track_data:
                # If the query has an album, only match those tracks
                if query.get('album'):
                    tracks = [
                        self.create_track(track)
                        for track in track_data.get('Items')
                        if track.get('Album') == query.get('album')[0]
                    ]
                # Otherwise return all tracks
                else:
                    tracks = [
                        self.create_track(track)
                        for track in track_data.get('Items')
                    ]

        # Use if query only has an album name
        elif 'album' in query:
            album_name = query.get('album')[0]
            url_params = {
                'IncludeItemTypes': 'MusicAlbum',
                'IncludeMedia': 'true',
                'Recursive': 'true',
                'searchTerm': album_name
            }
            url = self.api_url('/Users/{}/Items'.format(self.user_id),
                               url_params)
            album_data = self.http.get(url).get('Items')
            tracks = []
            # This can lead to false matches, but all we have at this point
            # is an album name to match against.  Options are limited
            for album in album_data:
                if album.get('Name') == album_name:
                    album_obj = models.Album(
                        name=album.get('Name'),
                        artists=self.create_artists(album),
                        uri='jellyfin:album:{}'.format(album.get('Id')))
                    if album_obj not in albums:
                        albums.append(album_obj)
                    raw_tracks = self.get_directory(album.get('Id'))
                    tracks += [
                        self.create_track(track)
                        for track in raw_tracks.get('Items', [])
                    ]

        return models.SearchResult(
            uri='jellyfin:search',
            tracks=tracks,
            albums=albums,
            artists=artist_ref,
        )
Ejemplo n.º 16
0
def search(config,
           session,
           web_client,
           query=None,
           uris=None,
           exact=False,
           types=_SEARCH_TYPES):
    # TODO Respect `uris` argument
    # TODO Support `exact` search

    if query is None:
        logger.debug('Ignored search without query')
        return models.SearchResult(uri='spotify:search')

    if 'uri' in query:
        return _search_by_uri(config, session, query, web_client)

    sp_query = translator.sp_search_query(query)
    if not sp_query:
        logger.debug('Ignored search with empty query')
        return models.SearchResult(uri='spotify:search')

    uri = 'spotify:search:%s' % urllib.quote(sp_query.encode('utf-8'))
    logger.info('Searching Spotify for: %s', sp_query)

    if session.connection.state is not spotify.ConnectionState.LOGGED_IN:
        logger.info('Spotify search aborted: Spotify is offline')
        return models.SearchResult(uri=uri)

    search_count = max(config['search_album_count'],
                       config['search_artist_count'],
                       config['search_track_count'])

    if search_count > 50:
        logger.warn(
            'Spotify currently allows maximum 50 search results of each type. '
            'Please set the config values spotify/search_album_count, '
            'spotify/search_artist_count and spotify/search_track_count '
            'to at most 50.')
        search_count = 50

    result = web_client.get('search',
                            params={
                                'q': sp_query,
                                'limit': search_count,
                                'market': session.user_country,
                                'type': ','.join(types)
                            })

    albums = [
        translator.web_to_album(web_album) for web_album in result['albums']
        ['items'][:config['search_album_count']]
    ] if 'albums' in result else []

    artists = [
        translator.web_to_artist(web_artist) for web_artist in
        result['artists']['items'][:config['search_artist_count']]
    ] if 'artists' in result else []

    tracks = [
        translator.web_to_track(web_track) for web_track in result['tracks']
        ['items'][:config['search_track_count']]
    ] if 'tracks' in result else []

    return models.SearchResult(uri=uri,
                               albums=albums,
                               artists=artists,
                               tracks=tracks)
Ejemplo n.º 17
0
    def exact_search(self, query):
        # Variable prep
        tracks = []
        raw_artist = ''
        artist_ref = []
        raw_albums = []

        # Check query for artist name
        if 'artist' in query:
            raw_artist = query.get('artist')
        elif 'albumartist' in query:
            raw_artist = query.get('albumartist')

        cleaned_artist = unidecode(raw_artist[0]).lower()

        # Use if query has artist name
        if raw_artist:
            # URL encode artist string
            artist = quote(raw_artist[0].encode('utf8')).replace('/', '-')
            artist_ref = [models.Artist(name=raw_artist[0])]
            url = self.api_url('/Artists/{}?UserId={}'.format(
                artist, self.user_id))

            artist_data = self.http.get(url)
            artist_id = artist_data.get('Id')

            # Get album list
            if self.albumartistsort:
                album_url = self.api_url(
                    '/Items?IncludeItemTypes=MusicAlbum&Recursive=true&'
                    'AlbumArtistIds={}&UserId={}&'.format(
                        artist_id, self.user_id))
            else:
                album_url = self.api_url(
                    '/Items?IncludeItemTypes=MusicAlbum&Recursive=true&'
                    'ArtistIds={}&UserId={}&'.format(artist_id, self.user_id))

            album_data = self.http.get(album_url)
            if album_data:
                raw_albums = album_data.get('Items')

            if self.albumartistsort:
                track_url = self.api_url(
                    '/Items?IncludeItemTypes=Audio&Recursive=true&'
                    'AlbumArtistIds={}&UserId={}&'.format(
                        artist_id, self.user_id))
            else:
                track_url = self.api_url(
                    '/Items?IncludeItemTypes=Audio&Recursive=true&'
                    'ArtistIds={}&UserId={}&'.format(artist_id, self.user_id))

            track_data = self.http.get(track_url)
            if track_data:
                # If the query has an album, only match those tracks
                if query.get('album'):
                    tracks = [
                        models.Track(
                            uri='jellyfin:track:{}'.format(track.get('Id')),
                            track_no=track.get('IndexNumber'),
                            disc_no=track.get('ParentIndexNumber'),
                            name=track.get('Name'),
                            artists=artist_ref,
                            album=models.Album(name=track.get('Album'),
                                               artists=artist_ref))
                        for track in track_data.get('Items')
                        if track.get('Album') == query.get('album')[0]
                    ]
                # Otherwise return all tracks
                else:
                    tracks = [
                        models.Track(
                            uri='jellyfin:track:{}'.format(track.get('Id')),
                            track_no=track.get('IndexNumber'),
                            disc_no=track.get('ParentIndexNumber'),
                            name=track.get('Name'),
                            artists=artist_ref,
                            album=models.Album(name=track.get('Album'),
                                               artists=artist_ref))
                        for track in track_data.get('Items')
                    ]

        # Use if query only has an album name
        elif 'album' in query:
            album_name = query.get('album')[0]
            album = quote(album_name.encode('utf8'))
            if raw_albums:
                album_id = [
                    i.get('Id') for i in raw_albums
                    if i.get('Name') == unidecode(album_name)
                ][0]
            else:
                url = self.api_url(
                    '/Items?IncludeItemTypes=Audio&Recursive=true&'
                    'Albums={}&UserId={}&'.format(album, self.user_id))
                album_data = self.http.get(url).get('Items')

                album_id = album_data[0].get('AlbumId')

                tracks = self.get_search_tracks(artist_ref, album_id)

        return models.SearchResult(
            uri='jellyfin:search',
            tracks=tracks,
            artists=artist_ref,
        )
Ejemplo n.º 18
0
def search(
    config,
    session,
    web_client,
    query=None,
    uris=None,
    exact=False,
    types=_SEARCH_TYPES,
):
    # TODO Respect `uris` argument
    # TODO Support `exact` search

    if query is None:
        logger.debug("Ignored search without query")
        return models.SearchResult(uri="spotify:search")

    if "uri" in query:
        return _search_by_uri(config, session, web_client, query)

    sp_query = translator.sp_search_query(query)
    if not sp_query:
        logger.debug("Ignored search with empty query")
        return models.SearchResult(uri="spotify:search")

    uri = f"spotify:search:{urllib.parse.quote(sp_query)}"
    logger.info(f"Searching Spotify for: {sp_query}")

    if session.connection.state is not spotify.ConnectionState.LOGGED_IN:
        logger.info("Spotify search aborted: Spotify is offline")
        return models.SearchResult(uri=uri)

    search_count = max(
        config["search_album_count"],
        config["search_artist_count"],
        config["search_track_count"],
    )

    if search_count > 50:
        logger.warn(
            "Spotify currently allows maximum 50 search results of each type. "
            "Please set the config values spotify/search_album_count, "
            "spotify/search_artist_count and spotify/search_track_count "
            "to at most 50.")
        search_count = 50

    result = web_client.get(
        "search",
        params={
            "q": sp_query,
            "limit": search_count,
            "market": "from_token",
            "type": ",".join(types),
        },
    )

    albums = ([
        translator.web_to_album(web_album) for web_album in result["albums"]
        ["items"][:config["search_album_count"]]
    ] if "albums" in result else [])

    artists = ([
        translator.web_to_artist(web_artist) for web_artist in
        result["artists"]["items"][:config["search_artist_count"]]
    ] if "artists" in result else [])

    tracks = ([
        translator.web_to_track(web_track) for web_track in result["tracks"]
        ["items"][:config["search_track_count"]]
    ] if "tracks" in result else [])

    return models.SearchResult(uri=uri,
                               albums=albums,
                               artists=artists,
                               tracks=tracks)