Esempio n. 1
0
class TestAudioServiceQueue(TestCase):
    def setUp(self):
        self.bus = mock.Mock(name='bus')
        self.audioservice = AudioService(self.bus)

    def test_uri(self):
        self.audioservice.queue('file:///hello_nasty.mp3')
        message = self.bus.emit.call_args_list[-1][0][0]
        self.assertEqual(message.msg_type, 'mycroft.audio.service.queue')
        self.assertEqual(message.data['tracks'], ['file:///hello_nasty.mp3'])

    def test_path(self):
        self.audioservice.queue('/hello_nasty.mp3')
        message = self.bus.emit.call_args_list[-1][0][0]
        self.assertEqual(message.msg_type, 'mycroft.audio.service.queue')
        self.assertEqual(message.data['tracks'], ['file:///hello_nasty.mp3'])

    def test_tuple(self):
        self.audioservice.queue(('/hello_nasty.mp3', 'audio/mp3'))
        message = self.bus.emit.call_args_list[-1][0][0]
        self.assertEqual(message.msg_type, 'mycroft.audio.service.queue')
        self.assertEqual(message.data['tracks'],
                         [('file:///hello_nasty.mp3', 'audio/mp3')])

    def test_invalid(self):
        with self.assertRaises(ValueError):
            self.audioservice.queue(12)
Esempio n. 2
0
class Subsonic(MycroftSkill):
    def __init__(self):
        MycroftSkill.__init__(self)
        self.results = None
        self.audio_service = None
        self.song_ids = dict()
        self.play_list_count = 0

    def initialize(self):
        self.audio_service = AudioService(self.emitter)
        self.add_event('mycroft.audio.playing_track',
                       self.handle_playing_track)
        self.add_event('mycroft.audio.service.next', self.handle_next_track)
        self.add_event('mycroft.audio.service.prev', self.handle_prev_track)

    def hash_password(self):
        """
        return random salted md5 hash of password
        """
        characters = string.ascii_uppercase + string.ascii_lowercase + string.digits
        salt = ''.join(SystemRandom().choice(characters) for i in range(9))
        salted_password = self.settings.get('password') + salt
        token = hashlib.md5(salted_password.encode('utf-8')).hexdigest()
        return token, salt

    def create_url(self, endpoint):
        """
        build the standard url for interfacing with the Subsonic REST API
        :param endpoint: REST endpoint to incorporate in the url
        """
        token, salt = self.hash_password()
        url = '{}/rest/{}?u={}&t={}&s={}&v=1.16.0&c=mycroft&f=json'.format(
            self.settings.get('server_url'), endpoint,
            self.settings.get('username'), token, salt)
        return url

    def make_request(self, url):
        """
        GET the supplied url and resturn the response as json.
        Handle any errors present.
        :param url: full url. see create_url method for details
        :return: Subsonic response or None on failure
        """
        r = requests.get(url=url)

        try:
            response = r.json()
        except ValueError:
            response = {
                'subsonic-response': {
                    'error': {
                        'code': 100,
                        'message': r.text
                    },
                    'status': 'failed'
                }
            }

        subsonic_response = response.get('subsonic-response', {})
        status = subsonic_response.get('status', 'failed')

        if status == 'failed':
            error = subsonic_response.get('error', {})
            self.speak_dialog('subsonic.error', {
                'code': error.get('code'),
                'message': error.get('message')
            })
            return None

        return response

    def handle_playing_track(self, message):
        """
        notify the Subsonic server that a track is being played
        triggered by the mycroft.audio.playing_track event
        """
        # get the track name from the message
        track_name = message.data.get('track')
        # get the corresponding ID from our map
        song_id = self.song_ids.get(track_name)

        if song_id:
            # scrobble the track
            self.make_request(
                url='{}&id={}'.format(self.create_url('scrobble'), song_id))

    def handle_next_track(self, message):
        """
        reduce the playlist count by 1
        triggered by the mycroft.audio.service.next event
        """
        self.play_list_count -= 1

    def handle_prev_track(self, message):
        """
        increase the playlist count by 1
        triggered by the mycroft.audio.service.prev event
        """
        self.play_list_count += 1

    def search(self, query):
        """
        search using query and return the result
        """
        results = self.make_request(
            url='{}&query={}'.format(self.create_url('search3'), query))
        if results:
            return results['subsonic-response']['searchResult3']
        return {}

    def get_playlists(self):
        """
        Get a list of available playlists from the server
        """
        playlists = self.make_request(url=self.create_url('getPlaylists'))
        if playlists:
            return playlists['subsonic-response']['playlists']['playlist']
        return []

    def get_random_songs(self):
        """
        Gather random tracks from the Subsonic server
        """
        url = self.create_url('getRandomSongs')
        random_songs = self.make_request(url)

        if not random_songs:
            return []

        return random_songs['subsonic-response']['randomSongs']['song']

    def get_album_tracks(self, album_id):
        """
        return a list of album track ids for the given album id
        """
        album_info = self.make_request('{}&id={}'.format(
            self.create_url('getAlbum'), album_id))
        songs = []

        for song in album_info['subsonic-response']['album']['song']:
            songs.append(song)

        return songs

    def play_songs(self, songs):
        """
        play a new set of tracks.
        """
        # we use the song_ids to scrobble tracks as they are played
        # this method resets the playlist so we reset the list of sing ids too
        self.song_ids = {}

        for song in songs:
            self.song_ids[song['title']] = song['id']

        shuffle(songs)
        playlist = [
            '{}&id={}'.format(self.create_url('download'), song['id'])
            for song in songs
        ]
        # this method resets the playlist so playlist length always starts off as long as this one
        self.play_list_count = len(playlist)
        self.audio_service.play(playlist, 'vlc')

    def queue_songs(self, songs):
        """
        add the songs to the currently playing queue
        (basically the same as play_songs above but we add to the song id map and play_list_count)
        """
        for song in songs:
            self.song_ids[song['title']] = song['id']

        shuffle(songs)
        playlist = [
            '{}&id={}'.format(self.create_url('download'), song['id'])
            for song in songs
        ]
        # this method adds to the playlist so playlist length gets bigger as songs are added
        self.play_list_count += len(playlist)
        self.audio_service.queue(playlist)

    @intent_handler(
        IntentBuilder('PlayArtistIntent').require('Play').optionally(
            'Music').optionally('ArtistKeyWord').require('Artist').optionally(
                '_TestRunner').build())
    def handle_play_artist_intent(self, message):
        """
        Handle playing tracks by a chosen artist
        Phrases like:
            Play some Aphex Twin
            Play some tracks by Tune-yards
            Play some noise by Atari Teenage Riot
        """
        if message.data.get('_TestRunner'):
            self.speak('You have reached the Artist Intent')
            return

        artist = message.data.get('Artist')
        available_artists = self.search(artist).get('artist', [])

        if not available_artists:
            self.speak_dialog('no.artists', {'artist': artist})
            return

        # we want to match the best search result
        # make a dict holding the details we need
        matching_artists = dict()

        for available_artist in available_artists:
            matching_artists[available_artist['name']] = available_artist['id']

        matched_artist_id = match_one(artist, matching_artists)[0]

        self.speak_dialog(
            'artist', {
                'artist':
                next(a for a in matching_artists
                     if matching_artists[a] == matched_artist_id)
            })

        songs = []
        artist_info = self.make_request('{}&id={}'.format(
            self.create_url('getArtist'), matched_artist_id))

        for album in artist_info['subsonic-response']['artist']['album']:
            songs += self.get_album_tracks(album.get('id'))

        self.play_songs(songs)

    @intent_handler(
        IntentBuilder('PlayMusicIntent').require('Play').optionally(
            'AlbumKeyWord').require('MusicTarget').require(
                'ArtistKeyword').require('Artist').build())
    def handle_play_music_intent(self, message):
        """
        Handle playing of an Album or a Single Track, with an Artist specified
        Phrases like:
            Play the album Syro by Aphex Twin
            Play Tundra by Squarepusher (single track)
        """
        # album keyword lets us determine that the user has definitely asked for an album
        album_keyword = message.data.get('AlbumKeyword')
        # target can be a single track or an album
        target = message.data.get('MusicTarget')
        artist = message.data.get('Artist')

        search_results = self.search(target)

        if album_keyword:
            # user gave tan Album keyword so we can skip all other search results
            available_targets = search_results.get('album', [])
        else:
            available_targets = search_results.get(
                'song', []) + search_results.get('album', [])

        # make sure the targets returned from the search have the correct artist
        matching_targets = []

        for found_target in available_targets:
            if 'artist' not in found_target:
                # videos may show up in search results but will have no artist
                continue
            # use fuzzy matching on the artist name and only accept those that match the
            if fuzzy_match(
                    found_target.get('artist').lower().strip(),
                    artist.lower().strip()) > 0.8:
                matching_targets.append(found_target)

        # if no targets have a nearly matching artist we report the failure to the user
        if not matching_targets:
            self.speak_dialog('no.targets', {
                'target': target,
                'artist': artist
            })
            return

        # otherwise we 'match_one' target based on the target name.
        # build a dict matching target name to target id (needed for playing0
        final_targets = dict()

        for matching_target in matching_targets:
            if 'name' in matching_target:
                # name only exists in an album
                final_targets[matching_target['name']] = {
                    'id': matching_target['id'],
                    'type': 'album',
                    'artist': matching_target['artist']
                }
            else:
                # we only chose albums and songs from the search results so can safely assume we have a song
                # songs are identified by 'title'
                final_targets[matching_target['title']] = {
                    'id': matching_target['id'],
                    'type': 'song',
                    'artist': matching_target['artist']
                }

        chosen_target = dict(match_one(target, final_targets)[0])
        chose_target_name = next(
            t for t in final_targets
            if final_targets[t]['id'] == chosen_target['id'])

        self.speak_dialog('target', {
            'target': chose_target_name,
            'artist': chosen_target['artist']
        })

        if chosen_target['type'] == 'song':
            songs = [
                next(t for t in matching_targets
                     if t['id'] == chosen_target['id'])
            ]
        else:
            songs = self.get_album_tracks(chosen_target['id'])

        self.play_songs(songs)

    @intent_handler(
        IntentBuilder('RandomIntent').require('Play').optionally(
            'Music').require('Random').optionally('Music').build())
    def handle_random_intent(self, message):
        """
        play random tunes until told to stop
        """
        has_played = message.data.get('has_played')

        if not has_played:
            self.speak_dialog('random')
            message.data['has_played'] = True
            self.play_songs(self.get_random_songs())

        else:
            # we have been here before so just add random tracks to the queue
            self.queue_songs(self.get_random_songs())

        while self.play_list_count > 1:
            time.sleep(5)
            continue

        self.handle_random_intent(message)

    @intent_handler(
        IntentBuilder('RadioIntent').require('Play').optionally(
            'Music').require('Radio').require('Artist').build())
    def handle_radio_intent(self, message):
        has_played = message.data.get('has_played')

        artist = message.data.get('Artist')
        available_artists = self.search(artist).get('artist', [])

        if not available_artists:
            self.speak_dialog('no.artists', {'artist': artist})
            return

        # we want to match the best search result
        # make a dict holding the details we need
        matching_artists = dict()

        for available_artist in available_artists:
            matching_artists[available_artist['name']] = available_artist['id']

        matched_artist_id = match_one(artist, matching_artists)[0]

        similar_songs = self.make_request('{}&id={}'.format(
            self.create_url('getSimilarSongs2'), matched_artist_id))

        if not has_played:
            self.speak_dialog(
                'radio', {
                    'artist':
                    next(a for a in matching_artists
                         if matching_artists[a] == matched_artist_id)
                })
            message.data['has_played'] = True
            self.play_songs(
                similar_songs['subsonic-response']['similarSongs2']['song'])
        else:
            self.queue_songs(
                similar_songs['subsonic-response']['similarSongs2']['song'])

        while self.play_list_count > 1:
            time.sleep(5)
            continue

        self.handle_radio_intent(message)

    @intent_handler(
        IntentBuilder('PlaylistIntent').require('Play').require(
            'Playlist').require('PlaylistKeyWord').build())
    def handle_playlist_intent(self, message):
        playlist = message.data.get('Playlist')
        available_playlists = self.get_playlists()
        matchable_playlists = dict()

        for available_playlist in available_playlists:
            matchable_playlists[
                available_playlist['name']] = available_playlist['id']

        chosen_playlist_id = match_one(playlist, matchable_playlists)[0]

        self.speak_dialog(
            'playlist', {
                'playlist':
                next(p['name'] for p in available_playlists
                     if p['id'] == chosen_playlist_id)
            })

        playlist_info = self.make_request(url='{}&id={}'.format(
            self.create_url('getPlaylist'), chosen_playlist_id))
        songs = playlist_info['subsonic-response']['playlist']['entry']

        self.play_songs(songs)
Esempio n. 3
0
class Mediaplayer(CommonPlaySkill):
    def __init__(self):
        #MycroftSkill.__init__(self)
        super().__init__(name="Mediaplayer")

    def initialize(self):
        super().initialize()
        self.playlists = []
        self.vlc_all_tracks = []
        self.vlc_all_tracks_info = []
        self.audio_service = AudioService(self.bus)
        self.vlc_audio_path = Path(str(self.settings.get('vlc_audio_path')))
        self.current_track = []
        self.track_change_request_in_progress = False
        #self.add_event("")
        self.register_all_intents()

    def register_all_intents(self):
        #self.register_intent_file('mediaplayer.next.intent', self.handle_mediaplayer_next)
        #self.register_intent_file('mediaplayer.prev.intent', self.handle_mediaplayer_prev)
        #self.register_intent_file('mediaplayer.stop.intent', self.handle_mediaplayer_stop)
        #self.register_intent_file('mediaplayer.pause.intent', self.handle_mediaplayer_pause)
        #self.register_intent_file('mediaplayer.resume.intent', self.handle_mediaplayer_resume)
        #self.register_intent_file('mediaplayer.info.intent', self.handle_mediaplayer_info)
        pass

    def enable_play_control_intents(self):
        self.enable_intent("mediaplayer.next.intent")
        self.enable_intent("mediaplayer.prev.intent")
        self.enable_intent("mediaplayer.stop.intent")
        self.enable_intent("mediaplayer.pause.intent")
        self.enable_intent("mediaplayer.resume.intent")

    def disable_play_controls(self):
        self.disable_intent("mediaplayer.next.intent")
        self.disable_intent("mediaplayer.prev.intent")
        self.disable_intent("mediaplayer.stop.intent")
        self.disable_intent("mediaplayer.pause.intent")
        self.disable_intent("mediaplayer.resume.intent")

    def handle_mediaplayer_info(self, message):
        self.speak_dialog('mediaplayer')
        self.speak("looking for backends.")
        for backend in self.audio_service.available_backends().keys():
            backend_text = "found " + str(backend)
            self.speak(backend_text)

    def handle_mediaplayer_next(self, message):
        if self.audio_service.is_playing:
            if not self.is_track_change_request_in_progress():
                self.play_next(message)
        else:
            self.speak("Nothing playing")

    def handle_mediaplayer_prev(self, message):
        if self.audio_service.is_playing:
            self.play_prev(message)
        else:
            self.speak("Nothing playing")

    def handle_mediaplayer_stop(self, message):
        if self.audio_service.is_playing:
            self.play_stop(message)
        else:
            self.speak("Nothing playing")

    def handle_mediaplayer_pause(self, message):
        self.speak("pause")
        self.play_pause(message)

    def handle_mediaplayer_resume(self, message):
        self.speak("resume")
        pass

    # @intent_handler('mediaplayer.play.intent')
    # def handle_mediaplayer_play(self, message):
    #     if not self.is_playing:
    #         self.play(message)
    #     else:
    #         self.speak("Already playing")

    def load_files_in_audio_path(self, path):
        tracks = []

        for dirpath, dirnames, filenames in os.walk(path):
            for file in filenames:
                track_path = Path(dirpath)
                track_path = track_path / file
                track_uri = 'file://' + str(track_path.resolve())
                track_data = (track_uri)
                tracks.append(track_data)
        return tracks

    def init_vlc_audio_list(self):
        self.vlc_all_tracks = self.load_files_in_audio_path(
            self.vlc_audio_path)
        # for track in self.vlc_all_tracks:
        #     self.audio_service.play(track)
        #     self.vlc_all_tracks_info.append([ track, self.audio_service.track_info()])
        #     self.audio_service.pause()

        #self.queue_tracks(self.vlc_all_tracks)
        self.current_track = []

    def get_vlc_track_info(self):
        pass

    def queue_tracks(self, tracks):
        self.audio_service.queue(tracks)

    def add_track_to_list(self, track, list):
        pass

    def add_tracks_to_list(self, tracks, list):
        pass

    def play(self, message):

        if not self.audio_service.is_playing:
            if not self.vlc_all_tracks:
                self.init_vlc_audio_list()
                self.audio_service.play(self.vlc_all_tracks, 'vlc')
                self.set_init_track()
            else:
                self.audio_service.play(self.vlc_all_tracks, 'vlc')
                self.set_init_track()
            #self.audio_service.play(self.audio_service.)
            #self.audio_service.play(self.vlc_all_tracks, 'vlc')
        pass

    def play_next(self, message):

        if self.audio_service.is_playing:
            self.audio_service.next()
            #if not self.is_track_change_request_in_progress():
            #    self.start_track_change_request()
            #    self.audio_service.next()
        pass

    def play_prev(self, message):
        pass

    def play_random(self, message):
        pass

    def play_resume(self, message):
        if self.audio_service.is_playing:
            self.audio_service.resume()

    def play_stop(self, message):
        if self.audio_service.is_playing:
            self.audio_service.pause()

    def play_pause(self, message):
        if self.audio_service.is_playing:
            self.audio_service.pause()

    def track_info(self, message):
        return self.audio_service.track_info()

    def track_info_reply(self, message):
        self.speak("request: track_info_reply")
        #return self.audio_service.track_info()

    def queue_track(self, message):
        self.speak("event: queue_track")
        pass

    def set_init_track(self):
        self.current_track = self.audio_service.track_info()

    def is_track_change_request_in_progress(self):
        if self.track_change_request_in_progress == True:
            if self.current_track != self.audio_service.track_info():
                self.complete_track_change_request()

        return self.track_change_request_in_progress

    def start_track_change_request(self):
        self.track_change_request_in_progress = True

    def complete_track_change_request(self):
        self.current_track = self.audio_service.track_info()
        self.track_change_request_in_progress = False

    def CPS_match_query_phrase(self, phrase):
        self.speak("phrase : " + str(phrase))

        if self.voc_match(phrase, "mediaplayer"):
            level = CPSMatchLevel.GENERIC
            phrase = "mediaplayer"
        else:
            level = CPSMatchLevel.GENERIC
        return (phrase, level)

    def CPS_start(self, phrase, data):
        data = {
            "phrase": phrase,
            "skill_id": self.skill_id,
            "service_name": self.spoken_name,
        }
        self.play(phrase)
        pass

    def CPS_send_status(self, artist='', track='', image=''):
        data = {
            'skill': self.name,
            'artist': artist,
            'track': track,
            'image': image,
            'status': None  # TODO Add status system
        }
        self.bus.emit(Message('play:status', data))