class URLRadio(CommonPlaySkill): def __init__(self): super(URLRadio, self).__init__('URL Radio') file_name = 'url_list.json' skill_path = "/opt/mycroft/skills/skill-url-radio.pcwii" with open(join(skill_path, file_name)) as f: self.channel_list = json.load(f) self.log.info(str(self.channel_list)) self.mediaplayer = VlcService(config={'low_volume': 10, 'duck': True}) self.audio_state = 'stopped' # 'playing', 'stopped' def initialize(self): self.log.info('initializing URLRadio') self.load_data_files(dirname(__file__)) super(URLRadio, self).initialize() def CPS_match_query_phrase(self, phrase): """ The method is invoked by the PlayBackControlSkill. """ self.log.info('URLRadio received the following phrase: ' + phrase) for each_channel in self.channel_list[ "stations"]: # loop through every interval if phrase in each_channel["name"]: print("Station Found, Exiting!") data = each_channel return phrase, CPSMatchLevel.TITLE, data break else: print("Station Not Found, Searching!") return None def CPS_start(self, phrase, data): """ Starts playback. Called by the playback control skill to start playback if the skill is selected (has the best match level) """ self.log.info( 'URLRadio Skill received the following phrase and Data: ' + phrase + ' ' + str(data)) self.speak_dialog('now.playing', data={"channel": data["name"]}, expect_response=False) wait_while_speaking url = str(data["url"]) self.mediaplayer.add_list(url) self.audio_state = 'playing' self.mediaplayer.play() pass def stop(self): if self.audio_state == 'playing': self.mediaplayer.stop() self.mediaplayer.clear_list() LOG.debug('Stopping stream') self.audio_state = 'stopped' return True
class YoutubeSkill(CommonPlaySkill): def __init__(self): super().__init__(name='YoutubeSkill') self.audio_state = 'stopped' # 'playing', 'stopped', 'paused' self.station_name = None self.stream_url = None self.mpeg_url = None self.process = None self.regexes = {} self.mediaplayer = VlcService(config={'low_volume': 10, 'duck': True}) self.audio_state = 'stopped' # 'playing', 'stopped' # self.add_event('mycroft.audio.service.pause', CPS_pause) # self.add_event('mycroft.audio.service.resume', CPS_resume) def CPS_match_query_phrase(self, phrase): # Look for regex matches starting from the most specific to the least # Play <data> match = re.search(self.translate_regex('on_youtube'), phrase) if match: data = re.sub(self.translate_regex('on_youtube'), '', phrase) LOG.debug('CPS Match (on_youtube): ' + data) return phrase, CPSMatchLevel.EXACT, data return phrase, CPSMatchLevel.GENERIC, phrase def CPS_start(self, phrase, data): LOG.debug('CPS Start: ' + data) self.search_youtube(data) def CPS_resume(self, phrase, data): self.mediaplayer.play() def CPS_pause(self, phrase, data): self.mediaplayer.pause() # Attempt to find the first result matching the query string def search_youtube(self, search_term): tracklist = [] res = requests.get(search_url + search_term) # TODO: check status code etc... html = res.content soup = BeautifulSoup(html, 'html.parser') vids = soup.findAll(attrs={'class': 'yt-uix-tile-link'}) for vid in vids: if not re.match('/watch\?v=\w{11}', vid['href']): LOG.debug('no media: ' + vid['href']) continue self.vid_url = vid['href'] self.vid_name = vid.string self.stream_url = self.get_stream_url(self.vid_url) LOG.debug('Found stream URL: ' + self.vid_url) LOG.debug('Media title: ' + self.vid_name) tracklist.append(self.stream_url) self.mediaplayer.add_list(tracklist) self.audio_state = 'playing' self.speak_dialog('now.playing') wait_while_speaking() self.mediaplayer.play() return # We didn't find any playable results self.speak_dialog('not.found') wait_while_speaking() LOG.debug('Could not find any results with the query term: ' + search_term) def get_stream_url(self, youtube_url): abs_url = base_url + youtube_url LOG.debug('pafy processing: ' + abs_url) streams = pafy.new(abs_url) LOG.debug('audiostreams found: ' + str(streams.audiostreams)) bestaudio = streams.getbestaudio() LOG.debug('audiostream selected: ' + str(bestaudio)) return bestaudio.url def stop(self): if self.audio_state == 'playing': self.mediaplayer.stop() self.mediaplayer.clear_list() LOG.debug('Stopping stream') self.audio_state = 'stopped' self.station_name = None self.station_id = None self.stream_url = None return True # these don't work (yet?) def pause(self, message=None): if self.audio_state == 'playing': self.mediaplayer.pause() self.audio_state = 'paused' def resume(self, message=None): if self.audio_state == 'paused': self.mediaplayer.play() self.audio_state = 'playing' def next_track(self, message): self.mediaplayer.next() def prev_track(self, message): self.mediaplayer.previous() def shutdown(self): if self.audio_state == 'playing': self.mediaplayer.stop() self.mediaplayer.clear_list() # Get the correct localized regex def translate_regex(self, regex): if regex not in self.regexes: path = self.find_resource(regex + '.regex') if path: with open(path) as f: string = f.read().strip() self.regexes[regex] = string return self.regexes[regex]
class TuneinSkill(CommonPlaySkill): def __init__(self): super().__init__(name="TuneinSkill") self.mediaplayer = VlcService(config={'low_volume': 10, 'duck': True}) self.audio_state = "stopped" # 'playing', 'stopped' self.station_name = None self.stream_url = None self.mpeg_url = None self.regexes = {} self.aliases = {} def initialize(self): self.init_websettings() self.settings_change_callback = self.init_websettings def init_websettings(self): self.get_aliases() def CPS_match_query_phrase(self, phrase): # Look for regex matches starting from the most specific to the least # Play <data> internet radio on tune in match = re.search(self.translate_regex('internet_radio_on_tunein'), phrase) if match: data = re.sub(self.translate_regex('internet_radio_on_tunein'), '', phrase) LOG.debug("CPS Match (internet_radio_on_tunein): " + data) return phrase, CPSMatchLevel.EXACT, data # Play <data> radio on tune in match = re.search(self.translate_regex('radio_on_tunein'), phrase) if match: data = re.sub(self.translate_regex('radio_on_tunein'), '', phrase) LOG.debug("CPS Match (radio_on_tunein): " + data) return phrase, CPSMatchLevel.EXACT, data # Play <data> on tune in match = re.search(self.translate_regex('on_tunein'), phrase) if match: data = re.sub(self.translate_regex('on_tunein'), '', phrase) LOG.debug("CPS Match (on_tunein): " + data) return phrase, CPSMatchLevel.EXACT, data # Play <data> internet radio match = re.search(self.translate_regex('internet_radio'), phrase) if match: data = re.sub(self.translate_regex('internet_radio'), '', phrase) LOG.debug("CPS Match (internet_radio): " + data) return phrase, CPSMatchLevel.CATEGORY, data # Play <data> radio match = re.search(self.translate_regex('radio'), phrase) if match: data = re.sub(self.translate_regex('radio'), '', phrase) LOG.debug("CPS Match (radio): " + data) return phrase, CPSMatchLevel.CATEGORY, data return phrase, CPSMatchLevel.GENERIC, phrase def CPS_start(self, phrase, data): LOG.debug("CPS Start: " + data) self.find_station(data) @intent_file_handler('StreamRequest.intent') def handle_stream_intent(self, message): self.find_station(message.data["station"]) LOG.debug("Station data: " + message.data["station"]) def get_aliases(self): self.aliases.clear() for i in range(0, MAXALIASES): _station = self.settings.get(f"station{i}", False) _alias = self.settings.get(f"alias{i}", False) if _station and _alias: self.aliases[_alias.lower()] = _station.lower() def remove_aliases(self, search_term): """ Applies the aliases either defined in the webconfig or using ~/tunein_aliases.yaml (deprecated)""" # backwards compat if not self.aliases: home = expanduser('~') alias_file = home + '/tunein_aliases.yaml' if path.exists(alias_file): with open(alias_file, 'r') as file: self.aliases = yaml.load(file, Loader=yaml.FullLoader) for alias, station in self.aliases.items(): if alias in search_term: search_term = search_term.replace(alias, station) LOG.debug(f"Removed alias. Search_term: {search_term}") return search_term # Attempt to find the first active station matching the query string def find_station(self, search_term): tracklist = [] retry = True search_term = self.remove_aliases(search_term) dom = request_api(search_term) # results are each in their own <outline> tag as defined by OPML (https://en.wikipedia.org/wiki/OPML) # fuzzy matches the query to the given stations NodeList match, perc = _fuzzy_match(search_term, dom.getElementsByTagName("outline")) # No matching stations if match is None: self.speak_dialog("not.found") wait_while_speaking() LOG.debug("Could not find a station with the query term: " + search_term) return # stop the current stream if we have one running if self.audio_state == "playing": self.stop() # Ignore entries that are marked as unavailable self.mpeg_url = match.getAttribute("URL") self.station_name = match.getAttribute("text") # this URL will return audio/x-mpegurl data. This is just a list of URLs to the real streams self.stream_url = self.get_stream_url(self.mpeg_url) self.audio_state = "playing" self.speak_dialog("now.playing", {"station": self.station_name}) wait_while_speaking() LOG.debug("Station: " + self.station_name) LOG.debug("Station name fuzzy match percent: " + str(perc)) LOG.debug("Stream URL: " + self.stream_url) tracklist.append(self.stream_url) self.mediaplayer.add_list(tracklist) self.mediaplayer.play() def get_stream_url(self, mpegurl): res = requests.get(mpegurl) # Get the first line from the results for line in res.text.splitlines(): return self.process_url(line) def stop(self): if self.audio_state == "playing": self.mediaplayer.stop() self.mediaplayer.clear_list() LOG.debug("Stopping stream") self.audio_state = "stopped" self.station_name = None self.stream_url = None self.mpeg_url = None return True def shutdown(self): if self.audio_state == 'playing': self.mediaplayer.stop() self.mediaplayer.clear_list() # Check what kind of url was pulled from the x-mpegurl data def process_url(self, url): if (len(url) > 4): if url[-3:] == 'm3u': return url[:-4] if url[-3:] == 'pls': return self.process_pls(url) else: return url return url # Pull down the pls data and pull out the real stream url out of it def process_pls(self, url): res = requests.get(url) # Loop through the data looking for the first url for line in res.text.splitlines(): if line.startswith("File1="): return line[6:] # Get the correct localized regex def translate_regex(self, regex): if regex not in self.regexes: path = self.find_resource(regex + '.regex') if path: with open(path) as f: string = f.read().strip() self.regexes[regex] = string return self.regexes[regex]
class TuneinSkill(CommonPlaySkill): def __init__(self): super().__init__(name="TuneinSkill") self.mediaplayer = VlcService(config={'low_volume': 10, 'duck': True}) self.audio_state = "stopped" # 'playing', 'stopped' self.station_name = None self.stream_url = None self.mpeg_url = None self.regexes = {} def CPS_match_query_phrase(self, phrase): # Look for regex matches starting from the most specific to the least # Play <data> internet radio on tune in match = re.search(self.translate_regex('internet_radio_on_tunein'), phrase) if match: data = re.sub(self.translate_regex('internet_radio_on_tunein'), '', phrase) LOG.debug("CPS Match (internet_radio_on_tunein): " + data) return phrase, CPSMatchLevel.EXACT, data # Play <data> radio on tune in match = re.search(self.translate_regex('radio_on_tunein'), phrase) if match: data = re.sub(self.translate_regex('radio_on_tunein'), '', phrase) LOG.debug("CPS Match (radio_on_tunein): " + data) return phrase, CPSMatchLevel.EXACT, data # Play <data> on tune in match = re.search(self.translate_regex('on_tunein'), phrase) if match: data = re.sub(self.translate_regex('on_tunein'), '', phrase) LOG.debug("CPS Match (on_tunein): " + data) return phrase, CPSMatchLevel.EXACT, data # Play <data> internet radio match = re.search(self.translate_regex('internet_radio'), phrase) if match: data = re.sub(self.translate_regex('internet_radio'), '', phrase) LOG.debug("CPS Match (internet_radio): " + data) return phrase, CPSMatchLevel.CATEGORY, data # Play <data> radio match = re.search(self.translate_regex('radio'), phrase) if match: data = re.sub(self.translate_regex('radio'), '', phrase) LOG.debug("CPS Match (radio): " + data) return phrase, CPSMatchLevel.CATEGORY, data return phrase, CPSMatchLevel.GENERIC, phrase def CPS_start(self, phrase, data): LOG.debug("CPS Start: " + data) self.find_station(data) @intent_file_handler('StreamRequest.intent') def handle_stream_intent(self, message): self.find_station(message.data["station"]) LOG.debug("Station data: " + message.data["station"]) def apply_aliases(self, search_term): # Allow search terms to be expanded or aliased home = expanduser('~') alias_file = home + '/tunein_aliases.yaml' if path.exists(alias_file): with open(alias_file, 'r') as file: alias_list = yaml.load(file) if search_term in alias_list: search_term = alias_list[search_term] return search_term # Attempt to find the first active station matching the query string def find_station(self, search_term): tracklist = [] LOG.debug("pre-alias search_term: " + search_term) search_term = self.apply_aliases(search_term) LOG.debug("aliased search_term: " + search_term) payload = {"query": search_term} # get the response from the TuneIn API res = requests.post(base_url, data=payload, headers=headers) dom = parseString(res.text) # results are each in their own <outline> tag as defined by OPML (https://en.wikipedia.org/wiki/OPML) entries = dom.getElementsByTagName("outline") # Loop through outlines in the lists for entry in entries: # Only look at outlines that are of type=audio and item=station if (entry.getAttribute("type") == "audio") and (entry.getAttribute("item") == "station"): if (entry.getAttribute("key") != "unavailable"): if (entry.getAttribute("text") == "triple j Unearthed"): continue # stop the current stream if we have one running if (self.audio_state == "playing"): self.stop() # Ignore entries that are marked as unavailable self.mpeg_url = entry.getAttribute("URL") self.station_name = entry.getAttribute("text") # this URL will return audio/x-mpegurl data. This is just a list of URLs to the real streams self.stream_url = self.get_stream_url(self.mpeg_url) self.audio_state = "playing" self.speak_dialog("now.playing", {"station": self.station_name}) wait_while_speaking() LOG.debug("Found stream URL: " + self.stream_url) tracklist.append(self.stream_url) self.mediaplayer.add_list(tracklist) self.mediaplayer.play() return # We didn't find any playable stations self.speak_dialog("not.found") wait_while_speaking() LOG.debug("Could not find a station with the query term: " + search_term) def get_stream_url(self, mpegurl): res = requests.get(mpegurl) # Get the first line from the results for line in res.text.splitlines(): return self.process_url(line) def stop(self): if self.audio_state == "playing": self.mediaplayer.stop() self.mediaplayer.clear_list() LOG.debug("Stopping stream") self.audio_state = "stopped" self.station_name = None self.stream_url = None self.mpeg_url = None return True def shutdown(self): if self.audio_state == 'playing': self.mediaplayer.stop() self.mediaplayer.clear_list() # Check what kind of url was pulled from the x-mpegurl data def process_url(self, url): if (len(url) > 4): if url[-3:] == 'm3u': return url[:-4] if url[-3:] == 'pls': return self.process_pls(url) else: return url return url # Pull down the pls data and pull out the real stream url out of it def process_pls(self, url): res = requests.get(url) # Loop through the data looking for the first url for line in res.text.splitlines(): if line.startswith("File1="): return line[6:] # Get the correct localized regex def translate_regex(self, regex): if regex not in self.regexes: path = self.find_resource(regex + '.regex') if path: with open(path) as f: string = f.read().strip() self.regexes[regex] = string return self.regexes[regex]
class IHeartRadioSkill(CommonPlaySkill): def __init__(self): super().__init__(name="IHeartRadioSkill") self.mediaplayer = VlcService(config={'low_volume': 10, 'duck': True}) self.audio_state = "stopped" # 'playing', 'stopped' self.station_name = None self.station_id = None self.stream_url = None self.regexes = {} self.set_urls() self.settings.set_changed_callback(self.set_urls) def set_urls(self): country = self.settings.get('country', 'default') country_code = self.location['city']['state']['country']['code'].lower( ) if country == 'default' else country if country_code[-1] != '.': country_code = country_code + '.' if country == 'global' or not self.test_for_local_api(country_code): country_code = '' self.search_url = "http://{}api.iheart.com/api/v3/search/all".format( country_code) self.station_url = "https://{}api.iheart.com/api/v2/content/liveStations/".format( country_code) def test_for_local_api(self, country_code): try: payload = { "keywords": "test", "maxRows": 1, "bundle": "false", "station": "true", "artist": "false", "track": "false", "playlist": "false", "podcast": "false" } search_url = "http://{}api.iheart.com/api/v3/search/all".format( country_code) r = requests.get(search_url, params=payload, headers=headers) return r.status_code == 200 except: return False def CPS_match_query_phrase(self, phrase): # Look for regex matches starting from the most specific to the least # Play <data> internet radio on i heart radio match = re.search(self.translate_regex('internet_radio_on_iheart'), phrase) if match: data = re.sub(self.translate_regex('internet_radio_on_iheart'), '', phrase) LOG.debug("CPS Match (internet_radio_on_iheart): " + data) return phrase, CPSMatchLevel.EXACT, data # Play <data> radio on i heart radio match = re.search(self.translate_regex('radio_on_iheart'), phrase) if match: data = re.sub(self.translate_regex('radio_on_iheart'), '', phrase) LOG.debug("CPS Match (radio_on_iheart): " + data) return phrase, CPSMatchLevel.EXACT, data # Play <data> on i heart radio match = re.search(self.translate_regex('on_iheart'), phrase) if match: data = re.sub(self.translate_regex('on_iheart'), '', phrase) LOG.debug("CPS Match (on_iheart): " + data) return phrase, CPSMatchLevel.EXACT, data # Play <data> internet radio match = re.search(self.translate_regex('internet_radio'), phrase) if match: data = re.sub(self.translate_regex('internet_radio'), '', phrase) LOG.debug("CPS Match (internet_radio): " + data) return phrase, CPSMatchLevel.CATEGORY, data # Play <data> radio match = re.search(self.translate_regex('radio'), phrase) if match: data = re.sub(self.translate_regex('radio'), '', phrase) LOG.debug("CPS Match (radio): " + data) return phrase, CPSMatchLevel.CATEGORY, data return phrase, CPSMatchLevel.GENERIC, phrase def CPS_start(self, phrase, data): LOG.debug("CPS Start: " + data) self.find_station(data) @intent_file_handler('StreamRequest.intent') def handle_stream_intent(self, message): self.find_station(message.data["station"]) LOG.debug("Station data: " + message.data["station"]) def find_station(self, search_term): tracklist = [] payload = { "keywords": search_term, "maxRows": 1, "bundle": "false", "station": "true", "artist": "false", "track": "false", "playlist": "false", "podcast": "false" } # get the response from the IHeartRadio API search_res = requests.get(self.search_url, params=payload, headers=headers) search_obj = json.loads(search_res.text) if (len(search_obj["results"]["stations"]) > 0): self.station_name = search_obj["results"]["stations"][0]["name"] self.station_id = search_obj["results"]["stations"][0]["id"] LOG.debug("Station name: " + self.station_name + " ID: " + str(self.station_id)) # query the station URL using the ID station_res = requests.get(self.station_url + str(self.station_id)) station_obj = json.loads(station_res.text) self.audio_state = "playing" self.speak_dialog("now.playing", {"station": self.station_name}) wait_while_speaking() # Use the first stream URL for x in list(station_obj["hits"][0]["streams"])[0:1]: self.stream_url = station_obj["hits"][0]["streams"][x] break LOG.debug("Station URL: " + self.stream_url) tracklist.append(self.stream_url) self.mediaplayer.add_list(tracklist) self.mediaplayer.play() else: self.speak_dialog("not.found") wait_while_speaking() def stop(self): if self.audio_state == "playing": self.mediaplayer.stop() self.mediaplayer.clear_list() LOG.debug("Stopping stream") self.audio_state = "stopped" self.station_name = None self.station_id = None self.stream_url = None return True def shutdown(self): if self.audio_state == 'playing': self.mediaplayer.stop() self.mediaplayer.clear_list() # Get the correct localized regex def translate_regex(self, regex): if regex not in self.regexes: path = self.find_resource(regex + '.regex') if path: with open(path) as f: string = f.read().strip() self.regexes[regex] = string return self.regexes[regex]
class AmznMusicSkill(CommonPlaySkill): def __init__(self): super().__init__(name="AmznMusicSkill") # self.mediaplayer = MPlayerService(config=None, bus=None) self.mediaplayer = VlcService(config={'low_volume': 10, 'duck': True}) self.state = 'idle' self.cps_id = "amzn-music" self.am = None self.vocabs = [] # keep a list of vocabulary words self.username = "" self.password = "" self.library_only = True def initialize(self): self.username = self.settings.get("username", "") self.password = self.settings.get("password", "") self.library_only = self.settings.get("library_only", True) # self._load_vocab_files() # handle credentials if (not self.username) and (not self.password): credentials = self._load_credentials_store() if credentials: self.username = base64.b64decode(credentials['e']) self.password = base64.b64decode(credentials['p']) if self.username and self.password: LOG.debug("login to amazon music") self.am = AmazonMusic(credentials=[self.username, self.password]) self.mediaplayer.clear_list() # Setup handlers for playback control messages self.add_event('mycroft.audio.service.next', self.next) self.add_event('mycroft.audio.service.prev', self.previous) self.add_event('mycroft.audio.service.pause', self.pause) self.add_event('mycroft.audio.service.resume', self.resume) self.add_event('mycroft.audio.service.lower_volume', self.lower_volume) self.add_event('mycroft.audio.service.restore_volume', self.restore_volume) self.add_event('recognizer_loop:audio_output_start', self.lower_volume) self.add_event('recognizer_loop:record_begin', self.lower_volume) self.add_event('recognizer_loop:audio_output_end', self.restore_volume) self.add_event('recognizer_loop:record_end', self.restore_volume) self.add_event('mycroft.audio.service.track_info', self._track_info_handler) self.add_event('mycroft.audio.playing_track', self._playing_track_handler) self.mediaplayer.set_track_start_callback(self._track_start_handler) def CPS_match_query_phrase(self, phrase): LOG.debug("phrase {} lib-only".format(phrase, self.library_only)) # Not ready to play if not self.am: return None if 'amazon' in phrase.lower(): bonus = 0.1 else: bonus = 0 #phrase = re.sub(self.translate('on_amazon_regex'), '', phrase) #LOG.debug("phrase {}".format(phrase)) phrase = self._clean_utterance(phrase) LOG.debug("clean phrase {}".format(phrase)) confidence, data = self.continue_playback(phrase, bonus) if not data: confidence, data = self.specific_query(phrase, bonus) if not data: confidence, data = self.generic_query(phrase, bonus) if data: if confidence > 0.9: confidence = CPSMatchLevel.EXACT elif confidence > 0.7: confidence = CPSMatchLevel.MULTI_KEY elif confidence > 0.5: confidence = CPSMatchLevel.TITLE else: confidence = CPSMatchLevel.CATEGORY return phrase, confidence, data def continue_playback(self, phrase, bonus=0.0): LOG.debug("phrase {}".format(phrase)) if phrase.strip() == 'amazon': return (1.0, {'data': None, 'name': None, 'type': 'continue'}) else: return None, None def specific_query(self, phrase, bonus=0.0): LOG.debug("phrase {}".format(phrase)) # Check if playlist match = re.match(self.translate('playlist_regex'), phrase) LOG.debug("match playlist {}".format(match)) if match: bonus += 0.1 playlist, conf, data = \ self.get_best_playlist(match.groupdict()['playlist']) confidence = min(conf + bonus, 1.0) if not playlist: return 0, None return (confidence, { 'asin': data['asin'], 'title': data['title'], 'name': playlist, 'type': 'Playlist' }) # Check album match = re.match(self.translate('album_regex'), phrase) LOG.debug("match album {}".format(phrase)) if match: bonus += 0.1 album = match.groupdict()['album'] return self.query_album(album, bonus) # Check artist match = re.match(self.translate('artist_regex'), phrase) LOG.debug("match artist {}".format(phrase)) if match: bonus += 0.1 artist = match.groupdict()['artist'] return self.query_artist(artist, bonus) # Check song/track match = re.match(self.translate('song_regex'), phrase) LOG.debug("match song {}".format(phrase)) if match: bonus += 0.1 track = match.groupdict()['track'] return self.query_track(track, bonus) # Check genre match = re.match(self.translate('genre_regex'), phrase) LOG.debug("match genre {}".format(phrase)) if match: bonus += 0.1 genre = match.groupdict()['genre'] return self.query_genre(genre, bonus) return None, None def generic_query(self, phrase, bonus=0.0): LOG.debug("phrase {}".format(phrase)) playlist, conf, asin = self.get_best_playlist(phrase) if conf > 0.5: return (conf, {'asin': asin, 'name': playlist, 'type': 'Playlist'}) else: return self.query_album(phrase, bonus) def query_genre(self, genre, bonus=0.0): LOG.debug("genre {}".format(genre)) results = self.am.search(genre, library_only=self.library_only, tracks=True, albums=False, playlists=False, artists=False, stations=False) best_score = 0.0 best_match = "" for res in results: if 'track' in res[0]: for hit in res[1]['hits']: primaryGenre = hit['document']['primaryGenre'] score = fuzzy_match(genre.lower(), primaryGenre.lower()) if score > best_score: best_match = primaryGenre best_score = score if (best_score + bonus) >= 1.0: break if best_score > 0.0: conf = min(best_score + bonus, 1.0) return (conf, { 'genre': best_match, 'name': genre, 'type': 'Genre' }) def query_track(self, trackname, bonus=0.0): LOG.debug("trackname {}".format(trackname)) by_word = ' {} '.format(self.translate('by')) artist = "" if len(trackname.split(by_word)) > 1: trackname, artist = trackname.split(by_word) # trackname = '*{}* artist:{}'.format(trackname, artist) bonus += 0.1 LOG.debug("trackname {} artist {}".format(trackname, artist)) results = self.am.search(trackname, library_only=self.library_only, tracks=True, albums=False, playlists=False, artists=False, stations=False) tracks = {} for res in results: if 'track' in res[0]: for hit in res[1]['hits']: title = hit['document']['title'].lower() if artist: title += (' ' + hit['document']['artistName'].lower()) asin = hit['document']['asin'] tracks[title] = { 'asin': asin, 'albumAsin': hit['document']['albumAsin'], 'artist': hit['document']['artistName'], 'title': hit['document']['title'] } if tracks: match = trackname if artist: match += (' ' + artist) key, confidence = match_one(match.lower(), list(tracks.keys())) if confidence > 0.7: confidence = min(confidence + bonus, 1.0) return (confidence, { 'asin': tracks[key]['asin'], 'albumAsin': tracks[key]['albumAsin'], 'name': key, 'artist': tracks[key]['artist'], 'title': tracks[key]['title'], 'type': 'Song' }) return None, None def query_artist(self, artist, bonus=0.0): results = self.am.search(artist, library_only=self.library_only, tracks=False, albums=False, playlists=False, artists=True, stations=False) artists = {} for res in results: if 'artists' in res[0]: for hit in res[1]['hits']: name = hit['document']['name'].lower() asin = hit['document']['asin'] artists[name] = { 'asin': asin, 'name': hit['document']['name'] } if artists: key, confidence = match_one(artist.lower(), list(artists.keys())) if confidence > 0.7: confidence = min(confidence + bonus, 1.0) return (confidence, { 'asin': artists[key]['asin'], 'name': key, 'artist': artists[key]['name'], 'type': 'Artist' }) return None, None def query_album(self, album, bonus=0.0): LOG.debug("album {}".format(album)) by_word = ' {} '.format(self.translate('by')) artist = "" if len(album.split(by_word)) > 1: album, artist = album.split(by_word) bonus += 0.1 LOG.debug("album {} artist {}".format(album, artist)) results = self.am.search(album, library_only=self.library_only, tracks=False, albums=True, playlists=False, artists=False, stations=False) albums = {} for res in results: if 'album' in res[0]: for hit in res[1]['hits']: title = hit['document']['title'].lower() if artist: title += (' ' + hit['document']['artistName'].lower()) asin = hit['document']['asin'] albums[title] = { 'asin': asin, 'artist': hit['document']['artistName'], 'title': hit['document']['title'] } if albums: match = album if artist: match += (' ' + artist) key, confidence = match_one(match.lower(), list(albums.keys())) if confidence > 0.7: confidence = min(confidence + bonus, 1.0) return (confidence, { 'asin': albums[key]['asin'], 'artist': albums[key]['artist'], 'title': albums[key]['title'], 'name': key, 'type': 'Album' }) return None, None def get_best_playlist(self, playlist): """ Get best playlist matching the provided name Arguments: playlist (str): Playlist name Returns: (str) best match, confidence, asin """ LOG.debug("playlist {}".format(playlist)) results = self.am.search(playlist, library_only=self.library_only, tracks=False, albums=False, playlists=True, artists=False, stations=False) playlists = {} for res in results: # LOG.debug(res[0]) if 'playlist' in res[0]: for hit in res[1]['hits']: title = hit['document']['title'].lower() asin = hit['document']['asin'] playlists[title] = { 'asin': asin, 'title': hit['document']['title'] } if playlists: key, confidence = match_one(playlist.lower(), list(playlists.keys())) LOG.debug("key {} confidence {}".format(key, confidence)) if confidence > 0.7: return key, confidence, playlists[key] return None, 0, None def CPS_start(self, phrase, data): LOG.debug("phrase: {} data: {}".format(phrase, data)) tracklist = [] if 'continue' in data['type']: self.resume() return # single track if 'Song' in data['type']: stream_url = self._get_track_url_from_album( data['asin'], data['albumAsin']) if stream_url: tracklist.append(stream_url) elif ('Album' in data['type']) or ('Playlist' in data['type']): if 'Album' in data['type']: entity = self.am.get_album(data['asin']) else: entity = self.am.get_playlists(data['asin']) for track in entity.tracks: stream_url = "" LOG.debug("getting url for {}".format(track.name)) try: stream_url = track.stream_url except Exception as e: LOG.error(e) LOG.debug(stream_url) if stream_url: tracklist.append(stream_url) elif 'Artist' in data['type']: results = self.am.search(data['name'], library_only=self.library_only, tracks=True, albums=False, playlists=False, artists=False, stations=False) tracklist = self._get_tracklist_from_searchresult(results, data) elif 'Genre' in data['type']: results = self.am.search(data['genre'], library_only=self.library_only, tracks=True, albums=False, playlists=False, artists=False, stations=False) tracklist = self._get_tracklist_from_searchresult(results, data) if len(tracklist): if self.state in ['playing', 'paused']: self.mediaplayer.stop() self.mediaplayer.clear_list() self.mediaplayer.add_list(tracklist) self.speak(self._get_play_message(data)) self.mediaplayer.play() self.state = 'playing' else: LOG.debug("empty tracklist!") def _get_tracklist_from_searchresult(self, result, data): tracklist = [] for res in result: for hit in res[1]['hits']: stream_url = "" if data['type'] == 'Artist': if hit['document']['artistAsin'] == data['asin']: album_asin = hit['document']['albumAsin'] track_asin = hit['document']['asin'] stream_url = self._get_track_url_from_album( track_asin, album_asin) elif data['type'] == 'Genre': if hit['document']['primaryGenre'] == data['genre']: album_asin = hit['document']['albumAsin'] track_asin = hit['document']['asin'] stream_url = self._get_track_url_from_album( track_asin, album_asin) if stream_url: tracklist.append(stream_url) return tracklist def _get_play_message(self, data): message = "" data_type = data['type'] if data_type == 'Album': message = self.dialog_renderer.render( "ListeningTo{}".format(data_type), { 'album': data['title'], 'artist': data['artist'] }) elif data_type == 'Song': message = self.dialog_renderer.render( "ListeningTo{}".format(data_type), { 'tracks': data['title'], 'artist': data['artist'] }) elif data_type == 'Artist': message = self.dialog_renderer.render( "ListeningTo{}".format(data_type), {'artist': data['artist']}) elif data_type == 'Playlist': message = self.dialog_renderer.render( "ListeningTo{}".format(data_type), {'playlist': data['name']}) elif data_type == 'Genre': message = self.dialog_renderer.render( "ListeningTo{}".format(data_type), {'genre': data['genre']}) return message def _get_track_url_from_album(self, track_asin, album_asin): LOG.debug("track_asin {}, album_asin {}".format( track_asin, album_asin)) stream_url = "" album = self.am.get_album(album_asin) for track in album.tracks: if (track.identifierType == 'ASIN') and \ (track.identifier == track_asin): LOG.debug("getting url for {}".format(track.name)) try: stream_url = track.stream_url except Exception as e: LOG.error(e) break # LOG.debug(stream_url) return stream_url def stop(self): if self.state != 'idle': self.mediaplayer.stop() self.state = 'idle' return True else: return False def pause(self): if self.state == 'playing': self.mediaplayer.pause() self.state = 'paused' return True return False def resume(self): if self.state == 'paused': self.mediaplayer.resume() self.state = 'playing' return True return False def next(self): if self.state == 'playing': self.mediaplayer.next() return True return False def previous(self): if self.state == 'playing': self.mediaplayer.previous() return True return False def lower_volume(self): if self.state == 'playing': self.mediaplayer.lower_volume() return True return False def restore_volume(self): if self.state == 'playing': self.mediaplayer.restore_volume() return True return False def shutdown(self): if self.state != 'idle': self.mediaplayer.stop() self.mediaplayer.clear_list() def _track_start_handler(self): LOG.debug("_track_start_handler") def _playing_track_handler(self): LOG.debug("_playing_track_handler") def _track_info_handler(self): LOG.debug("_track_info_handler") # @intent_file_handler('music.amzn.intent') # def handle_music_amzn(self, message): # self.speak_dialog('music.amzn') """ Read credentials from file 'credentials.store' that is located in the skills base directory, e.g. /opt/mycroft/skills/amzn-music.comcross The credentials file is a pickled dictionary where the data is base64 encoded. This isn't super secure but will hinder the casual shoulder surfer to read the password """ def _load_credentials_store(self): credentials = {} skill_dir = dirname(__file__) credentials_file = 'credentials.store' if path.exists(skill_dir): file_list = listdir(skill_dir) if credentials_file in file_list: with open(skill_dir + '/' + credentials_file, 'rb') as f: credentials = pickle.load(f) return credentials def _load_vocab_files(self): # Keep a list of all the vocabulary words for this skill. Later # these words will be removed from utterances as part of the station # name. vocab_dirs = [ join(dirname(__file__), 'vocab', self.lang), join(dirname(__file__), 'locale', self.lang) ] for vocab_dir in vocab_dirs: if path.exists(vocab_dir): for vocab_type in listdir(vocab_dir): if vocab_type.endswith(".voc"): with open(join(vocab_dir, vocab_type), 'r') as vocfile: for line in vocfile: parts = line.strip().split("|") vocab = parts[0] self.vocabs.append(vocab) if not self.vocabs: LOG.error('No vocab loaded, ' + vocab_dirs + ' does not exist') def _clean_utterance(self, utterance): # LOG.debug("in {}".format(utterance)) utt = utterance.split(" ") common_words = self.translate("common.words").split(",") # LOG.debug("common_words {}".format(common_words)) # LOG.debug("vocabs {}".format(self.vocabs)) for i in range(0, len(utt)): if utt[i] in self.vocabs or utt[i] in common_words: utt[i] = "" res = "" for u in utt: res += "{} ".format(u) prev_len = len(res) + 1 while prev_len > len(res): res.replace(" ", " ") prev_len = len(res) # LOG.debug("out {}".format(res)) return res.strip() def _get_match_level_by_category(self, cat_name): LOG.debug(cat_name) category = cat_name.lower() if "artists" in category: return CPSMatchLevel.ARTIST elif "tracks" in category: return CPSMatchLevel.TITLE elif "albums" in category: return CPSMatchLevel.TITLE elif "stations" in category: return CPSMatchLevel.CATEGORY elif "playlists" in category: return CPSMatchLevel.CATEGORY else: return None
class PodcastSkill(CommonPlaySkill): def __init__(self): super(PodcastSkill, self).__init__(name="PodcastSkill") self.process = None self.user_agent = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11' self.mediaplayer = VlcService(config={'low_volume': 10, 'duck': True}) self.state = 'idle' self.cps_id = "amzn-music" def initialize(self): self.mediaplayer.clear_list() # Setup handlers for playback control messages self.add_event('mycroft.audio.service.next', self.next) self.add_event('mycroft.audio.service.prev', self.previous) self.add_event('mycroft.audio.service.pause', self.pause) self.add_event('mycroft.audio.service.resume', self.resume) self.add_event('mycroft.audio.service.lower_volume', self.lower_volume) self.add_event('mycroft.audio.service.restore_volume', self.restore_volume) def CPS_match_query_phrase(self, phrase): self.log.debug("phrase {}".format(phrase)) # Not ready to play if not self.mediaplayer: return None data = None best_index = -1 best_confidence = 0.0 if 'podcast' in phrase.lower(): bonus = 0.1 else: bonus = 0 podcast_names = [self.settings["nameone"], self.settings["nametwo"], self.settings["namethree"]] podcast_urls = [self.settings["feedone"], self.settings["feedtwo"], self.settings["feedthree"]] # fuzzy matching for index, name in enumerate(podcast_names): confidence = min(fuzzy_match(name.lower(), phrase.lower()) + bonus, 1.0) if confidence > best_confidence: best_index = index best_confidence = confidence self.log.debug("index {}, name {}, confidence {}".format(index, name, confidence)) # check for exact match data = self.chosen_podcast(phrase, podcast_names, podcast_urls) if data: confidence = CPSMatchLevel.EXACT elif best_index >= 0: data = podcast_urls[best_index] if best_confidence > 0.9: confidence = CPSMatchLevel.EXACT elif best_confidence > 0.6: confidence = CPSMatchLevel.TITLE elif best_confidence > 0.1: confidence = CPSMatchLevel.CATEGORY else: confidence = CPSMatchLevel.GENERIC self.log.info("phrase: {} confidence: {} data: {}".format(phrase, confidence, data)) return phrase, confidence, data def CPS_start(self, phrase, data): self.log.info("CPS_start phrase: {} data: {}".format(phrase, data)) tracklist = [] parsed_feed = pp.parse(data, urllib.request.urlopen(Request(data, data=None, headers={'User-Agent': self.user_agent})) ) episode_title = (parsed_feed['episodes'][0]['title']) # try and parse the rss feed, some are incompatible try: episode = (parsed_feed["episodes"][0]["enclosures"][0]["url"]) except: self.speak_dialog('badrss') # check for any redirects episode = urllib.request.urlopen(Request(episode, data=None, headers={'User-Agent': self.user_agent})) redirected_episode = episode.geturl() http_episode = re.sub('https', 'http', redirected_episode) self.log.info("http_episode: {}".format(http_episode)) tracklist.append(http_episode) if self.state in ['playing', 'paused']: self.mediaplayer.stop() self.mediaplayer.clear_list() self.mediaplayer.add_list(tracklist) # self.speak(self._get_play_message(data)) self.mediaplayer.play() self.state = 'playing' def chosen_podcast(self, utter, podcast_names, podcast_urls): for index, name in enumerate(podcast_names): # skip if podcast slot left empty if not name: continue if name.lower() in utter.lower(): listen_url = podcast_urls[index] break else: listen_url = "" return listen_url @intent_file_handler('PlayPodcast.intent') def handle_play_podcast_intent(self, message): utter = message.data['utterance'] self.enclosure.mouth_think() podcast_names = [self.settings["nameone"], self.settings["nametwo"], self.settings["namethree"]] podcast_urls = [self.settings["feedone"], self.settings["feedtwo"], self.settings["feedthree"]] for try_count in range(0, 2): listen_url = self.chosen_podcast(utter, podcast_names, podcast_urls) if listen_url: break utter = self.get_response('nomatch') else: self.speak_dialog('not.found') return False # normalise feed and parse it normalised_feed = pp.normalize_feed_url(listen_url) parsed_feed = pp.parse(normalised_feed, urllib.request.urlopen(Request(normalised_feed, data=None, headers={'User-Agent': self.user_agent}))) # Check what episode the user wants episode_index = 0 # This block adds functionality for the user to choose an episode while(True): episode_title = parsed_feed['episodes'][episode_index]['title'] podcast_title = parsed_feed['title'] data_dict = {"podcast_title": podcast_title, "episode_title": episode_title} if episode_index == 0: response = self.get_response('play.previous', data=data_dict, on_fail='please.repeat') else: response = self.get_response('play.next.previous', data=data_dict, on_fail='please.repeat') # error check if response is None: break if "stop" in response: self.speak("Operation cancelled.") return False elif "play" in response: break elif "previous" in response: episode_index += 1 elif "next" in response: # ensure index doesnt go below zero if episode_index != 0: episode_index -= 1 self.speak("Playing podcast.") wait_while_speaking() # try and parse the rss feed, some are incompatible try: episode = (parsed_feed["episodes"][episode_index]["enclosures"][0]["url"]) except: self.speak_dialog('badrss') # check for any redirects episode = urllib.request.urlopen(Request(episode, data=None, headers={'User-Agent': self.user_agent})) redirected_episode = episode.geturl() # convert stream to http for mpg123 compatibility http_episode = re.sub('https', 'http', redirected_episode) # if audio service module is available use it if self.audioservice: self.audioservice.play(http_episode, message.data['utterance']) else: # othervice use normal mp3 playback self.process = play_mp3(http_episode) self.enclosure.mouth_text(episode_title) @intent_file_handler('LatestEpisode.intent') def handle_latest_episode_intent(self, message): utter = message.data['utterance'] self.enclosure.mouth_think() podcast_names = [self.settings["nameone"], self.settings["nametwo"], self.settings["namethree"]] podcast_urls = [self.settings["feedone"], self.settings["feedtwo"], self.settings["feedthree"]] # check if the user specified a podcast to check for a new podcast for index, name in enumerate(podcast_names): # skip if podcast slot left empty if not name: continue if name.lower() in utter.lower(): parsed_feed = pp.parse(podcast_urls[index], urllib.request.urlopen(Request(podcast_urls[index], data=None, headers={'User-Agent': self.user_agent}))) last_episode = (parsed_feed['episodes'][0]['title']) speech_string = "The latest episode of " + name + " is " + last_episode break else: # if no podcast names are provided, list all new episodes new_episodes = [] for index, url in enumerate(podcast_urls): # skip if url slot left empty if not url: continue parsed_feed = pp.parse(podcast_urls[index], urllib.request.urlopen(Request(podcast_urls[index], data=None, headers={'User-Agent': self.user_agent}))) last_episode = (parsed_feed['episodes'][0]['title']) new_episodes.append(last_episode) # skip if i[0] slot left empty elements = [": ".join(i) for i in zip(podcast_names, new_episodes) if i[0]] speech_string = "The latest episodes are the following: " speech_string += ", ".join(elements[:-2] + [" and ".join(elements[-2:])]) self.speak(speech_string) def stop(self): if self.state != 'idle': self.mediaplayer.stop() self.state = 'idle' return True else: return False def pause(self): if self.state == 'playing': self.mediaplayer.pause() self.state = 'paused' return True return False def resume(self): if self.state == 'paused': self.mediaplayer.resume() self.state = 'playing' return True return False def next(self): if self.state == 'playing': self.mediaplayer.next() return True return False def previous(self): if self.state == 'playing': self.mediaplayer.previous() return True return False def lower_volume(self): if self.state == 'playing': self.mediaplayer.lower_volume() return True return False def restore_volume(self): if self.state == 'playing': self.mediaplayer.restore_volume() return True return False def shutdown(self): if self.state != 'idle': self.mediaplayer.stop() self.mediaplayer.clear_list()
class IHeartRadioSkill(CommonPlaySkill): def __init__(self): super().__init__(name="IHeartRadioSkill") self.mediaplayer = VlcService(config={'low_volume': 10, 'duck': True}) self.audio_state = "stopped" # 'playing', 'stopped' self.station_name = None self.station_id = None self.stream_url = None self.regexes = {} self.mute_commercials = False def initialize(self): self.gui.register_handler('skill.pause.event', self.handle_pause_event) self.gui.register_handler('skill.timer.event', self.setCurrentTrack) self.gui.register_handler('skill.mute.event', self.handle_mute_event) self.gui.register_handler('skill.volume.event', self.handle_volume_event) self.settings_change_callback = self.set_urls self.set_urls() def handle_pause_event(self, message): if self.audio_state == "playing": LOG.debug("Pause clicked") self.gui["playPauseImage"] = "play.svg" self.gui["audio_state"] = "stopped" # needed for gui Timer self.stop() else: LOG.debug("Play clicked") self.gui["playPauseImage"] = "pause.svg" self.audio_state = "playing" self.gui["audio_state"] = "playing" # needed for gui Timer self.stream_url = self.gui["streamURL"] # Restore these variables self.station_id = self.gui["stationID"] tracklist = [] tracklist.append(self.stream_url) self.mediaplayer.add_list(tracklist) self.mediaplayer.play() def handle_mute_event(self, message): muteState = message.data["state"] LOG.info("in mute event: "+str(muteState)) self.mute_commercials = muteState # Volume can be done via voice or by the slider on the UI def handle_volume_event(self, message): level = message.data["level"] LOG.debug("in volume event: "+str(level)) self.bus.emit(Message("mycroft.volume.set", data={"percent": level/10 })) # Needs value between 0.0 and 1.0 def set_urls(self): country = self.settings.get('country', 'default') country_code = self.location['city']['state']['country']['code'].lower() if country == 'default' else country if country_code[-1] != '.': country_code = country_code + '.' if country == 'global' or not self.test_for_local_api(country_code): country_code = '' self.search_url = "http://{}api.iheart.com/api/v3/search/all".format(country_code) self.station_url = "https://{}api.iheart.com/api/v2/content/liveStations/".format(country_code) self.currentTrack_url = "https://{}api.iheart.com/api/v3/live-meta/stream/".format(country_code) def test_for_local_api(self, country_code): try: payload = { "keywords" : "test", "maxRows" : 1, "bundle" : "false", "station" : "true", "artist" : "false", "track" : "false", "playlist" : "false", "podcast" : "false" } search_url = "http://{}api.iheart.com/api/v3/search/all".format(country_code) r = requests.get(search_url, params=payload, headers=headers) return r.status_code == 200 except: return False def CPS_match_query_phrase(self, phrase): # Look for regex matches starting from the most specific to the least # Play <data> internet radio on i heart radio match = re.search(self.translate_regex('internet_radio_on_iheart'), phrase) if match: data = re.sub(self.translate_regex('internet_radio_on_iheart'), '', phrase) LOG.debug("CPS Match (internet_radio_on_iheart): " + data) return phrase, CPSMatchLevel.EXACT, data # Play <data> radio on i heart radio match = re.search(self.translate_regex('radio_on_iheart'), phrase) if match: data = re.sub(self.translate_regex('radio_on_iheart'), '', phrase) LOG.debug("CPS Match (radio_on_iheart): " + data) return phrase, CPSMatchLevel.EXACT, data # Play <data> on i heart radio match = re.search(self.translate_regex('on_iheart'), phrase) if match: data = re.sub(self.translate_regex('on_iheart'), '', phrase) LOG.debug("CPS Match (on_iheart): " + data) return phrase, CPSMatchLevel.EXACT, data # Play <data> internet radio match = re.search(self.translate_regex('internet_radio'), phrase) if match: data = re.sub(self.translate_regex('internet_radio'), '', phrase) LOG.debug("CPS Match (internet_radio): " + data) return phrase, CPSMatchLevel.CATEGORY, data # Play <data> radio match = re.search(self.translate_regex('radio'), phrase) if match: data = re.sub(self.translate_regex('radio'), '', phrase) LOG.debug("CPS Match (radio): " + data) return phrase, CPSMatchLevel.CATEGORY, data return phrase, CPSMatchLevel.GENERIC, phrase def CPS_start(self, phrase, data): LOG.debug("CPS Start: " + data) self.find_station(data) @intent_file_handler('StreamRequest.intent') def handle_stream_intent(self, message): self.find_station(message.data["station"]) LOG.debug("Station data: " + message.data["station"]) def find_station(self, search_term): tracklist = [] payload = { "keywords" : search_term, "maxRows" : 1, "bundle" : "false", "station" : "true", "artist" : "false", "track" : "false", "playlist" : "false", "podcast" : "false" } # get the response from the IHeartRadio API search_res = requests.get(self.search_url, params=payload, headers=headers) search_obj = json.loads(search_res.text) if (len(search_obj["results"]["stations"]) > 0): self.station_name = search_obj["results"]["stations"][0]["name"] self.station_id = search_obj["results"]["stations"][0]["id"] LOG.debug("Station name: " + self.station_name + " ID: " + str(self.station_id)) # query the station URL using the ID station_res = requests.get(self.station_url+str(self.station_id)) station_obj = json.loads(station_res.text) LOG.debug("Logo Url: "+station_obj["hits"][0]["logo"]) self.audio_state = "playing" self.gui["audio_state"] = "playing" self.speak_dialog("now.playing", {"station": self.station_name} ) wait_while_speaking() # Use the first stream URL for x in list(station_obj["hits"][0]["streams"])[0:1]: self.stream_url = station_obj["hits"][0]["streams"][x] break LOG.debug("Station URL: " + self.stream_url) self.gui["streamURL"] = self.stream_url self.gui["stationID"] = self.station_id self.gui["logoURL"] = station_obj["hits"][0]["logo"] self.gui["description"] = station_obj["hits"][0]["description"] self.gui["playPauseImage"] = "pause.svg" self.setCurrentTrack("") self.gui.show_page("controls.qml", override_idle=True) tracklist.append(self.stream_url) self.mediaplayer.add_list(tracklist) self.mediaplayer.play() else: self.speak_dialog("not.found") wait_while_speaking() def setCurrentTrack(self,message): currentTrack_res = requests.get(self.currentTrack_url+str(self.station_id)+"/currentTrackMeta") LOG.debug("Current Track Response: "+str(currentTrack_res.status_code)) if currentTrack_res.status_code == 204 and self.mute_commercials: self.mediaplayer.pause() # Pause until a song is put back on the Currently Playing list if currentTrack_res.status_code == 200: self.mediaplayer.resume() # Just in case we are paused... currentTrack_obj = json.loads(currentTrack_res.text) self.gui["title"] = currentTrack_obj["title"] self.gui["artist"] = "Artist: "+currentTrack_obj["artist"] self.gui["album"] = "Album: "+currentTrack_obj["album"] if "imagePath" in currentTrack_obj: self.gui["currentTrackImg"] = currentTrack_obj["imagePath"] else: self.gui["currentTrackImg"] = self.gui["logoURL"] # Load the previous three songs trackHistory_res = requests.get(self.currentTrack_url+str(self.station_id)+"/trackHistory?limit=3") LOG.debug("Track History Response: "+str(trackHistory_res.status_code)) if trackHistory_res.status_code == 200: trackHistory_obj = json.loads(trackHistory_res.text) # Setup previous 1 self.gui["previous1Title"] = trackHistory_obj["data"][0]["title"] self.gui["previous1Artist"] = "Artist: "+trackHistory_obj["data"][0]["artist"] self.gui["previous1Album"] = "Artist: "+trackHistory_obj["data"][0]["album"] if "imagePath" in trackHistory_obj["data"][0]: self.gui["previous1Img"] = trackHistory_obj["data"][0]["imagePath"] else: self.gui["previous1Img"] = self.gui["logoURL"] # Setup previous 2 self.gui["previous2Title"] = trackHistory_obj["data"][1]["title"] self.gui["previous2Artist"] = "Artist: "+trackHistory_obj["data"][1]["artist"] self.gui["previous2Album"] = "Artist: "+trackHistory_obj["data"][1]["album"] if "imagePath" in trackHistory_obj["data"][1]: self.gui["previous2Img"] = trackHistory_obj["data"][1]["imagePath"] else: self.gui["previous2Img"] = self.gui["logoURL"] # Setup previous 3 self.gui["previous3Title"] = trackHistory_obj["data"][2]["title"] self.gui["previous3Artist"] = "Artist: "+trackHistory_obj["data"][2]["artist"] self.gui["previous3Album"] = "Artist: "+trackHistory_obj["data"][2]["album"] if "imagePath" in trackHistory_obj["data"][2]: self.gui["previous3Img"] = trackHistory_obj["data"][2]["imagePath"] else: self.gui["previous3Img"] = self.gui["logoURL"] def stop(self): if self.audio_state == "playing": self.mediaplayer.stop() self.mediaplayer.clear_list() LOG.debug("Stopping stream") self.audio_state = "stopped" self.station_name = None self.station_id = None self.stream_url = None return True def shutdown(self): if self.audio_state == 'playing': self.mediaplayer.stop() self.mediaplayer.clear_list() # Get the correct localized regex def translate_regex(self, regex): if regex not in self.regexes: path = self.find_resource(regex + '.regex') if path: with open(path) as f: string = f.read().strip() self.regexes[regex] = string return self.regexes[regex]