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"), ], )
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()
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], )
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(), )
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)
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)
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)
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 ])
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' ) ) ] )
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, )
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)
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
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])
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, )
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)
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, )
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)