Exemple #1
0
 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"
Exemple #2
0
    def __init__(self):
        super(USBMusicSkill, self).__init__('USBMusicSkill')
        self.mediaplayer = VlcService(config={'low_volume': 10, 'duck': True})
        self.song_list = []
        self.prev_status = False
        self.song_artist = ""
        self.song_label = ""
        self.song_album = ""
        self.auto_play = False
        self.insert_command = ""
        self.command_enable = False
        self.prev_status = False
        self.status = False
        self.library_ready = False
        self.path = ""
        self.local_path = ""
        self.smb_path = ""
        self.smb_uname = ""
        self.smb_pass = ""

        self.usb_monitor = NewThread
        self.usbdevice = usbdev
        self.observer = self.usbdevice.startListener()
        #self.audio_service = None
        self.audio_state = 'stopped'  # 'playing', 'stopped'
        LOG.info("USB Music Skill Loaded!")
    def __init__(self):
        super().__init__(name="TuneinSkill")

        self.mediaplayer = VlcService(config={'low_volume': 10, 'duck': True})
        self.audio_state = "stopped"  # 'playing', 'stopped'
        self.station_name = None
        self.stream_url = None
        self.mpeg_url = None
        self.regexes = {}
Exemple #4
0
 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'
Exemple #5
0
 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
Exemple #6
0
 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)
Exemple #7
0
 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
Exemple #8
0
    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'
Exemple #9
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 #10
0
 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)
Exemple #11
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]
Exemple #12
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 #13
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 #14
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]
Exemple #15
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
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 #17
0
class USBMusicSkill(CommonPlaySkill):
    def __init__(self):
        super(USBMusicSkill, self).__init__('USBMusicSkill')
        self.mediaplayer = VlcService(config={'low_volume': 10, 'duck': True})
        self.song_list = []
        self.prev_status = False
        self.song_artist = ""
        self.song_label = ""
        self.song_album = ""
        self.auto_play = False
        self.insert_command = ""
        self.command_enable = False
        self.prev_status = False
        self.status = False
        self.library_ready = False
        self.path = ""
        self.local_path = ""
        self.smb_path = ""
        self.smb_uname = ""
        self.smb_pass = ""

        self.usb_monitor = NewThread
        self.usbdevice = usbdev
        self.observer = self.usbdevice.startListener()
        #self.audio_service = None
        self.audio_state = 'stopped'  # 'playing', 'stopped'
        LOG.info("USB Music Skill Loaded!")

    def initialize(self):
        self.load_data_files(dirname(__file__))
        #self.audio_service = AudioService(self.bus)
        LOG.info("USB Music Skill Initialized!")
        self.halt_usb_monitor_thread()
        self.init_usb_monitor_thread()
        self.settings_change_callback = self.on_websettings_changed
        self.on_websettings_changed()

    def on_websettings_changed(self):  # called when updating mycroft home page
        self.auto_play = self.settings.get(
            "auto_play", False)  # used to enable / disable auto_play
        self.local_path = self.settings.get("local_path", "/home/pi/Music")
        self.smb_path = self.settings.get("smb_path",
                                          "//192.168.0.20/SMBMusic")
        self.smb_uname = self.settings.get("smb_uname", "guest")
        self.smb_pass = self.settings.get("smb_pass", "")
        self.insert_command = self.settings.get("insert_command", "")
        if len(self.insert_command) > 0:
            self.command_enable = self.settings.get("command_enable", False)
        else:
            LOG.info('No Command Specified, Enable Set to: False')
            self.command_enable = False
        LOG.info('USB-Music Settings Changed, Command Enable now: ' +
                 str(self.command_enable))
        LOG.info('USB-Music Settings Changed, AutoPlay now: ' +
                 str(self.auto_play))
        LOG.info('USB-Music Settings Changed, SMB Path now: ' +
                 str(self.smb_path))
        LOG.info('USB-Music Settings Changed, Local Path now: ' +
                 str(self.local_path))

    def send_message(
            self,
            message):  # Sends the remote received commands to the messagebus
        LOG.info("Sending a command to the message bus: " + message)
        payload = json.dumps({
            "type": "recognizer_loop:utterance",
            "context": "",
            "data": {
                "utterances": [message]
            }
        })
        uri = 'ws://localhost:8181/core'
        ws = create_connection(uri)
        ws.send(payload)
        ws.close()

    def init_usb_monitor_thread(self):  # creates the workout thread
        self.usb_monitor.idStop = False
        self.usb_monitor.id = 101
        self.usb_monitor.idThread = threading.Thread(
            target=self.start_usb_thread,
            args=(self.usb_monitor.id, lambda: self.usb_monitor.idStop))
        self.usb_monitor.idThread.start()

    def halt_usb_monitor_thread(self):  # requests an end to the workout
        try:
            self.usb_monitor.id = 101
            self.usb_monitor.idStop = True
            self.usb_monitor.idThread.join()
        except Exception as e:
            LOG.error(
                e)  # if there is an error attempting the workout then here....

    def numeric_replace(self, in_words=""):
        word_list = in_words.split()
        return_list = []
        for each_word in word_list:
            try:
                new_word = w2n.word_to_num(each_word)
            except Exception as e:
                # LOG.info(e)
                new_word = each_word
            return_list.append(new_word)
            return_string = ' '.join(str(e) for e in return_list)
        return return_string

    def parse_music_utterance(self, phrase):
        # Todo: move Regex to file for language support
        # Todo: This needs to be refactored as it is not a sustainable way to search music
        # returns what was spoken in the utterance
        return_item = "none"
        return_type = "any"
        str_request = str(phrase)
        LOG.info("Parse Music Received: " + str_request)
        primary_regex = r"((?<=album) (?P<album>.*$))|((?<=by) (?P<artist1>.*$))|((?<=artist) (?P<artist>.*$))|((?<=song) (?P<label>.*$))"
        secondary_regex = None
        if str_request.find('some') != -1:
            secondary_regex = r"((?<=some) (?P<any>.*$))"
        elif str_request.find('my') != -1:
            secondary_regex = r"((?<=my) (?P<any>.*$))"
        elif str_request.find('all') != -1:
            secondary_regex = r"((?<=all) (?P<any>.*$))"
        elif str_request.find('any') != -1:
            secondary_regex = r"((?<=any) (?P<any>.*$))"
        else:
            secondary_regex = r"((?<=play) (?P<any>.*$))"
        key_found = re.search(primary_regex, str_request)
        if key_found:
            LOG.info("Primary Regex Key Found")
            if key_found.group("label"):
                LOG.info("found label")
                return_item = key_found.group("label")
                return_type = "label"
            elif key_found.group("artist"):
                LOG.info("found artist")
                return_item = key_found.group("artist")
                return_type = "artist"
            elif key_found.group("artist1"):  # secondary artist
                LOG.info("found artist")
                return_item = key_found.group("artist1")
                return_type = "artist"
            elif key_found.group("album"):
                LOG.info("found album")
                return_item = key_found.group("album")
                return_type = "album"
        else:
            LOG.info("Primary Regex Key Not Found")
            if secondary_regex:
                key_found = re.search(secondary_regex, str_request)
            if key_found.group("any"):
                LOG.info("Secondary Regex Key Found")
                return_item = key_found.group("any")
                return_type = "any"
            else:
                LOG.info("Secondary Regex Key Not Found")
                return_item = "none"
                return_type = "none"
        # Returns the item that was requested and the type of the requested item ie. artist, album, label
        return return_item, return_type

    def search_music_library(self, search_string, category="any"):
        found_list = [
        ]  # this is a dict that will contain all the items found in the library
        LOG.info("searching the music library for: " + str(search_string) +
                 ", " + str(category))
        if category == "any":
            found_list = self.search_music_item(search_string,
                                                category="label")
            if len(found_list) > 0:
                return found_list
            LOG.info("Label: " + search_string + ", Not Found!")
            found_list = self.search_music_item(search_string,
                                                category="artist")
            if len(found_list) > 0:
                return found_list
            LOG.info("Artist: " + search_string + ", Not Found!")
            found_list = self.search_music_item(search_string,
                                                category="album")
            if len(found_list) > 0:
                return found_list
            found_list = self.search_music_item(search_string,
                                                category="location")
            if len(found_list) > 0:
                return found_list
            if len(found_list) == 0:
                LOG.info("Album: " + search_string + ", Not Found!")
                return
        else:
            found_list = self.search_music_item(search_string,
                                                category=str(category))
        if len(found_list) > 0:
            return found_list

    def search_music_item(self, search_item, category="label"):
        # category options: label, artist, album
        found_list = [
        ]  # this is a dict of all the items found that match the search
        search_item = self.numeric_replace(search_item)
        search_words = search_item.replace("-", "").lower().split()
        # check each song in the list for strings that match all the words in the search
        for each_song in self.song_list:  # check each song in the list for the one we are looking for
            item_name = each_song[category].replace("-", "")
            if len(item_name) > 0:
                item_name = self.numeric_replace(item_name)
                if all(words in item_name.lower() for words in search_words):
                    found_length = len(each_song['label'].split())
                    info = {
                        "location": each_song['location'],
                        "label": each_song['label'],
                        "album": each_song['album'],
                        "artist": each_song['artist'],
                        "source": each_song['source']
                    }
                    found_list.append(info)
        LOG.info('Found the following songs: ' + str(found_list))
        # remove duplicates
        temp_list = []  # this is a dict
        for each_song in found_list:
            info = {
                "location": each_song['location'],
                "label": each_song['label'],
                "album": each_song['album'],
                "artist": each_song['artist'],
                "source": each_song['source']
            }  # Todo this is missing in the kodi skill????
            song_title = str(each_song['label'])
            if song_title not in str(temp_list):
                temp_list.append(info)
            else:
                if len(each_song['label']) == len(song_title):
                    LOG.info('found duplicate')
                else:
                    temp_list.append(info)
        found_list = temp_list
        return found_list  # returns a dictionary of matched movies

    def merge_library(self, dict1, dict2):
        return dict1 + dict2

    def start_usb_thread(self, my_id, terminate):
        """
        This thread monitors the USB port for an insertion / removal event
        """
        # Todo automatically play when stick is inserted
        LOG.info("USB Monitoring Loop Started!")
        while not terminate():  # wait while this interval completes
            time.sleep(
                1
            )  # Todo make the polling time a variable or make it a separate thread
            # get the status of the connected usb device
            self.status = self.usbdevice.isDeviceConnected()
            if self.status != self.prev_status:
                LOG.info("USB Status Changed!")
                self.prev_status = self.status
                if self.status:  # Device inserted
                    # remove any existing mount points
                    self.usbdevice.uMountPathUsbDevice()
                    LOG.info("Device Inserted!")
                    device = self.usbdevice.getDevData()
                    # mount the device and get the path
                    self.path = self.usbdevice.getMountPathUsbDevice()
                    LOG.info("Stat: " + str(self.status))
                    LOG.info("dev: " + str(device))
                    LOG.info("path: " + str(self.path))
                    LOG.info("---------------------------------")
                    self.speak_dialog('update.library',
                                      data={"source": str("usb")},
                                      expect_response=False)
                    wait_while_speaking()
                    self.song_list = [
                        i for i in self.song_list if not (i['source'] == 'usb')
                    ]
                    self.song_list = self.merge_library(
                        self.song_list, self.create_library(self.path, "usb"))
                    if self.command_enable:
                        self.send_message(self.insert_command)
                    if self.auto_play:
                        self.play_all(self.song_list)
                else:
                    #self.audio_service.stop()
                    self.mediaplayer.stop()
                    # unmount the path
                    self.usbdevice.uMountPathUsbDevice()
                    LOG.info("Device Removed!")
                    # Todo remove context "USB" so all play requests start with this skill
                    self.speak_dialog('usb.removed', expect_response=False)
                    wait_while_speaking()
                    self.song_list = []
                    self.path = ""
                    self.on_websettings_changed()
        self.usbdevice.stopListener(self.observer)

    def play_all(self, library):
        LOG.info('Automatically playing the USB Device')
        tracklist = []
        for each_song in library:
            LOG.info("CPS Now Playing... " + each_song['label'] +
                     " from location: " + each_song['location'])
            url = each_song['location']
            tracklist.append(url)
        random.shuffle(tracklist)
        self.speak_dialog('now.playing')
        wait_while_speaking()
        self.mediaplayer.add_list(tracklist)
        self.mediaplayer.play()
        #self.audio_service.play(tracklist)

        self.audio_state = 'playing'

    def create_library(self, source_path, source_type="usb"):
        # Todo - add regex to remove numbers from the begining of filenames to get song name (\d{1,3}|)( - |)(?P<song_Name>.+)
        self.library_ready = False
        new_library = []
        for root, d_names, f_names in os.walk(str(source_path)):
            for fileName in f_names:
                audio = None
                foundType = [
                    musicType for musicType in MUSIC_TYPES
                    if (musicType.lower() in fileName.lower())
                ]
                if bool(foundType):
                    song_path = str(root) + "/" + str(fileName)
                    if True:
                        # try:  # Removed to find error
                        if "flac" in str(foundType[0]):  # add flac filter
                            audio = FLAC(song_path)
                            # LOG.info("Checking FLAC Tags" + str(audio))
                        elif "aac" in str(foundType[0]):  # add flac filter:
                            audio = AAC(song_path)
                            # LOG.info("Checking aac Tags" + str(audio))
                        elif "mp3" in str(foundType[0]):  # add flac filter:
                            try:
                                audio = EasyID3(song_path)
                            except ID3NoHeaderError:
                                LOG.info("No ID Tags Found... " + song_path)
                                audio = {}
                            # LOG.info("Checking mp3 Tags" + str(audio))
                        elif "m4a" in str(foundType[0]):  # add flac filter:
                            audio = MP4(song_path)
                            # LOG.info("Checking m4a Tags" + str(audio))
                        if audio is not None:  # An ID3 tag found
                            if 'title' not in audio.keys():
                                trim_length = (len(str(foundType[0])) + 1) * -1
                                self.song_label = str(fileName)[:trim_length]
                            else:
                                self.song_label = audio['title'][0]
                                #LOG.info("Validating title: " + self.song_label)
                            if 'artist' not in audio.keys():
                                if 'Contributing artists' in audio.keys():
                                    self.song_artist = audio[
                                        'Contributing artists'][0]
                                else:
                                    self.song_artist = ""
                            else:
                                self.song_artist = audio['artist'][0]
                                #LOG.info("Validating artist: " + self.song_artist)
                            if 'album' not in audio.keys():
                                self.song_album = ""
                            else:
                                self.song_album = audio['album'][0]
                        else:  # There was no ID3 Tag found, use filename as song title
                            trim_length = (len(str(foundType[0])) + 1) * -1
                            self.song_label = str(fileName)[:trim_length]
                            self.song_artist = ""
                            self.song_album = ""
                info = {
                    "location": song_path,
                    "label": self.song_label,
                    "artist": self.song_artist,
                    "album": self.song_album,
                    "source": str(source_type)
                }
                new_library.append(info)
        song_count = len(new_library)
        if song_count == 0:
            self.speak_dialog('no.files',
                              data={"source": str(source_type)},
                              expect_response=False)
        else:
            self.speak_dialog('scan.complete',
                              data={
                                  "count": str(song_count),
                                  "source": str(source_type)
                              },
                              expect_response=False)
        wait_while_speaking()
        LOG.info("Added: " + str(song_count) + " to the library from the " +
                 str(source_type) + " Device")
        self.library_ready = True
        return new_library

    def CPS_match_query_phrase(self, phrase):
        """
            The method is invoked by the PlayBackControlSkill.
        """
        LOG.info('USBMusicSkill received the following phrase: ' + phrase)
        if self.status or self.library_ready:  # Confirm the USB is inserted
            LOG.info("USBMusicSkill is Searching for requested media...")
            play_request = self.parse_music_utterance(
                phrase)  # get the requested Music Item
            LOG.info("USBMusicSkill Parse Routine Returned: " +
                     str(play_request))
            music_playlist = self.search_music_library(
                play_request[0],
                category=play_request[1])  # search for the item in the library
            if music_playlist is None:
                return None  # until a match is found
            else:
                if len(music_playlist) > 0:
                    match_level = CPSMatchLevel.EXACT
                    data = music_playlist
                    LOG.info('Music found that matched the request!')
                    return phrase, match_level, data
                else:
                    return None  # until a match is found
        else:
            LOG.info("Device or Library Not Ready, Passing on this request")
            return None

    def CPS_start(self, phrase, data):
        """ Starts playback.
            Called by the playback control skill to start playback if the
            skill is selected (has the best match level)
        """
        tracklist = []
        LOG.info(
            'USBMusicSkill, Playback received the following phrase and Data: '
            + phrase + ' ' + str(data))
        for each_song in data:
            LOG.info("CPS Now Playing... " + each_song['label'] +
                     " from location: " + each_song['location'])
            url = each_song['location']
            tracklist.append(url)
        #LOG.info(str(tracklist))
        self.speak_dialog('now.playing')
        wait_while_speaking()
        #self.audio_service.play(tracklist)
        #random.shuffle(tracklist)
        self.mediaplayer.add_list(tracklist)
        self.mediaplayer.play()
        self.audio_state = 'playing'
        pass

    @intent_handler(
        IntentBuilder('').require("UpdateKeyword").require(
            "USBKeyword").require("LibraryKeyword"))
    def handle_update_usb_library_intent(self, message):
        LOG.info("Called Update Library Intent")
        if self.usbdevice.isDeviceConnected():
            device = self.usbdevice.getDevData()
            # mount the device and get the path
            self.path = self.usbdevice.getMountPathUsbDevice()
            self.speak_dialog(
                'update.library',
                data={"source": str(message.data.get("USBKeyword"))},
                expect_response=False)
            wait_while_speaking()
            self.song_list = [
                i for i in self.song_list if not (i['source'] == 'usb')
            ]
            self.song_list = self.merge_library(
                self.song_list, self.create_library(self.path, "usb"))
        else:
            self.usbdevice.uMountPathUsbDevice()
            # Play Music Added here
            self.speak_dialog('usb.not.mounted', expect_response=False)
            wait_while_speaking()
            LOG.info("USB Device Not Detected")

    # Todo: Add an unmount / release command

    @intent_handler(
        IntentBuilder('').require("RemoveKeyword").require("USBKeyword"))
    def handle_remove_usb_intent(self, message):
        self.usbdevice.uMountPathUsbDevice()
        LOG.info("Device Removed!")

    @intent_handler(
        IntentBuilder('').require("UpdateKeyword").require(
            "NetworkKeyword").require("LibraryKeyword"))
    def handle_get_smb_music_intent(self, message):
        self.path = self.usbdevice.MountSMBPath(self.smb_path, self.smb_uname,
                                                self.smb_pass)
        self.speak_dialog(
            'update.library',
            data={"source": str(message.data.get("NetworkKeyword"))},
            expect_response=False)
        wait_while_speaking()
        self.song_list = [
            i for i in self.song_list if not (i['source'] == 'smb')
        ]
        self.song_list = self.merge_library(
            self.song_list, self.create_library(self.path, "smb"))
        LOG.info("SMB Mounted!")

    @intent_handler(
        IntentBuilder('').require("UpdateKeyword").require(
            "LocalKeyword").require("LibraryKeyword"))
    def handle_get_local_music_intent(self, message):
        self.path = self.local_path
        self.speak_dialog(
            'update.library',
            data={"source": str(message.data.get("LocalKeyword"))},
            expect_response=False)
        wait_while_speaking()
        self.song_list = [
            i for i in self.song_list if not (i['source'] == 'local')
        ]
        self.song_list = self.merge_library(
            self.song_list, self.create_library(self.path, "local"))
        LOG.info("Local Mounted!")

    @intent_handler(
        IntentBuilder('').require("UpdateKeyword").require(
            "MusicKeyword").require("LibraryKeyword"))
    def handle_get_All_available_intent(self, message):
        self.path = self.usbdevice.MountSMBPath(self.smb_path, self.smb_uname,
                                                self.smb_pass)
        self.speak_dialog(
            'update.library',
            data={"source": str(message.data.get("MusicKeyword"))},
            expect_response=False)
        wait_while_speaking()
        self.song_list = [
            i for i in self.song_list if not (i['source'] == 'smb')
        ]
        self.song_list = self.merge_library(
            self.song_list, self.create_library(self.path, "smb"))
        LOG.info("SMB Mounted!")
        self.path = self.local_path
        self.song_list = [
            i for i in self.song_list if not (i['source'] == 'local')
        ]
        self.song_list = self.merge_library(
            self.song_list, self.create_library(self.path, "local"))
        LOG.info("Local Mounted!")
        if self.usbdevice.isDeviceConnected():
            device = self.usbdevice.getDevData()
            # mount the device and get the path
            self.path = self.usbdevice.getMountPathUsbDevice()
            self.speak_dialog(
                'update.library',
                data={"source": str(message.data.get("USBKeyword"))},
                expect_response=False)
            wait_while_speaking()
            self.song_list = [
                i for i in self.song_list if not (i['source'] == 'usb')
            ]
            self.song_list = self.merge_library(
                self.song_list, self.create_library(self.path, "usb"))

    @intent_handler(
        IntentBuilder('').require("StartKeyword").require(
            "USBKeyword").require('ScanKeyword'))
    def handle_start_usb_intent(self, message):
        # Todo: add ability to stop the thread when the skill re-loads
        LOG.info('Thread Running: ' + str(self.usb_monitor.idThread.isAlive()))
        if self.usb_monitor.idThread.isAlive():
            LOG.info("Scan is already running!")
        else:
            LOG.info("Scan should start!")
            self.init_usb_monitor_thread()

    @intent_handler(
        IntentBuilder('').require("ShowKeyword").require(
            "MusicKeyword").require('LibraryKeyword'))
    def handle_show_music_library_intent(self, message):
        LOG.info(str(self.song_list))
        LOG.info('Library Size: ' + str(len(self.song_list)))

    def stop(self):
        if self.audio_state == 'playing':
            #self.audio_service.stop()
            self.mediaplayer.stop()

            LOG.debug('Stopping stream')
        self.audio_state = 'stopped'
        return True

        #LOG.info('Stopping USB Monitor Thread!')
        #self.halt_usb_monitor_thread()
        pass
Exemple #18
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()
Exemple #19
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]