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 __init__(self): super(USBMusicSkill, self).__init__('USBMusicSkill') self.mediaplayer = VlcService(config={'low_volume': 10, 'duck': True}) self.song_list = [] self.prev_status = False self.song_artist = "" self.song_label = "" self.song_album = "" self.auto_play = False self.insert_command = "" self.command_enable = False self.prev_status = False self.status = False self.library_ready = False self.path = "" self.local_path = "" self.smb_path = "" self.smb_uname = "" self.smb_pass = "" self.usb_monitor = NewThread self.usbdevice = usbdev self.observer = self.usbdevice.startListener() #self.audio_service = None self.audio_state = 'stopped' # 'playing', 'stopped' LOG.info("USB Music Skill Loaded!")
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 __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 __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 __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 __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 __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'
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
def initialize(self): self.uri = self.settings.get("musicsource", "") self.token = self.settings.get("plextoken", "") self.lib_name = self.settings.get("plexlib", "") self.client = self.settings.get("plexclient", "") self.ducking = self.settings.get("ducking", "True") self.p_uri = self.uri+":32400" if self.load_plex_backend(): if not os.path.exists(self.data_path): self.speak_dialog("library.unknown") self.load_data() self.vlc_player = VlcService(config={'duck': self.ducking}) self.vlc_player.normal_volume = 85 self.vlc_player.low_volume = 20 if self.ducking: self.add_event('recognizer_loop:record_begin', self.handle_listener_started) self.add_event('recognizer_loop:record_end', self.handle_listener_stopped) self.add_event('recognizer_loop:audio_output_start', self.handle_audio_start) self.add_event('recognizer_loop:audio_output_end', self.handle_audio_stop)
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]
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 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 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 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 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 USBMusicSkill(CommonPlaySkill): def __init__(self): super(USBMusicSkill, self).__init__('USBMusicSkill') self.mediaplayer = VlcService(config={'low_volume': 10, 'duck': True}) self.song_list = [] self.prev_status = False self.song_artist = "" self.song_label = "" self.song_album = "" self.auto_play = False self.insert_command = "" self.command_enable = False self.prev_status = False self.status = False self.library_ready = False self.path = "" self.local_path = "" self.smb_path = "" self.smb_uname = "" self.smb_pass = "" self.usb_monitor = NewThread self.usbdevice = usbdev self.observer = self.usbdevice.startListener() #self.audio_service = None self.audio_state = 'stopped' # 'playing', 'stopped' LOG.info("USB Music Skill Loaded!") def initialize(self): self.load_data_files(dirname(__file__)) #self.audio_service = AudioService(self.bus) LOG.info("USB Music Skill Initialized!") self.halt_usb_monitor_thread() self.init_usb_monitor_thread() self.settings_change_callback = self.on_websettings_changed self.on_websettings_changed() def on_websettings_changed(self): # called when updating mycroft home page self.auto_play = self.settings.get( "auto_play", False) # used to enable / disable auto_play self.local_path = self.settings.get("local_path", "/home/pi/Music") self.smb_path = self.settings.get("smb_path", "//192.168.0.20/SMBMusic") self.smb_uname = self.settings.get("smb_uname", "guest") self.smb_pass = self.settings.get("smb_pass", "") self.insert_command = self.settings.get("insert_command", "") if len(self.insert_command) > 0: self.command_enable = self.settings.get("command_enable", False) else: LOG.info('No Command Specified, Enable Set to: False') self.command_enable = False LOG.info('USB-Music Settings Changed, Command Enable now: ' + str(self.command_enable)) LOG.info('USB-Music Settings Changed, AutoPlay now: ' + str(self.auto_play)) LOG.info('USB-Music Settings Changed, SMB Path now: ' + str(self.smb_path)) LOG.info('USB-Music Settings Changed, Local Path now: ' + str(self.local_path)) def send_message( self, message): # Sends the remote received commands to the messagebus LOG.info("Sending a command to the message bus: " + message) payload = json.dumps({ "type": "recognizer_loop:utterance", "context": "", "data": { "utterances": [message] } }) uri = 'ws://localhost:8181/core' ws = create_connection(uri) ws.send(payload) ws.close() def init_usb_monitor_thread(self): # creates the workout thread self.usb_monitor.idStop = False self.usb_monitor.id = 101 self.usb_monitor.idThread = threading.Thread( target=self.start_usb_thread, args=(self.usb_monitor.id, lambda: self.usb_monitor.idStop)) self.usb_monitor.idThread.start() def halt_usb_monitor_thread(self): # requests an end to the workout try: self.usb_monitor.id = 101 self.usb_monitor.idStop = True self.usb_monitor.idThread.join() except Exception as e: LOG.error( e) # if there is an error attempting the workout then here.... def numeric_replace(self, in_words=""): word_list = in_words.split() return_list = [] for each_word in word_list: try: new_word = w2n.word_to_num(each_word) except Exception as e: # LOG.info(e) new_word = each_word return_list.append(new_word) return_string = ' '.join(str(e) for e in return_list) return return_string def parse_music_utterance(self, phrase): # Todo: move Regex to file for language support # Todo: This needs to be refactored as it is not a sustainable way to search music # returns what was spoken in the utterance return_item = "none" return_type = "any" str_request = str(phrase) LOG.info("Parse Music Received: " + str_request) primary_regex = r"((?<=album) (?P<album>.*$))|((?<=by) (?P<artist1>.*$))|((?<=artist) (?P<artist>.*$))|((?<=song) (?P<label>.*$))" secondary_regex = None if str_request.find('some') != -1: secondary_regex = r"((?<=some) (?P<any>.*$))" elif str_request.find('my') != -1: secondary_regex = r"((?<=my) (?P<any>.*$))" elif str_request.find('all') != -1: secondary_regex = r"((?<=all) (?P<any>.*$))" elif str_request.find('any') != -1: secondary_regex = r"((?<=any) (?P<any>.*$))" else: secondary_regex = r"((?<=play) (?P<any>.*$))" key_found = re.search(primary_regex, str_request) if key_found: LOG.info("Primary Regex Key Found") if key_found.group("label"): LOG.info("found label") return_item = key_found.group("label") return_type = "label" elif key_found.group("artist"): LOG.info("found artist") return_item = key_found.group("artist") return_type = "artist" elif key_found.group("artist1"): # secondary artist LOG.info("found artist") return_item = key_found.group("artist1") return_type = "artist" elif key_found.group("album"): LOG.info("found album") return_item = key_found.group("album") return_type = "album" else: LOG.info("Primary Regex Key Not Found") if secondary_regex: key_found = re.search(secondary_regex, str_request) if key_found.group("any"): LOG.info("Secondary Regex Key Found") return_item = key_found.group("any") return_type = "any" else: LOG.info("Secondary Regex Key Not Found") return_item = "none" return_type = "none" # Returns the item that was requested and the type of the requested item ie. artist, album, label return return_item, return_type def search_music_library(self, search_string, category="any"): found_list = [ ] # this is a dict that will contain all the items found in the library LOG.info("searching the music library for: " + str(search_string) + ", " + str(category)) if category == "any": found_list = self.search_music_item(search_string, category="label") if len(found_list) > 0: return found_list LOG.info("Label: " + search_string + ", Not Found!") found_list = self.search_music_item(search_string, category="artist") if len(found_list) > 0: return found_list LOG.info("Artist: " + search_string + ", Not Found!") found_list = self.search_music_item(search_string, category="album") if len(found_list) > 0: return found_list found_list = self.search_music_item(search_string, category="location") if len(found_list) > 0: return found_list if len(found_list) == 0: LOG.info("Album: " + search_string + ", Not Found!") return else: found_list = self.search_music_item(search_string, category=str(category)) if len(found_list) > 0: return found_list def search_music_item(self, search_item, category="label"): # category options: label, artist, album found_list = [ ] # this is a dict of all the items found that match the search search_item = self.numeric_replace(search_item) search_words = search_item.replace("-", "").lower().split() # check each song in the list for strings that match all the words in the search for each_song in self.song_list: # check each song in the list for the one we are looking for item_name = each_song[category].replace("-", "") if len(item_name) > 0: item_name = self.numeric_replace(item_name) if all(words in item_name.lower() for words in search_words): found_length = len(each_song['label'].split()) info = { "location": each_song['location'], "label": each_song['label'], "album": each_song['album'], "artist": each_song['artist'], "source": each_song['source'] } found_list.append(info) LOG.info('Found the following songs: ' + str(found_list)) # remove duplicates temp_list = [] # this is a dict for each_song in found_list: info = { "location": each_song['location'], "label": each_song['label'], "album": each_song['album'], "artist": each_song['artist'], "source": each_song['source'] } # Todo this is missing in the kodi skill???? song_title = str(each_song['label']) if song_title not in str(temp_list): temp_list.append(info) else: if len(each_song['label']) == len(song_title): LOG.info('found duplicate') else: temp_list.append(info) found_list = temp_list return found_list # returns a dictionary of matched movies def merge_library(self, dict1, dict2): return dict1 + dict2 def start_usb_thread(self, my_id, terminate): """ This thread monitors the USB port for an insertion / removal event """ # Todo automatically play when stick is inserted LOG.info("USB Monitoring Loop Started!") while not terminate(): # wait while this interval completes time.sleep( 1 ) # Todo make the polling time a variable or make it a separate thread # get the status of the connected usb device self.status = self.usbdevice.isDeviceConnected() if self.status != self.prev_status: LOG.info("USB Status Changed!") self.prev_status = self.status if self.status: # Device inserted # remove any existing mount points self.usbdevice.uMountPathUsbDevice() LOG.info("Device Inserted!") device = self.usbdevice.getDevData() # mount the device and get the path self.path = self.usbdevice.getMountPathUsbDevice() LOG.info("Stat: " + str(self.status)) LOG.info("dev: " + str(device)) LOG.info("path: " + str(self.path)) LOG.info("---------------------------------") self.speak_dialog('update.library', data={"source": str("usb")}, expect_response=False) wait_while_speaking() self.song_list = [ i for i in self.song_list if not (i['source'] == 'usb') ] self.song_list = self.merge_library( self.song_list, self.create_library(self.path, "usb")) if self.command_enable: self.send_message(self.insert_command) if self.auto_play: self.play_all(self.song_list) else: #self.audio_service.stop() self.mediaplayer.stop() # unmount the path self.usbdevice.uMountPathUsbDevice() LOG.info("Device Removed!") # Todo remove context "USB" so all play requests start with this skill self.speak_dialog('usb.removed', expect_response=False) wait_while_speaking() self.song_list = [] self.path = "" self.on_websettings_changed() self.usbdevice.stopListener(self.observer) def play_all(self, library): LOG.info('Automatically playing the USB Device') tracklist = [] for each_song in library: LOG.info("CPS Now Playing... " + each_song['label'] + " from location: " + each_song['location']) url = each_song['location'] tracklist.append(url) random.shuffle(tracklist) self.speak_dialog('now.playing') wait_while_speaking() self.mediaplayer.add_list(tracklist) self.mediaplayer.play() #self.audio_service.play(tracklist) self.audio_state = 'playing' def create_library(self, source_path, source_type="usb"): # Todo - add regex to remove numbers from the begining of filenames to get song name (\d{1,3}|)( - |)(?P<song_Name>.+) self.library_ready = False new_library = [] for root, d_names, f_names in os.walk(str(source_path)): for fileName in f_names: audio = None foundType = [ musicType for musicType in MUSIC_TYPES if (musicType.lower() in fileName.lower()) ] if bool(foundType): song_path = str(root) + "/" + str(fileName) if True: # try: # Removed to find error if "flac" in str(foundType[0]): # add flac filter audio = FLAC(song_path) # LOG.info("Checking FLAC Tags" + str(audio)) elif "aac" in str(foundType[0]): # add flac filter: audio = AAC(song_path) # LOG.info("Checking aac Tags" + str(audio)) elif "mp3" in str(foundType[0]): # add flac filter: try: audio = EasyID3(song_path) except ID3NoHeaderError: LOG.info("No ID Tags Found... " + song_path) audio = {} # LOG.info("Checking mp3 Tags" + str(audio)) elif "m4a" in str(foundType[0]): # add flac filter: audio = MP4(song_path) # LOG.info("Checking m4a Tags" + str(audio)) if audio is not None: # An ID3 tag found if 'title' not in audio.keys(): trim_length = (len(str(foundType[0])) + 1) * -1 self.song_label = str(fileName)[:trim_length] else: self.song_label = audio['title'][0] #LOG.info("Validating title: " + self.song_label) if 'artist' not in audio.keys(): if 'Contributing artists' in audio.keys(): self.song_artist = audio[ 'Contributing artists'][0] else: self.song_artist = "" else: self.song_artist = audio['artist'][0] #LOG.info("Validating artist: " + self.song_artist) if 'album' not in audio.keys(): self.song_album = "" else: self.song_album = audio['album'][0] else: # There was no ID3 Tag found, use filename as song title trim_length = (len(str(foundType[0])) + 1) * -1 self.song_label = str(fileName)[:trim_length] self.song_artist = "" self.song_album = "" info = { "location": song_path, "label": self.song_label, "artist": self.song_artist, "album": self.song_album, "source": str(source_type) } new_library.append(info) song_count = len(new_library) if song_count == 0: self.speak_dialog('no.files', data={"source": str(source_type)}, expect_response=False) else: self.speak_dialog('scan.complete', data={ "count": str(song_count), "source": str(source_type) }, expect_response=False) wait_while_speaking() LOG.info("Added: " + str(song_count) + " to the library from the " + str(source_type) + " Device") self.library_ready = True return new_library def CPS_match_query_phrase(self, phrase): """ The method is invoked by the PlayBackControlSkill. """ LOG.info('USBMusicSkill received the following phrase: ' + phrase) if self.status or self.library_ready: # Confirm the USB is inserted LOG.info("USBMusicSkill is Searching for requested media...") play_request = self.parse_music_utterance( phrase) # get the requested Music Item LOG.info("USBMusicSkill Parse Routine Returned: " + str(play_request)) music_playlist = self.search_music_library( play_request[0], category=play_request[1]) # search for the item in the library if music_playlist is None: return None # until a match is found else: if len(music_playlist) > 0: match_level = CPSMatchLevel.EXACT data = music_playlist LOG.info('Music found that matched the request!') return phrase, match_level, data else: return None # until a match is found else: LOG.info("Device or Library Not Ready, Passing on this request") 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) """ tracklist = [] LOG.info( 'USBMusicSkill, Playback received the following phrase and Data: ' + phrase + ' ' + str(data)) for each_song in data: LOG.info("CPS Now Playing... " + each_song['label'] + " from location: " + each_song['location']) url = each_song['location'] tracklist.append(url) #LOG.info(str(tracklist)) self.speak_dialog('now.playing') wait_while_speaking() #self.audio_service.play(tracklist) #random.shuffle(tracklist) self.mediaplayer.add_list(tracklist) self.mediaplayer.play() self.audio_state = 'playing' pass @intent_handler( IntentBuilder('').require("UpdateKeyword").require( "USBKeyword").require("LibraryKeyword")) def handle_update_usb_library_intent(self, message): LOG.info("Called Update Library Intent") if self.usbdevice.isDeviceConnected(): device = self.usbdevice.getDevData() # mount the device and get the path self.path = self.usbdevice.getMountPathUsbDevice() self.speak_dialog( 'update.library', data={"source": str(message.data.get("USBKeyword"))}, expect_response=False) wait_while_speaking() self.song_list = [ i for i in self.song_list if not (i['source'] == 'usb') ] self.song_list = self.merge_library( self.song_list, self.create_library(self.path, "usb")) else: self.usbdevice.uMountPathUsbDevice() # Play Music Added here self.speak_dialog('usb.not.mounted', expect_response=False) wait_while_speaking() LOG.info("USB Device Not Detected") # Todo: Add an unmount / release command @intent_handler( IntentBuilder('').require("RemoveKeyword").require("USBKeyword")) def handle_remove_usb_intent(self, message): self.usbdevice.uMountPathUsbDevice() LOG.info("Device Removed!") @intent_handler( IntentBuilder('').require("UpdateKeyword").require( "NetworkKeyword").require("LibraryKeyword")) def handle_get_smb_music_intent(self, message): self.path = self.usbdevice.MountSMBPath(self.smb_path, self.smb_uname, self.smb_pass) self.speak_dialog( 'update.library', data={"source": str(message.data.get("NetworkKeyword"))}, expect_response=False) wait_while_speaking() self.song_list = [ i for i in self.song_list if not (i['source'] == 'smb') ] self.song_list = self.merge_library( self.song_list, self.create_library(self.path, "smb")) LOG.info("SMB Mounted!") @intent_handler( IntentBuilder('').require("UpdateKeyword").require( "LocalKeyword").require("LibraryKeyword")) def handle_get_local_music_intent(self, message): self.path = self.local_path self.speak_dialog( 'update.library', data={"source": str(message.data.get("LocalKeyword"))}, expect_response=False) wait_while_speaking() self.song_list = [ i for i in self.song_list if not (i['source'] == 'local') ] self.song_list = self.merge_library( self.song_list, self.create_library(self.path, "local")) LOG.info("Local Mounted!") @intent_handler( IntentBuilder('').require("UpdateKeyword").require( "MusicKeyword").require("LibraryKeyword")) def handle_get_All_available_intent(self, message): self.path = self.usbdevice.MountSMBPath(self.smb_path, self.smb_uname, self.smb_pass) self.speak_dialog( 'update.library', data={"source": str(message.data.get("MusicKeyword"))}, expect_response=False) wait_while_speaking() self.song_list = [ i for i in self.song_list if not (i['source'] == 'smb') ] self.song_list = self.merge_library( self.song_list, self.create_library(self.path, "smb")) LOG.info("SMB Mounted!") self.path = self.local_path self.song_list = [ i for i in self.song_list if not (i['source'] == 'local') ] self.song_list = self.merge_library( self.song_list, self.create_library(self.path, "local")) LOG.info("Local Mounted!") if self.usbdevice.isDeviceConnected(): device = self.usbdevice.getDevData() # mount the device and get the path self.path = self.usbdevice.getMountPathUsbDevice() self.speak_dialog( 'update.library', data={"source": str(message.data.get("USBKeyword"))}, expect_response=False) wait_while_speaking() self.song_list = [ i for i in self.song_list if not (i['source'] == 'usb') ] self.song_list = self.merge_library( self.song_list, self.create_library(self.path, "usb")) @intent_handler( IntentBuilder('').require("StartKeyword").require( "USBKeyword").require('ScanKeyword')) def handle_start_usb_intent(self, message): # Todo: add ability to stop the thread when the skill re-loads LOG.info('Thread Running: ' + str(self.usb_monitor.idThread.isAlive())) if self.usb_monitor.idThread.isAlive(): LOG.info("Scan is already running!") else: LOG.info("Scan should start!") self.init_usb_monitor_thread() @intent_handler( IntentBuilder('').require("ShowKeyword").require( "MusicKeyword").require('LibraryKeyword')) def handle_show_music_library_intent(self, message): LOG.info(str(self.song_list)) LOG.info('Library Size: ' + str(len(self.song_list))) def stop(self): if self.audio_state == 'playing': #self.audio_service.stop() self.mediaplayer.stop() LOG.debug('Stopping stream') self.audio_state = 'stopped' return True #LOG.info('Stopping USB Monitor Thread!') #self.halt_usb_monitor_thread() pass
class PlexMusicSkill(CommonPlaySkill): def CPS_match_query_phrase(self, phrase): if self.refreshing_lib: self.speak_dialog("refresh.library") return None else: phrase = re.sub(self.translate_regex('on_plex'), '', phrase) title = "" artist = "" album = "" playlist = "" t_prob = 0 a_prob = 0 al_prob = 0 p_prob = 0 if phrase == "a random album" or phrase == "random album": title = random.choice(list(self.albums.keys())) data = { "title": title, "file": self.albums[title], "media_type": "album" } return phrase, CPSMatchLevel.EXACT, data elif phrase.startswith("artist"): artist, a_prob = self.artist_search(phrase[7:]) elif phrase.startswith("album"): album, al_prob = self.album_search(phrase[6:]) elif phrase.startswith("playlist"): playlist, p_prob = self.playlist_search(phrase[9:]) else: title, t_prob = self.title_search(phrase) artist, a_prob = self.artist_search(phrase) album, al_prob = self.album_search(phrase) playlist, p_prob = self.playlist_search(phrase) print(""" Plex Music skill Title %s %f Artist %s %d Album %s %d Playlist %s %d """ % (title, t_prob, artist, a_prob, album, al_prob, playlist, p_prob)) if t_prob > al_prob and t_prob > a_prob: data = { "title": title, "file": self.titles[title], "media_type": "title" } return phrase, CPSMatchLevel.TITLE, data elif a_prob >= al_prob and a_prob != 0: data = { "title": artist, "file": self.artists[artist], "media_type": "artist" } return phrase, CPSMatchLevel.MULTI_KEY, data elif al_prob >= a_prob and al_prob != 0: data = { "title": album, "file": self.albums[album], "media_type": "album" } return phrase, CPSMatchLevel.MULTI_KEY, data elif p_prob > al_prob: data = { "title": playlist, "file": self.playlists[playlist] } return phrase, CPSMatchLevel.MULTI_KEY, data else: return None def CPS_start(self, phrase, data): if data is None: return None if not self.client: if self.get_running(): self.vlc_player.clear_list() self.vlc_player.stop() title = data["title"] link = data["file"] media_type = data["media_type"] random.shuffle(link) try: if not self.client: self.vlc_player.add_list(link) self.vlc_player.play() """ if len(link) >= 1: self.vlc_player = self.vlcI.media_list_player_new() m = self.vlcI.media_list_new(link) self.vlc_player.set_media_list(m) self.vlc_player.play() elif len(link) > 0: self.vlc_player = self.vlcI.media_player_new() m = self.vlcI.media_new(link[0]) self.vlc_player.set_media(m) self.vlc_player.play() """ else: # plex doesn't take a collection of items like vlc # just pass a single key, and let the backend decide what to do key = self.keys[link[0]] self.plex.play_media(key, media_type) except exceptions.NotFound: LOG.info("Could not connect to configured Plex client") self.speak_dialog("client.connection.problem") return None except Exception as e: LOG.info(type(e)) LOG.info("Unexpected error:", sys.exc_info()[0]) raise finally: time.sleep(2) if not self.client: if not self.get_running(): self.speak_dialog("playback.problem") self.speak_dialog("excuses") def __init__(self): super().__init__(name="TemplateSkill") self.uri = "" self.token = "" self.lib_name = "" self.client = "" self.ducking = "True" self.regexes = {} self.refreshing_lib = False self.p_uri = self.uri+":32400" self.p_token = "?X-Plex-Token="+self.token self.data_path = os.path.expanduser("~/.config/plexSkill/") if not os.path.exists(self.data_path): os.mkdir(self.data_path) self.data_path += "data.json" self.plex = None self.artists = defaultdict(list) self.albums = defaultdict(list) self.titles = defaultdict(list) self.playlists = defaultdict(list) self.keys = defaultdict(list) self.tracks = {} self.vlc_player = None def initialize(self): self.uri = self.settings.get("musicsource", "") self.token = self.settings.get("plextoken", "") self.lib_name = self.settings.get("plexlib", "") self.client = self.settings.get("plexclient", "") self.ducking = self.settings.get("ducking", "True") self.p_uri = self.uri+":32400" if self.load_plex_backend(): if not os.path.exists(self.data_path): self.speak_dialog("library.unknown") self.load_data() self.vlc_player = VlcService(config={'duck': self.ducking}) self.vlc_player.normal_volume = 85 self.vlc_player.low_volume = 20 if self.ducking: self.add_event('recognizer_loop:record_begin', self.handle_listener_started) self.add_event('recognizer_loop:record_end', self.handle_listener_stopped) self.add_event('recognizer_loop:audio_output_start', self.handle_audio_start) self.add_event('recognizer_loop:audio_output_end', self.handle_audio_stop) def get_running(self): return self.vlc_player.player.is_playing() def load_data(self): LOG.info("loading "+self.data_path) try: if not os.path.isfile(self.data_path): LOG.info("making new JsonData") if self.load_plex_backend(): self.plex.down_plex_lib() self.speak_dialog("done") data = self.json_load(self.data_path) for artist in data: if artist == "playlist": for playlist in data[artist]: for song in data[artist][playlist]: p_artist = song[0] album = song[1] title = song[2] file = song[3] self.playlists[playlist].append(file) self.tracks[file] = (p_artist, album, title) for album in data[artist]: for song in data[artist][album]: title = song[0] file = song[1] # link key = song[2] #media key for remote play self.albums[album].append(file) self.artists[artist].append(file) self.titles[title].append(file) self.tracks[file] = (artist, album, title) self.keys[file] = key finally: self.refreshing_lib = False # thanks to forslund 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] ################################### # Utils def load_plex_backend(self): if self.plex is None: LOG.info("\n\nconnecting to:\n{} \n{} {}\n".format(self.p_uri, self.token, self.lib_name)) if self.token and self.p_uri and self.lib_name: self.plex = PlexBackend(self.p_uri, self.token, self.lib_name, self.data_path, self.client) return True else: self.speak_dialog("config.missing") return False else: return True def json_save(self, data, fname): with open(fname, 'w') as fp: dump(data, fp) def json_load(self, fname): with open(fname, 'r') as fp: return load(fp) def get_tokenized_uri(self, uri): return self.p_uri + uri + self.token def title_search(self, phrase): probabilities = process.extractOne(phrase, self.titles.keys(), scorer=fuzz.ratio) artist = probabilities[0] confidence = probabilities[1] return artist, confidence def artist_search(self, phrase): probabilities = process.extractOne(phrase, self.artists.keys(), scorer=fuzz.ratio) artist = probabilities[0] confidence = probabilities[1] return artist, confidence def album_search(self, phrase): probabilities = process.extractOne(phrase, self.albums.keys(), scorer=fuzz.ratio) album = probabilities[0] confidence = probabilities[1] return album, confidence def playlist_search(self, phrase): probabilities = process.extractOne(phrase, self.playlists.keys(), scorer=fuzz.ratio) playlist = probabilities[0] confidence = probabilities[1] return playlist, confidence ###################################################################### # audio ducking # note: most plex remote clients don't support volume controls, pause playback instead def handle_listener_started(self, message): if self.ducking: if not self.client: self.vlc_player.lower_volume() def handle_listener_stopped(self, message): if self.ducking: if not self.client: self.vlc_player.restore_volume() def handle_audio_start(self, event): if self.ducking: if not self.client: self.vlc_player.lower_volume() def handle_audio_stop(self, event): if self.ducking: if not self.client: self.vlc_player.restore_volume() ################################################################## # intents @intent_file_handler('play.music.intent') def handle_play_music_intent(self, message): pass @intent_file_handler('resume.music.intent') def handle_resume_music_intent(self, message): if self.refreshing_lib: self.speak_dialog("refresh.library") return None else: if not self.client: self.vlc_player.resume() else: self.plex.resume() @intent_file_handler('pause.music.intent') def handle_pause_music_intent(self, message): if self.refreshing_lib: self.speak_dialog("refresh.library") return None else: if not self.client: self.vlc_player.pause() else: self.plex.pause() @intent_file_handler('next.music.intent') def handle_next_music_intent(self, message): if self.refreshing_lib: self.speak_dialog("refresh.library") return None else: if not self.client: self.vlc_player.next() else: self.plex.next() @intent_file_handler('prev.music.intent') def handle_prev_music_intent(self, message): if self.refreshing_lib: self.speak_dialog("refresh.library") return None else: if not self.client: self.vlc_player.previous() else: self.plex.previous() @intent_file_handler('information.intent') def handle_music_information_intent(self, message): if not self.client: if self.get_running(): meta = self.vlc_player.track_info() artist, album, title = meta["artists"], meta["album"], meta["name"] if title.startswith("file"): media = self.vlc_player.player.get_media() link = media.get_mrl() artist, album, title = self.tracks[link] if isinstance(artist, list): artist = artist[0] LOG.info("""\nPlex skill is playing: {} by {} Album: {} """.format(title, artist, album)) self.speak_dialog('information', data={'title': title, "artist": artist}) else: return None @intent_file_handler('reload.library.intent') def handle_reload_library_intent(self, message): if self.refreshing_lib: self.speak_dialog("already.refresh.library") return None else: self.refreshing_lib = True self.speak_dialog("refresh.library") try: os.remove(self.data_path) except FileNotFoundError: pass self.load_data() def converse(self, utterances, lang="en-us"): return False def stop(self): if not self.client: self.vlc_player.stop() else: self.plex.stop()
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]