Example #1
0
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]
Example #2
0
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
Example #3
0
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()
Example #4
0
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()