import re import time from fuzzywuzzy.process import extractOne as extract_one from mycroft.skills.common_play_skill import CommonPlaySkill, CPSMatchLevel from .mopidypost import Mopidy NOTHING_FOUND = (None, 0.0) class MopidySkill(CommonPlaySkill): def __init__(self): super(MopidySkill, self).__init__('Mopidy Skill') self.mopidy = None self.volume_is_low = False self.regexes = {} def _connect(self): url = 'http://localhost:6680' if self.settings: url = self.settings.get('mopidy_url', url) try: mopidy = Mopidy(url) except Exception: self.log.warning('Could not connect to Mopidy server') return None self.log.info('Connected to mopidy server') self.cancel_scheduled_event('MopidyConnect') self.albums = {} self.artists = {} self.genres = {} self.playlists = {} self.radios = {} self.track_names = {} self.log.info('Loading content') self.albums['gmusic'] = mopidy.get_gmusic_albums() self.artists['gmusic'] = mopidy.get_gmusic_artists() self.genres['gmusic'] = mopidy.get_gmusic_radio() self.playlists['gmusic'] = {} self.track_names['gmusic'] = {} self.albums['local'] = mopidy.get_local_albums() self.artists['local'] = mopidy.get_local_artists() self.genres['local'] = mopidy.get_local_genres() self.playlists['local'] = mopidy.get_local_playlists() self.track_names['local'] = mopidy.get_local_track_names() self.albums['spotify'] = {} self.artists['spotify'] = {} self.genres['spotify'] = {} self.playlists['spotify'] = mopidy.get_spotify_playlists() self.track_names['spotify'] = {} self.playlist = {} for loc in ['local', 'gmusic', 'spotify']: self.log.info(loc) self.playlist.update(self.playlists[loc]) self.log.info(loc) self.playlist.update(self.genres[loc]) self.log.info(loc) self.playlist.update(self.artists[loc]) self.log.info(loc) self.playlist.update(self.albums[loc]) self.log.info(loc) self.playlist.update(self.track_names[loc]) return mopidy def initialize(self): self.log.info('initializing Mopidy skill') super(MopidySkill, self).initialize() # Setup handlers for playback control messages self.add_event('mycroft.audio.service.next', self.handle_next) self.add_event('mycroft.audio.service.prev', self.handle_prev) self.add_event('mycroft.audio.service.pause', self.handle_pause) self.add_event('mycroft.audio.service.resume', self.handle_resume) self.mopidy = self._connect() def play(self, tracks): self.mopidy.clear_list() self.mopidy.add_list(tracks) self.mopidy.play() def translate_regex(self, regex): if regex not in self.regexes: path = self.find_resource(regex + '.regex', 'dialog') if path: with open(path) as f: string = f.read().strip() self.regexes[regex] = string return self.regexes[regex] def CPS_match_query_phrase(self, phrase): # If no mopidy connection can be detected return None if self.mopidy is None: self.mopidy = self._connect() if not self.mopidy: return None self.log.info('Checking Mopidy for {}'.format(phrase)) mopidy_specified = 'mopidy' in phrase phrase = re.sub(self.translate_regex('on_mopidy'), '', phrase) match_level = CPSMatchLevel.MULTI_KEY match = self.specific_query(phrase) # If nothing was found check for a generic match if match == NOTHING_FOUND: match = self.generic_query(phrase) match_level = CPSMatchLevel.GENERIC self.log.info('Mopidy match: {}'.format(match)) if match == NOTHING_FOUND: self.log.debug('Nothing found on mopidy') return None else: return (phrase, (CPSMatchLevel.EXACT if mopidy_specified else match_level), {'playlist': match[0], 'playlist_type': match[2], 'library_type': match[3] }) def query_song(self, song): best_found = None best_conf = 0 library_type = None for t in self.track_names: found, conf = (extract_one(song, self.track_names[t].keys()) or (None, 0)) if conf > best_conf and conf > 50: best_conf = conf best_found = found library_type = t return best_found, best_conf, 'song', library_type def query_artist(self, artist): best_found = None best_conf = 0.0 library_type = None for t in self.artists: found, conf = (extract_one(artist, self.artists[t].keys()) or (None, 0)) if conf > best_conf and conf > 50: best_conf = conf best_found = found library_type = t return best_found, best_conf, 'artist', library_type def query_album(self, album): best_found = None best_conf = 0 library_type = None for t in self.albums: self.log.info(self.albums[t].keys()) found, conf = (extract_one(album, self.albums[t].keys()) or (None, 0)) if conf > best_conf and conf > 50: best_conf = conf best_found = found library_type = t self.log.info('ALBUMS') self.log.info((best_found, best_conf)) return best_found, best_conf, 'album', library_type def specific_query(self, phrase): """Check if the request is for a specific type. This checks, albums, artists, genres and tracks. """ # Check if playlist #match = re.match(self.translate_regex('playlist'), phrase) #if match: # return self.query_playlist(match.groupdict()['playlist']) # Check album match = re.match(self.translate_regex('album'), phrase) if match: album = match.groupdict()['album'] return self.query_album(album) # Check artist match = re.match(self.translate_regex('artist'), phrase) if match: artist = match.groupdict()['artist'] return self.query_artist(artist) match = re.match(self.translate_regex('song'), phrase) if match: song = match.groupdict()['track'] return self.query_song(song) return NOTHING_FOUND def generic_query(self, phrase): found, conf = extract_one(phrase, self.playlist.keys()) if conf > 50: return found, conf, 'generic', '' else: return NOTHING_FOUND def CPS_start(self, phrase, data): p = data.get('playlist') list_type = data.get('playlist_type', 'generic') library_type = data.get('library_type', 'generic') lists = {'generic': self.playlist, 'artist': self.artists, 'album': self.albums, 'song': self.track_names } if list_type == 'generic': playlists = lists[list_type] else: playlists = lists[list_type][library_type] self.stop() self.speak('Playing {}'.format(p)) time.sleep(3) if playlists[p]['type'] == 'playlist': tracks = self.mopidy.get_items(playlists[p]['uri']) if playlists[p]['type'] == 'track': tracks = playlists[p]['uri'] else: tracks = self.mopidy.get_tracks(playlists[p]['uri']) self.play(tracks) def stop(self, message=None): self.log.info('Handling stop request') if self.mopidy: self.mopidy.clear_list() self.mopidy.stop() def handle_next(self, message): self.mopidy.next() def handle_prev(self, message): self.mopidy.previous() def handle_pause(self, message): self.mopidy.pause() def handle_resume(self, message): """Resume playback if paused""" self.mopidy.resume() def lower_volume(self, message): self.log.info('lowering volume') self.mopidy.lower_volume() self.volume_is_low = True def restore_volume(self, message): self.log.info('maybe restoring volume') self.volume_is_low = False time.sleep(2) if not self.volume_is_low: self.log.info('restoring volume') self.mopidy.restore_volume() def handle_currently_playing(self, message): current_track = self.mopidy.currently_playing() if current_track is not None: self.mopidy.lower_volume() time.sleep(1) if 'album' in current_track: data = {'current_track': current_track['name'], 'artist': current_track['album']['artists'][0]['name']} self.speak_dialog('currently_playing', data) time.sleep(6) self.mopidy.restore_volume() def create_skill(): return MopidySkill()