def test_songs_by_artist(self): artist = 'slaves' client = EmbyClient(HOST, USERNAME, PASSWORD) response = client.search(artist, [MediaItemType.ARTIST.value]) search_items = EmbyCroft.parse_search_hints_from_response(response) artists = EmbyMediaItem.from_list(search_items) assert len(artists) == 1 artist_id = artists[0].id songs = client.get_songs_by_artist(artist_id) assert songs is not None for song in songs.json()['Items']: assert artist in [a.lower() for a in song['Artists']]
class EmbyCroft(object): def __init__(self, host, username, password, client_id='12345'): self.log = logging.getLogger(__name__) self.version = "UNKNOWN" self.set_version() self.client = EmbyClient(host, username, password, device="Mycroft", client="Emby Skill", client_id=client_id, version=self.version) @staticmethod def determine_intent(intent: dict): """ Determine the intent! :param self: :param intent: :return: """ if 'media' in intent: return intent['media'], IntentType.from_string('media') elif 'artist' in intent: return intent['artist'], IntentType.from_string('artist') elif 'album' in intent: return intent['album'], IntentType.from_string('album') else: return None def handle_intent(self, intent: str, intent_type: IntentType): """ Returns songs for given intent if songs are found; none if not :param intent: :return: """ songs = [] if intent_type == IntentType.MEDIA: # default to instant mix songs = self.find_songs(intent) elif intent_type == IntentType.ARTIST: # return songs by artist artist_items = self.search_artist(intent) if len(artist_items) > 0: songs = self.get_songs_by_artist(artist_items[0].id) # shuffle by default for songs by artist shuffle(songs) elif intent_type == IntentType.ALBUM: # return songs by album album_items = self.search_album(intent) if len(album_items) > 0: songs = self.get_songs_by_album(album_items[0].id) return songs def find_songs(self, media_name, media_type=None) -> []: """ This is the expected entry point for determining what songs to play :param media_name: :param media_type: :return: """ songs = [] songs = self.instant_mix_for_media(media_name) return songs def search_artist(self, artist): """ Helper method to just search Emby for an artist :param artist: :return: """ return self.search(artist, [MediaItemType.ARTIST.value]) def search_album(self, artist): """ Helper method to just search Emby for an album :param album: :return: """ return self.search(artist, [MediaItemType.ALBUM.value]) def search_song(self, song): """ Helper method to just search Emby for songs :param song: :return: """ return self.search(song, [MediaItemType.SONG.value]) def search(self, query, include_media_types=[]): """ Searches Emby from a given query :param query: :param include_media_types: :return: """ response = self.client.search(query, include_media_types) search_items = EmbyCroft.parse_search_hints_from_response(response) return EmbyMediaItem.from_list(search_items) def get_instant_mix_songs(self, item_id): """ Requests an instant mix from an Emby item id and returns song uris to be played by the Audio Service :param item_id: :return: """ response = self.client.instant_mix(item_id) queue_items = EmbyMediaItem.from_list( EmbyCroft.parse_response(response)) song_uris = [] for item in queue_items: song_uris.append(self.client.get_song_file(item.id)) return song_uris def instant_mix_for_media(self, media_name): """ Method that takes in a media name (artist/song/album) and returns an instant mix of song uris to be played :param media_name: :return: """ items = self.search(media_name) self.log.log(20, items) if items is None: items = [] item_count = len(items) self.log.log( 20, 'Found {0} item(s) when searching for {1}'.format( item_count, media_name)) songs = [] if item_count > 0: songs = self.get_instant_mix_songs(items[0].id) return songs def get_albums_by_artist(self, artist_id): return self.client.get_albums_by_artist(artist_id) def get_songs_by_album(self, album_id): response = self.client.get_songs_by_album(album_id) return self.convert_response_to_playable_songs(response) def get_songs_by_artist(self, artist_id): response = self.client.get_songs_by_artist(artist_id) return self.convert_response_to_playable_songs(response) def convert_response_to_playable_songs(self, item_query_response): queue_items = EmbyMediaItem.from_list( EmbyCroft.parse_response(item_query_response)) return self.convert_to_playable_songs(queue_items) def convert_to_playable_songs(self, songs): song_uris = [] for item in songs: song_uris.append(self.client.get_song_file(item.id)) return song_uris @staticmethod def parse_search_hints_from_response(response): if response.text: response_json = response.json() return response_json["SearchHints"] @staticmethod def parse_response(response): if response.text: response_json = response.json() return response_json["Items"] def parse_common_phrase(self, phrase: str): """ Attempts to match emby items with phrase :param phrase: :return: """ logging.log(20, "phrase: " + phrase) phrase = phrase.lower() # see if phrase contains mb or emby if 'mb' in phrase or 'emby' in phrase: # remove from phrase phrase.replace("mb", "") phrase.replace("emby", "") results = self.search(phrase) if results is None or len(results) is 0: return None, None else: logging.log(20, "Found: " + str(len(results)) + " to parse") # the idea here is # if an artist is found, return songs from this artist # elif an album is found, return songs from this album # elif a song is found, return song artists = [] albums = [] songs = [] for result in results: if result.type == MediaItemType.ARTIST: artists.append(result) elif result.type == MediaItemType.ALBUM: albums.append(result) elif result.type == MediaItemType.SONG: songs.append(result) else: logging.log( 20, "Item is not an Artist/Album/Song: " + result.type.value) if artists: artist_songs = self.get_songs_by_artist(artists[0].id) return 'artist', artist_songs elif albums: album_songs = self.get_songs_by_album(albums[0].id) return 'album', album_songs elif songs: # if a song(s) matches pick the 1st song_songs = self.convert_to_playable_songs(songs) return 'song', song_songs else: return None, None def set_version(self): """ Attempts to get version based on the git hash :return: """ try: self.version = subprocess.check_output( ["git", "describe", "--always"]).strip().decode() except Exception as e: self.log.log( 20, "Failed to determine version with error: {}".format(str(e)))