Exemple #1
0
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
Exemple #2
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]
Exemple #3
0
class TuneinSkill(CommonPlaySkill):

    def __init__(self):
        super().__init__(name="TuneinSkill")

        self.mediaplayer = VlcService(config={'low_volume': 10, 'duck': True})
        self.audio_state = "stopped"  # 'playing', 'stopped'
        self.station_name = None
        self.stream_url = None
        self.mpeg_url = None
        self.regexes = {}
        self.aliases = {}

    def initialize(self):
        self.init_websettings()
        self.settings_change_callback = self.init_websettings

    def init_websettings(self):
        self.get_aliases()

    def CPS_match_query_phrase(self, phrase):
        # Look for regex matches starting from the most specific to the least
        # Play <data> internet radio on tune in
        match = re.search(self.translate_regex('internet_radio_on_tunein'), phrase)
        if match:
            data = re.sub(self.translate_regex('internet_radio_on_tunein'), '', phrase)
            LOG.debug("CPS Match (internet_radio_on_tunein): " + data)
            return phrase, CPSMatchLevel.EXACT, data

        # Play <data> radio on tune in
        match = re.search(self.translate_regex('radio_on_tunein'), phrase)
        if match:
            data = re.sub(self.translate_regex('radio_on_tunein'), '', phrase)
            LOG.debug("CPS Match (radio_on_tunein): " + data)
            return phrase, CPSMatchLevel.EXACT, data

        # Play <data> on tune in
        match = re.search(self.translate_regex('on_tunein'), phrase)
        if match:
            data = re.sub(self.translate_regex('on_tunein'), '', phrase)
            LOG.debug("CPS Match (on_tunein): " + data)
            return phrase, CPSMatchLevel.EXACT, data

        # Play <data> internet radio
        match = re.search(self.translate_regex('internet_radio'), phrase)
        if match:
            data = re.sub(self.translate_regex('internet_radio'), '', phrase)
            LOG.debug("CPS Match (internet_radio): " + data)
            return phrase, CPSMatchLevel.CATEGORY, data

        # Play <data> radio
        match = re.search(self.translate_regex('radio'), phrase)
        if match:
            data = re.sub(self.translate_regex('radio'), '', phrase)
            LOG.debug("CPS Match (radio): " + data)
            return phrase, CPSMatchLevel.CATEGORY, data

        return phrase, CPSMatchLevel.GENERIC, phrase

    def CPS_start(self, phrase, data):
        LOG.debug("CPS Start: " + data)
        self.find_station(data)

    @intent_file_handler('StreamRequest.intent')
    def handle_stream_intent(self, message):
        self.find_station(message.data["station"])
        LOG.debug("Station data: " + message.data["station"])

    def get_aliases(self):
        self.aliases.clear()
        for i in range(0, MAXALIASES):
            _station = self.settings.get(f"station{i}", False)
            _alias = self.settings.get(f"alias{i}", False)
            if _station and _alias:
                self.aliases[_alias.lower()] = _station.lower()

    def remove_aliases(self, search_term):
        """ Applies the aliases either defined in the webconfig or using ~/tunein_aliases.yaml (deprecated)"""
        # backwards compat
        if not self.aliases:
            home = expanduser('~')
            alias_file = home + '/tunein_aliases.yaml'
            if path.exists(alias_file):
                with open(alias_file, 'r') as file:
                    self.aliases = yaml.load(file, Loader=yaml.FullLoader)

        for alias, station in self.aliases.items():
            if alias in search_term:
                search_term = search_term.replace(alias, station)
                LOG.debug(f"Removed alias. Search_term: {search_term}")

        return search_term

    # Attempt to find the first active station matching the query string
    def find_station(self, search_term):

        tracklist = []
        retry = True
        search_term = self.remove_aliases(search_term)

        dom = request_api(search_term)
        # results are each in their own <outline> tag as defined by OPML (https://en.wikipedia.org/wiki/OPML)
        # fuzzy matches the query to the given stations NodeList
        match, perc = _fuzzy_match(search_term, dom.getElementsByTagName("outline"))

        # No matching stations
        if match is None:
            self.speak_dialog("not.found")
            wait_while_speaking()
            LOG.debug("Could not find a station with the query term: " + search_term)
            return

        # stop the current stream if we have one running
        if self.audio_state == "playing":
            self.stop()
        # Ignore entries that are marked as unavailable
        self.mpeg_url = match.getAttribute("URL")
        self.station_name = match.getAttribute("text")
        # this URL will return audio/x-mpegurl data. This is just a list of URLs to the real streams
        self.stream_url = self.get_stream_url(self.mpeg_url)
        self.audio_state = "playing"
        self.speak_dialog("now.playing", {"station": self.station_name})
        wait_while_speaking()
        LOG.debug("Station: " + self.station_name)
        LOG.debug("Station name fuzzy match percent: " + str(perc))
        LOG.debug("Stream URL: " + self.stream_url)
        tracklist.append(self.stream_url)
        self.mediaplayer.add_list(tracklist)
        self.mediaplayer.play()

    def get_stream_url(self, mpegurl):
        res = requests.get(mpegurl)
        # Get the first line from the results
        for line in res.text.splitlines():
            return self.process_url(line)

    def stop(self):
        if self.audio_state == "playing":
            self.mediaplayer.stop()
            self.mediaplayer.clear_list()
            LOG.debug("Stopping stream")

        self.audio_state = "stopped"
        self.station_name = None
        self.stream_url = None
        self.mpeg_url = None
        return True

    def shutdown(self):
        if self.audio_state == 'playing':
            self.mediaplayer.stop()
            self.mediaplayer.clear_list()

    # Check what kind of url was pulled from the x-mpegurl data
    def process_url(self, url):
        if (len(url) > 4):
            if url[-3:] == 'm3u':
                return url[:-4]
            if url[-3:] == 'pls':
                return self.process_pls(url)
            else:
                return url
        return url

    # Pull down the pls data and pull out the real stream url out of it
    def process_pls(self, url):
        res = requests.get(url)
        # Loop through the data looking for the first url
        for line in res.text.splitlines():
            if line.startswith("File1="):
                return line[6:]

    # Get the correct localized regex
    def translate_regex(self, regex):
        if regex not in self.regexes:
            path = self.find_resource(regex + '.regex')
            if path:
                with open(path) as f:
                    string = f.read().strip()
                self.regexes[regex] = string
        return self.regexes[regex]
class TuneinSkill(CommonPlaySkill):
    def __init__(self):
        super().__init__(name="TuneinSkill")

        self.mediaplayer = VlcService(config={'low_volume': 10, 'duck': True})
        self.audio_state = "stopped"  # 'playing', 'stopped'
        self.station_name = None
        self.stream_url = None
        self.mpeg_url = None
        self.regexes = {}

    def CPS_match_query_phrase(self, phrase):
        # Look for regex matches starting from the most specific to the least
        # Play <data> internet radio on tune in
        match = re.search(self.translate_regex('internet_radio_on_tunein'),
                          phrase)
        if match:
            data = re.sub(self.translate_regex('internet_radio_on_tunein'), '',
                          phrase)
            LOG.debug("CPS Match (internet_radio_on_tunein): " + data)
            return phrase, CPSMatchLevel.EXACT, data

        # Play <data> radio on tune in
        match = re.search(self.translate_regex('radio_on_tunein'), phrase)
        if match:
            data = re.sub(self.translate_regex('radio_on_tunein'), '', phrase)
            LOG.debug("CPS Match (radio_on_tunein): " + data)
            return phrase, CPSMatchLevel.EXACT, data

        # Play <data> on tune in
        match = re.search(self.translate_regex('on_tunein'), phrase)
        if match:
            data = re.sub(self.translate_regex('on_tunein'), '', phrase)
            LOG.debug("CPS Match (on_tunein): " + data)
            return phrase, CPSMatchLevel.EXACT, data

        # Play <data> internet radio
        match = re.search(self.translate_regex('internet_radio'), phrase)
        if match:
            data = re.sub(self.translate_regex('internet_radio'), '', phrase)
            LOG.debug("CPS Match (internet_radio): " + data)
            return phrase, CPSMatchLevel.CATEGORY, data

        # Play <data> radio
        match = re.search(self.translate_regex('radio'), phrase)
        if match:
            data = re.sub(self.translate_regex('radio'), '', phrase)
            LOG.debug("CPS Match (radio): " + data)
            return phrase, CPSMatchLevel.CATEGORY, data

        return phrase, CPSMatchLevel.GENERIC, phrase

    def CPS_start(self, phrase, data):
        LOG.debug("CPS Start: " + data)
        self.find_station(data)

    @intent_file_handler('StreamRequest.intent')
    def handle_stream_intent(self, message):
        self.find_station(message.data["station"])
        LOG.debug("Station data: " + message.data["station"])

    def apply_aliases(self, search_term):
        # Allow search terms to be expanded or aliased
        home = expanduser('~')
        alias_file = home + '/tunein_aliases.yaml'
        if path.exists(alias_file):
            with open(alias_file, 'r') as file:
                alias_list = yaml.load(file)
            if search_term in alias_list:
                search_term = alias_list[search_term]
        return search_term

    # Attempt to find the first active station matching the query string
    def find_station(self, search_term):

        tracklist = []
        LOG.debug("pre-alias search_term: " + search_term)
        search_term = self.apply_aliases(search_term)
        LOG.debug("aliased search_term: " + search_term)

        payload = {"query": search_term}
        # get the response from the TuneIn API
        res = requests.post(base_url, data=payload, headers=headers)
        dom = parseString(res.text)
        # results are each in their own <outline> tag as defined by OPML (https://en.wikipedia.org/wiki/OPML)
        entries = dom.getElementsByTagName("outline")

        # Loop through outlines in the lists
        for entry in entries:
            # Only look at outlines that are of type=audio and item=station
            if (entry.getAttribute("type")
                    == "audio") and (entry.getAttribute("item") == "station"):
                if (entry.getAttribute("key") != "unavailable"):
                    if (entry.getAttribute("text") == "triple j Unearthed"):
                        continue
                    # stop the current stream if we have one running
                    if (self.audio_state == "playing"):
                        self.stop()
                    # Ignore entries that are marked as unavailable
                    self.mpeg_url = entry.getAttribute("URL")
                    self.station_name = entry.getAttribute("text")
                    # this URL will return audio/x-mpegurl data. This is just a list of URLs to the real streams
                    self.stream_url = self.get_stream_url(self.mpeg_url)
                    self.audio_state = "playing"
                    self.speak_dialog("now.playing",
                                      {"station": self.station_name})
                    wait_while_speaking()
                    LOG.debug("Found stream URL: " + self.stream_url)
                    tracklist.append(self.stream_url)
                    self.mediaplayer.add_list(tracklist)
                    self.mediaplayer.play()
                    return

        # We didn't find any playable stations
        self.speak_dialog("not.found")
        wait_while_speaking()
        LOG.debug("Could not find a station with the query term: " +
                  search_term)

    def get_stream_url(self, mpegurl):
        res = requests.get(mpegurl)
        # Get the first line from the results
        for line in res.text.splitlines():
            return self.process_url(line)

    def stop(self):
        if self.audio_state == "playing":
            self.mediaplayer.stop()
            self.mediaplayer.clear_list()
            LOG.debug("Stopping stream")

        self.audio_state = "stopped"
        self.station_name = None
        self.stream_url = None
        self.mpeg_url = None
        return True

    def shutdown(self):
        if self.audio_state == 'playing':
            self.mediaplayer.stop()
            self.mediaplayer.clear_list()

    # Check what kind of url was pulled from the x-mpegurl data
    def process_url(self, url):
        if (len(url) > 4):
            if url[-3:] == 'm3u':
                return url[:-4]
            if url[-3:] == 'pls':
                return self.process_pls(url)
            else:
                return url
        return url

    # Pull down the pls data and pull out the real stream url out of it
    def process_pls(self, url):
        res = requests.get(url)
        # Loop through the data looking for the first url
        for line in res.text.splitlines():
            if line.startswith("File1="):
                return line[6:]

    # Get the correct localized regex
    def translate_regex(self, regex):
        if regex not in self.regexes:
            path = self.find_resource(regex + '.regex')
            if path:
                with open(path) as f:
                    string = f.read().strip()
                self.regexes[regex] = string
        return self.regexes[regex]
Exemple #5
0
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]
Exemple #6
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
Exemple #7
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()
Exemple #8
0
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]