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 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 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 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()