def __init__(self, library, hostname='localhost', port=9090, username='', password='', charset='utf8'): """init""" LMSServerNotifications.__init__(self, self._callback, hostname, port, username, password, charset) self.logger = logging.getLogger("LMSPlaylist") #objects #create new LMSServer to perform independant request self.__server = LMSServer(hostname, port, username, password) #members self.running = True self.library = library self.__lastresponse = {} self.__play_callback = None self.__pause_callback = None self.__stop_callback = None self.__addtrack_callback = None self.__deltrack_callback = None self.__movetrack_callback = None self.__reload_callback = None
def __init__(self, server_ip, server_port=9090, server_user='', server_password=''): """constructor""" #init self.logger = logging.getLogger("Library") #members self.server_ip = server_ip self.server_port = server_port self.__cover_path = os.path.join(os.path.expanduser('~'), '.squeezedesktop', 'cache') if not os.path.exists(self.__cover_path): #create cache directory os.makedirs(self.__cover_path) self.__server_infos_path = os.path.join(os.path.expanduser('~'), '.squeezedesktop', 'server.conf') self.__albums_count = 0 self.__artists_count = 0 self.__genres_count = 0 self.__years_count = 0 #objects self.server = LMSServer(server_ip, server_port, server_user, server_password) self.server.connect() self.cache_covers = None
def __init__(self, server_ip, server_cli_port=9090, server_html_port=9000, server_user='', server_password=''): """ Constructor """ #init self.logger = logging.getLogger("Library") #members self.server_ip = server_ip self.server_cli_port = server_cli_port self.server_html_port = server_html_port self.__cover_path = os.path.join(os.path.expanduser('~'), '.squeezedesktop', 'cache') if not os.path.exists(self.__cover_path): try: #create cache directory os.makedirs(self.__cover_path) except: self.logger.warning('Unable to create ~/.squeezedesktop directory. Cover cache disabled') self.__cover_path = None self.__server_infos_path = os.path.join(os.path.expanduser('~'), '.squeezedesktop', 'server.conf') self.__albums_count = 0 self.__artists_count = 0 self.__genres_count = 0 self.__years_count = 0 #objects self.server = LMSServer(server_ip, server_cli_port, server_user, server_password) self.server.connect() self.cache_covers = None
class LMSPlaylist(LMSServerNotifications): """ Manage playlist """ FILTER_TIMEOUT = 10 #in ms ALLOWED_COMMANDS = ['playlist', 'power', 'play', 'pause'] def __init__(self, library, hostname='localhost', port=9090, username='', password='', charset='utf8'): """ Constructor """ LMSServerNotifications.__init__(self, self._callback, hostname, port, username, password, charset) self.logger = logging.getLogger("LMSPlaylist") #objects #create new LMSServer to perform independant request self.__server = LMSServer(hostname, port, username, password) #members self.running = True self.library = library self.__lastresponse = {} self.__play_callback = None self.__pause_callback = None self.__stop_callback = None self.__addtrack_callback = None self.__deltrack_callback = None self.__movetrack_callback = None self.__reload_callback = None self.__lastNewsongId = None def __millitime(self): return int(round(time.time() * 1000)) def _callback(self): #nothing to do here, everything is done in overwritten method _process_response pass def __filterByTimestamp(self, player_id): """ Filter response by timestamp @return True if response must be filtered """ msec = self.__millitime() if self.__lastresponse.has_key(player_id): if msec<(self.__lastresponse[player_id] + self.FILTER_TIMEOUT): #forget response return True else: #update last response time self.__lastresponse[player_id] = msec else: #save current response time self.__lastresponse[player_id] = msec return False def __filterByResponse(self, command): """ Filter response by command @return True if response must be filtered """ if not command in self.ALLOWED_COMMANDS: return True else: return False def _process_response(self, items): """ Overwrite _process_reponse from LMSServerNotifications process response received by lmsserver this function can be overwriten to process some other stuff """ self.logger.debug('-->_process_response %s' % str(items)) try: #filter response if self.__filterByResponse(items[1]): #don't process response self.logger.debug(' ---> response kicked') return None if items[1]=='playlist': if items[2]=='newsong': #192.168.1.1 playlist newsong Thinking%20Of%20You%20(Flo%20Rida) 20 #player starts playing a new song #HACK: when playing songs randomly, there is an issue in squeezeboxserver: newsong is sometimes called twice, #once with current song and secondly with the new current song. Also specified playlist_index is incorrect. #To fix that we call get_current_song function manually and return song infos in callback parameters and we #store last song id received to drop duplicates song = self.get_current_song(items[0]) self.logger.debug('---> newsong notif %s' % song) if not song: #no song found (why?), execute callback anyway self.logger.debug('no song found') self.__play_callback(items[0], song, items[4]) elif song.has_key('id') and song['id']!=self.__lastNewsongId: #song id is different than previous one, execute callback self.logger.debug('song found') self.__lastNewsongId = song['id'] self.__play_callback(items[0], song, items[4]) else: #drop notification self.logger.debug('--> drop notification') elif items[2]=='pause': #192.168.1.1 pause [0|1] #player update pause status if len(items)==4: if items[3]=='1': #player is paused if self.__pause_callback: self.__pause_callback(items[0]) else: #player is playing if self.__play_callback: self.__play_callback(items[0], '', '') else: #player is paused if self.__pause_callback: self.__pause_callback(items[0]) elif items[2]=='addtracks': #192.168.1.1 playlist addtracks track.id=274336 index:40 #new track added in playlist if self.__addtrack_callback: self.__addtrack_callback(items[0], items[3], items[4].replace('index:', '')) elif items[2]=='delete': #192.168.1.1 playlist delete 0 #new track added in playlist if self.__deltrack_callback: self.__deltrack_callback(items[0], items[3]) elif items[2]=='stop': #192.168.1.1 playlist stop #player stopped if self.__stop_callback: self.__stop_callback(items[0]) elif items[2]=='loadtracks': #192.168.1.1 playlist loadtracks track.id%3D243510 index%3A0 #playlist reloaded if self.__reload_callback: self.__reload_callback(items[0]) elif items[2]=='': #192.168.1.1 playlist move 24 23 #track moved in playlist if self.__movetrack_callback: self.__movetrack_callback(items[0], int(items[3]), int(items[4])) elif items[1]=='pause': #00:04:20:12:47:33 pause if self.__pause_callback: self.__pause_callback(items[0]) elif items[1]=='play': #00:04:20:12:47:33 play if self.__play_callback: self.__play_callback(items[0], '', '') elif items[1]=='power': if items[2]=='1': #00:04:20:12:47:33 power 1 #player is on if self.__on_callback: self.__on_callback(items[0]) elif items[2]=='0': #00:04:20:12:47:33 power 0 #player is off if self.__off_callback: self.__off_callback(items[0]) except Exception as e: self.logger.exception('Exception in _process_response:') def set_callbacks(self, play_callback, pause_callback, stop_callback, on_callback, off_callback, addtrack_callback, deltrack_callback, movetrack_callback, reload_callback): """ Set callbacks: @param play_callback: player starts playing @param pause_callback: player playback is paused @param stop_callback: player stops playing @param addtrack_callback: new track added to playlist @param deltrack_callback: track deleted from playlist @param movetrack_callback : track moved on playlist @param reload_callback: playlist reloaded @param on_callback: player switched on @param off_callback: player switched off """ self.__play_callback = play_callback self.__pause_callback = pause_callback self.__stop_callback = stop_callback self.__addtrack_callback = addtrack_callback self.__deltrack_callback = deltrack_callback self.__movetrack_callback = movetrack_callback self.__reload_callback = reload_callback self.__on_callback = on_callback self.__off_callback = off_callback def get_playlist(self, player_id): """ Return full playlist content """ playlist = [] #get number of item in playlist self.logger.debug('request') count = 0 try: count = int(self.__server.request('%s playlist tracks ?' % player_id)) except Exception as e: self.logger.exception('Failed to get playlist songs count:') count = 0 self.logger.debug('playlist count=%d' % count) #get current song current_song = 0 try: current_song = int(self.__server.request('%s playlist index ?' % player_id)) except Exception as e: self.logger.exception('Failed to get current song index:') current_song = 0 self.logger.debug('current_song=%d' % current_song) #get songs infos one by one self.logger.debug('player_id=%s' % player_id) for i in range(count): try: count, url, error = self.__server.request_with_results('%s playlist path %d ?' % (player_id, i)) if not error: url = '%s%s' % ('file:',url[0]['file']) #self.logger.debug('url=%s' % url) song = self.library.get_song_infos_by_url(url) #add current song info if i==current_song: song.update({'current':True}) else: song.update({'current':False}) playlist.append( song ) except Exception, e: #problem during song infos retrieving self.logger.exception('Unable to get song infos:') return playlist
class LMSLibrary(): LIBRARY_EMPTY = 0 LIBRARY_UPTODATE = 1 LIBRARY_UPDATING = 2 def __init__(self, server_ip, server_cli_port=9090, server_html_port=9000, server_user='', server_password=''): """ Constructor """ #init self.logger = logging.getLogger("Library") #members self.server_ip = server_ip self.server_cli_port = server_cli_port self.server_html_port = server_html_port self.__cover_path = os.path.join(os.path.expanduser('~'), '.squeezedesktop', 'cache') if not os.path.exists(self.__cover_path): try: #create cache directory os.makedirs(self.__cover_path) except: self.logger.warning('Unable to create ~/.squeezedesktop directory. Cover cache disabled') self.__cover_path = None self.__server_infos_path = os.path.join(os.path.expanduser('~'), '.squeezedesktop', 'server.conf') self.__albums_count = 0 self.__artists_count = 0 self.__genres_count = 0 self.__years_count = 0 #objects self.server = LMSServer(server_ip, server_cli_port, server_user, server_password) self.server.connect() self.cache_covers = None def __del__(self): """ Destructor """ #only stop threads if runnings if self.cache_covers: self.cache_covers.stop() def get_albums(self): """ Return all albums """ # id Album ID. Item delimiter. #l album Album name, including the server's added "(N of M)" if the server is set to group multi disc albums together. See tag "title" for the unmodified value. #y year Album year. This is determined by the server based on the album tracks. #j artwork_track_id Identifier of one of the album tracks, used by the server to display the album's artwork. #t title "Raw" album title as found in the album tracks ID3 tags, as opposed to "album". Note that "title" and "album" are identical if the server is set to group discs together. #i disc Disc number of this album. Only if the server is not set to group multi-disc albums together. #q disccount Number of discs for this album. Only if known. #w compilation 1 if this album is a compilation. #a artist The album artist (depends on server configuration). #S artist_id The album artist id (depends on server configuration). #s textkey The album's "textkey" is the first letter of the sorting key. #X album_replay_gain The album's replay-gain. #need at least j tag to find associated cover in cache count, items, error = self.server.request_with_results('albums 0 %d tags:lj' % self.__albums_count) if error: return None else: return items def get_album(self, id): """ Return album infos """ if id!=None: count, items, error = self.server.request_with_results('albums 0 1 album_id:%d tags:ljyS' % id) if error: return None else: return items else: return None def get_album_songs(self, id): """ Return all songs from specified album id """ # rescan Returned with value 1 if the server is still scanning the database. The results may therefore be incomplete. Not returned if no scan is in progress. # count Number of results returned by the query, that is, total number of elements to return for this song. # id Track ID. # title Song title #a artist Artist name. #A <role> For every artist role (one of "artist", "composer", "conductor", "band", "albumartist" or "trackartist"), a comma separated list of names. #B buttons A hash with button definitions. Only available for certain plugins such as Pandora. #c coverid coverid to use when constructing an artwork URL, such as /music/$coverid/cover.jpg #C compilation 1 if the album this track belongs to is a compilation #d duration Song duration in seconds. #e album_id Album ID. Only if known. #f filesize Song file length in bytes. Only if known. #g genre Genre name. Only if known. #G genres Genre names, separated by commas (only useful if the server is set to handle multiple items in tags). #i disc Disc number. Only if known. #I samplesize Song sample size (in bits) #j coverart 1 if coverart is available for this song. Not listed otherwise. #J artwork_track_id Identifier of the album track used by the server to display the album's artwork. Not listed if artwork is not available for this album. #k comment Song comments, if any. #K artwork_url A full URL to remote artwork. Only available for certain plugins such as Pandora and Rhapsody. #l album Album name. Only if known. #L info_link A custom link to use for trackinfo. Only available for certain plugins such as Pandora. #m bpm Beats per minute. Only if known. #M musicmagic_mixable 1 if track is mixable, otherwise 0. #n modificationTime Date and time song file was last changed on disk. #N remote_title Title of the internet radio station. #o type Content type. Only if known. #p genre_id Genre ID. Only if known. #P genre_ids Genre IDs, separated by commas (only useful if the server is set to handle multiple items in tags). #D addedTime Date and time song file was first added to the database. #U lastUpdated Date and time song file was last updated in the database. #q disccount Number of discs. Only if known. #r bitrate Song bitrate. Only if known. #R rating Song rating, if known and greater than 0. #s artist_id Artist ID. #S <role>_ids For each role as defined above, the list of ids. #t tracknum Track number. Only if known. #T samplerate Song sample rate (in KHz) #u url Song file url. #v tagversion Version of tag information in song file. Only if known. #w lyrics Lyrics. Only if known. #x remote If 1, this is a remote track. #X album_replay_gain Replay gain of the album (in dB), if any #y year Song year. Only if known. #Y replay_gain Replay gain (in dB), if any if id!=None: count, items, error = self.server.request_with_results('songs 0 200 album_id:%deJ' % id) if error: return None else: return items else: return None def get_artists(self): """ Return all artists """ # id Artist ID. Item delimiter. # artist Artist name. #s textkey The artist's "textkey" is the first letter of the sorting key. count, items, error = self.server.request_with_results('artists 0 %d' % self.__artists_count) if error: return None else: return items def get_artist(self, id): """ Return artist infos """ if id!=None: count, items, error = self.server.request_with_results('artists 0 1 artist_id:%d' % id) if error: return None else: return items else: return None def get_artist_albums(self, id): """ Return albums from specified artist id """ if id!=None: count, items, error = self.server.request_with_results('albums 0 %d artist_id:%d tags:ljyS' % (self.__albums_count, id)) if error: return None else: return items else: return None def get_genres(self): """ Return all genres """ count, items, error = self.server.request_with_results('genres 0 %d' % self.__genres_count) if error: return None else: return items def get_genre(self, id): """ Return genre infos """ if id!=None: count, items, error = self.server.request_with_results('genre 0 1 genre_id:%d' % id) if error: return None else: return items else: return None def get_genre_albums(self, id): """ Return albums from specified genre id """ if id!=None: count, items, error = self.server.request_with_results('albums 0 %d genre_id:%d tags:ljyS' % (self.__albums_count, id)) if error: return None else: return items else: return None def get_years(self): """ Return all years """ count, items, error = self.server.request_with_results('years 0 %d' % self.__years_count) if error: return None else: return items def get_year_albums(self, id): """ Return albums from specified year id """ if id!=None: count, items, error = self.server.request_with_results('albums 0 %d year:%d tags:ljyS' % (self.__albums_count, id)) if error: return None else: return items else: return None def get_song_infos(self, id): """ Return full song infos """ if id!=None: count, items, error = self.server.request_with_results('songinfo 0 50 track_id:%d tags:adefgIJKlNortTuvxyY' % id) if not error and count==1: return items[0] else: return None else: return None def get_song_infos_by_url(self, url): """ Return full song infos """ if id!=None: count, items, error = self.server.request_with_results('songinfo 0 50 url:%s tags:adefgIJKlNortTuvxyY' % url) self.logger.debug('count=%d' % count) self.logger.debug('items=%s' % str(items)) self.logger.debug('error=%s' % str(error)) if not error and count==1: return items[0] else: return None else: return None def get_remote_cover(self, artwork_url): """ Just download cover of remote service and return data @param artwork_url: url found in player.get_current_status. Only available for online services (deezer, spotify, pandora...) """ try: #generate url url = 'http://%s:%d/%s' % (self.server_ip, self.server_html_port, artwork_url) self.logger.debug('Remote cover url: %s' % url) #download and return file content b = urllib.urlopen(url) return b.read() except: self.logger.exception('Unable to get remote cover:') return None def get_cover(self, album_id, artwork_track_id, filename, size=(100,100)): """ Get cover and return cover data or None if error @param filename: cover filename used to get file extension """ if album_id and artwork_track_id: #get extension (_, ext) = os.path.splitext(filename) if not ext or len(ext)==0: ext = '.png' #generate url url = 'http://%s:%d/music/%s/cover_%dx%d%s' % (self.server_ip, self.server_html_port, artwork_track_id, size[0], size[1], ext) self.logger.debug('Cover url: "%s"' % url) #download and return file content try: b = urllib.urlopen(url) return b.read() except: self.logger.exception('Unable to get cover:') return None else: #missing parameters self.logger.error('Unable to get cover, missing parameters!') return None def get_cover_path(self, album_id, artwork_track_id, local_path=None, filename=None, size=(100,100)): """ Return cover from cache or try to download it from server (if local_path specified) @param local_path: path to download file. Mandatory if covers aren't cached. @param filename: output filename. Default "cover_<width>x<height>.png" @param size: size of cover. Useless if covers are cached """ cover_path = None if album_id and artwork_track_id: if self.__cover_path!=None: cover_path = os.path.join(self.__cover_path, '%s_%s.png' % (album_id, artwork_track_id)) self.logger.debug('Cover path:%s' % cover_path) if not os.path.exists(cover_path): #no cover cached if local_path: #path specified, try to download it directly from server #build output filepath cover_path = None if filename: cover_path = os.path.join(local_path, filename) else: cover_path = os.path.join(local_path, '%s_%s.png' % (album_id, artwork_track_id)) #download and save file locally data = self.get_cover(album_id, artwork_track_id, filename, size) if data: f = open(cover_path, 'wb') f.write(data) f.close() self.logger.debug('Cover downloaded to %s' % cover_path) else: self.logger.error('Unable to get cover data') cover_path = None else: #cover doesn't exists self.logger.error('Cover for album id %d and track id %d is not cached!') cover_path = None else: #cover cache disabled pass return cover_path def search(self, term): """ Search something on database """ #TODO pass def check_update(self, update_covers_cache=False): """ Check if library needs update, return True if database needs update followed by number of albums, artists, and genres """ #get stats lms_genres_count = int(self.server.request('info total genres ?')) self.__genres_count = lms_genres_count lms_artists_count = int(self.server.request('info total artists ?')) self.__artists_count = lms_artists_count lms_albums_count = int(self.server.request('info total albums ?')) self.__albums_count = lms_albums_count self.logger.debug('LMS total artists=%d albums=%d genres=%d' % (lms_artists_count, lms_albums_count, lms_genres_count)) #check if fresh install if not os.path.exists(self.__server_infos_path): #conf file doesn't exist create empty one conf = open(self.__server_infos_path, 'w') conf.write('albums:0\nartists:0\ngenres:0') local_genres_count = 0 local_artists_count = 0 local_albums_count = 0 else: #read from file conf = open(self.__server_infos_path, 'r') for line in conf.readlines(): if line.startswith('artists'): try: local_artists_count = int(line.split(':')[1].strip()) except: local_artists_count = 0 elif line.startswith('albums'): try: local_albums_count = int(line.split(':')[1].strip()) except: local_albums_count = 0 elif line.startswith('genres'): try: local_genres_count = int(line.split(':')[1].strip()) except: local_genres_count = 0 #compare results self.logger.debug('%d==%d %d==%d %d==%d' % (lms_genres_count, local_genres_count, lms_artists_count, local_artists_count, lms_albums_count, local_albums_count)) if lms_genres_count!=local_genres_count or lms_artists_count!=local_artists_count or lms_albums_count!=local_albums_count: #need update if update_covers_cache: self.logger.debug('Need covers cache update') self.__cache_covers(lms_albums_count) def __cache_covers(self, albums_count): """ Cache covers for thumbnails """ count, albums, error = self.server.request_with_results('albums 0 %d tags:j' % albums_count) if not error: if self.__cover_path!=None: self.logger.debug('Updating cache...') self.cache_covers = CacheCovers(self.server_ip, self.server_html_port, self.__cover_path, albums) self.cache_covers.start() else: #error self.logger.error('Unable to get albums list')
class LMSPlaylist(LMSServerNotifications): """Manage playlist""" FILTER_TIMEOUT = 10 #in ms ALLOWED_COMMANDS = ['playlist', 'power', 'play', 'pause'] def __init__(self, library, hostname='localhost', port=9090, username='', password='', charset='utf8'): """init""" LMSServerNotifications.__init__(self, self._callback, hostname, port, username, password, charset) self.logger = logging.getLogger("LMSPlaylist") #objects #create new LMSServer to perform independant request self.__server = LMSServer(hostname, port, username, password) #members self.running = True self.library = library self.__lastresponse = {} self.__play_callback = None self.__pause_callback = None self.__stop_callback = None self.__addtrack_callback = None self.__deltrack_callback = None self.__movetrack_callback = None self.__reload_callback = None def __millitime(self): return int(round(time.time() * 1000)) def _callback(self): #nothing to do here, everything is done in overwritten method _process_response pass def __filterByTimestamp(self, player_id): """Filter response by timestamp return True if response must be filtered""" msec = self.__millitime() if self.__lastresponse.has_key(player_id): if msec < (self.__lastresponse[player_id] + self.FILTER_TIMEOUT): #forget response return True else: #update last response time self.__lastresponse[player_id] = msec else: #save current response time self.__lastresponse[player_id] = msec return False def __filterByResponse(self, command): """Filter response by command return True if response must be filtered""" if not command in self.ALLOWED_COMMANDS: return True else: return False def _process_response(self, items): """overwrite _process_reponse from LMSServerNotifications process response received by lmsserver this function can be overwriten to process some other stuff""" self.logger.debug('-->_process_response %s' % str(items)) try: #filter response if self.__filterByResponse(items[1]): #don't process response self.logger.debug(' ---> response kicked') return None if items[1] == 'playlist': if items[2] == 'newsong': #192.168.1.1 playlist newsong Thinking%20Of%20You%20(Flo%20Rida) 20 #player starts playing a new song if self.__play_callback: self.__play_callback(items[0], items[3], items[4]) elif items[2] == 'pause': #192.168.1.1 pause [0|1] #player update pause status if len(items) == 4: if items[3] == '1': #player is paused if self.__pause_callback: self.__pause_callback(items[0]) else: #player is playing if self.__play_callback: self.__play_callback(items[0], '', '') else: #player is paused if self.__pause_callback: self.__pause_callback(items[0]) elif items[2] == 'addtracks': #192.168.1.1 playlist addtracks track.id=274336 index:40 #new track added in playlist if self.__addtrack_callback: self.__addtrack_callback( items[0], items[3], items[4].replace('index:', '')) elif items[2] == 'delete': #192.168.1.1 playlist delete 0 #new track added in playlist if self.__deltrack_callback: self.__deltrack_callback(items[0], items[3]) elif items[2] == 'stop': #192.168.1.1 playlist stop #player stopped if self.__stop_callback: self.__stop_callback(items[0]) elif items[2] == 'loadtracks': #192.168.1.1 playlist loadtracks track.id%3D243510 index%3A0 #playlist reloaded if self.__reload_callback: self.__reload_callback(items[0]) elif items[2] == '': #192.168.1.1 playlist move 24 23 #track moved in playlist if self.__movetrack_callback: self.__movetrack_callback(items[0], int(items[3]), int(items[4])) elif items[1] == 'pause': #00:04:20:12:47:33 pause if self.__pause_callback: self.__pause_callback(items[0]) elif items[1] == 'play': #00:04:20:12:47:33 play if self.__play_callback: self.__play_callback(items[0], '', '') elif items[1] == 'power': if items[2] == '1': #00:04:20:12:47:33 power 1 #player is on if self.__on_callback: self.__on_callback(items[0]) elif items[2] == '0': #00:04:20:12:47:33 power 0 #player is off if self.__off_callback: self.__off_callback(items[0]) except Exception as e: self.logger.error('Exception in _process_response: %s' % str(e)) def set_callbacks(self, play_callback, pause_callback, stop_callback, on_callback, off_callback, addtrack_callback, deltrack_callback, movetrack_callback, reload_callback): """callbacks: play_callback: player starts playing pause_callback: player playback is paused stop_callback: player stops playing addtrack_callback: new track added to playlist deltrack_callback: track deleted from playlist movetrack_callback : track moved on playlist reload_callback: playlist reloaded on_callback: player switched on off_callback: player switched off """ self.__play_callback = play_callback self.__pause_callback = pause_callback self.__stop_callback = stop_callback self.__addtrack_callback = addtrack_callback self.__deltrack_callback = deltrack_callback self.__movetrack_callback = movetrack_callback self.__reload_callback = reload_callback self.__on_callback = on_callback self.__off_callback = off_callback def get_playlist(self, player_id): """return full playlist content""" playlist = [] #get number of item in playlist self.logger.debug('request') count = 0 try: count = int( self.__server.request('%s playlist tracks ?' % player_id)) except Exception as e: self.logger.fatal('Failed to get playlist songs count: %s' % str(e)) count = 0 self.logger.debug('playlist count=%d' % count) #get current song current_song = 0 try: current_song = int( self.__server.request('%s playlist index ?' % player_id)) except Exception as e: self.logger.fatal('Failed to get current song index: %s' % str(e)) current_song = 0 self.logger.debug('current_song=%d' % current_song) #get songs infos one by one self.logger.debug('player_id=%s' % player_id) for i in range(count): try: count, url, error = self.__server.request_with_results( '%s playlist path %d ?' % (player_id, i)) if not error: url = '%s%s' % ('file:', url[0]['file']) #self.logger.debug('url=%s' % url) song = self.library.get_song_infos_by_url(url) #add current song info if i == current_song: song.update({'current': True}) else: song.update({'current': False}) playlist.append(song) except Exception, e: #problem during song infos retrieving self.logger.error('Unable to get song infos: %s' % str(e)) return playlist
class LMSLibrary(): LIBRARY_EMPTY = 0 LIBRARY_UPTODATE = 1 LIBRARY_UPDATING = 2 def __init__(self, server_ip, server_port=9090, server_user='', server_password=''): """constructor""" #init self.logger = logging.getLogger("Library") #members self.server_ip = server_ip self.server_port = server_port self.__cover_path = os.path.join(os.path.expanduser('~'), '.squeezedesktop', 'cache') if not os.path.exists(self.__cover_path): #create cache directory os.makedirs(self.__cover_path) self.__server_infos_path = os.path.join(os.path.expanduser('~'), '.squeezedesktop', 'server.conf') self.__albums_count = 0 self.__artists_count = 0 self.__genres_count = 0 self.__years_count = 0 #objects self.server = LMSServer(server_ip, server_port, server_user, server_password) self.server.connect() self.cache_covers = None def __del__(self): """destructor""" #only stop threads if runnings if self.cache_covers: self.cache_covers.stop() def get_albums(self): """return all albums""" # id Album ID. Item delimiter. #l album Album name, including the server's added "(N of M)" if the server is set to group multi disc albums together. See tag "title" for the unmodified value. #y year Album year. This is determined by the server based on the album tracks. #j artwork_track_id Identifier of one of the album tracks, used by the server to display the album's artwork. #t title "Raw" album title as found in the album tracks ID3 tags, as opposed to "album". Note that "title" and "album" are identical if the server is set to group discs together. #i disc Disc number of this album. Only if the server is not set to group multi-disc albums together. #q disccount Number of discs for this album. Only if known. #w compilation 1 if this album is a compilation. #a artist The album artist (depends on server configuration). #S artist_id The album artist id (depends on server configuration). #s textkey The album's "textkey" is the first letter of the sorting key. #X album_replay_gain The album's replay-gain. #need at least j tag to find associated cover in cache count, items, error = self.server.request_with_results( 'albums 0 %d tags:lj' % self.__albums_count) if error: return None else: return items def get_album(self, id): """return album infos""" if id != None: count, items, error = self.server.request_with_results( 'albums 0 1 album_id:%d tags:ljyS' % id) if error: return None else: return items else: return None def get_album_songs(self, id): """return all songs from specified album id""" # rescan Returned with value 1 if the server is still scanning the database. The results may therefore be incomplete. Not returned if no scan is in progress. # count Number of results returned by the query, that is, total number of elements to return for this song. # id Track ID. # title Song title #a artist Artist name. #A <role> For every artist role (one of "artist", "composer", "conductor", "band", "albumartist" or "trackartist"), a comma separated list of names. #B buttons A hash with button definitions. Only available for certain plugins such as Pandora. #c coverid coverid to use when constructing an artwork URL, such as /music/$coverid/cover.jpg #C compilation 1 if the album this track belongs to is a compilation #d duration Song duration in seconds. #e album_id Album ID. Only if known. #f filesize Song file length in bytes. Only if known. #g genre Genre name. Only if known. #G genres Genre names, separated by commas (only useful if the server is set to handle multiple items in tags). #i disc Disc number. Only if known. #I samplesize Song sample size (in bits) #j coverart 1 if coverart is available for this song. Not listed otherwise. #J artwork_track_id Identifier of the album track used by the server to display the album's artwork. Not listed if artwork is not available for this album. #k comment Song comments, if any. #K artwork_url A full URL to remote artwork. Only available for certain plugins such as Pandora and Rhapsody. #l album Album name. Only if known. #L info_link A custom link to use for trackinfo. Only available for certain plugins such as Pandora. #m bpm Beats per minute. Only if known. #M musicmagic_mixable 1 if track is mixable, otherwise 0. #n modificationTime Date and time song file was last changed on disk. #N remote_title Title of the internet radio station. #o type Content type. Only if known. #p genre_id Genre ID. Only if known. #P genre_ids Genre IDs, separated by commas (only useful if the server is set to handle multiple items in tags). #D addedTime Date and time song file was first added to the database. #U lastUpdated Date and time song file was last updated in the database. #q disccount Number of discs. Only if known. #r bitrate Song bitrate. Only if known. #R rating Song rating, if known and greater than 0. #s artist_id Artist ID. #S <role>_ids For each role as defined above, the list of ids. #t tracknum Track number. Only if known. #T samplerate Song sample rate (in KHz) #u url Song file url. #v tagversion Version of tag information in song file. Only if known. #w lyrics Lyrics. Only if known. #x remote If 1, this is a remote track. #X album_replay_gain Replay gain of the album (in dB), if any #y year Song year. Only if known. #Y replay_gain Replay gain (in dB), if any if id != None: count, items, error = self.server.request_with_results( 'songs 0 200 album_id:%deJ' % id) if error: return None else: return items else: return None def get_artists(self): """return all artists""" # id Artist ID. Item delimiter. # artist Artist name. #s textkey The artist's "textkey" is the first letter of the sorting key. count, items, error = self.server.request_with_results( 'artists 0 %d' % self.__artists_count) if error: return None else: return items def get_artist(self, id): """return artist infos""" if id != None: count, items, error = self.server.request_with_results( 'artists 0 1 artist_id:%d' % id) if error: return None else: return items else: return None def get_artist_albums(self, id): """return albums from specified artist id""" if id != None: count, items, error = self.server.request_with_results( 'albums 0 %d artist_id:%d tags:ljyS' % (self.__albums_count, id)) if error: return None else: return items else: return None def get_genres(self): """return all genres""" count, items, error = self.server.request_with_results( 'genres 0 %d' % self.__genres_count) if error: return None else: return items def get_genre(self, id): """return genre infos""" if id != None: count, items, error = self.server.request_with_results( 'genre 0 1 genre_id:%d' % id) if error: return None else: return items else: return None def get_genre_albums(self, id): """return albums from specified genre id""" if id != None: count, items, error = self.server.request_with_results( 'albums 0 %d genre_id:%d tags:ljyS' % (self.__albums_count, id)) if error: return None else: return items else: return None def get_years(self): """return all years""" count, items, error = self.server.request_with_results( 'years 0 %d' % self.__years_count) if error: return None else: return items def get_year_albums(self, id): """return albums from specified year id""" if id != None: count, items, error = self.server.request_with_results( 'albums 0 %d year:%d tags:ljyS' % (self.__albums_count, id)) if error: return None else: return items else: return None def get_song_infos(self, id): """return full song infos""" if id != None: count, items, error = self.server.request_with_results( 'songinfo 0 50 track_id:%d tags:adefgIJlnortTuvyY' % id) if not error and count == 1: return items[0] else: return None else: return None def get_song_infos_by_url(self, url): """return full song infos""" if id != None: count, items, error = self.server.request_with_results( 'songinfo 0 50 url:%s tags:adefgIJlnortTuvyY' % url) self.logger.debug('count=%d' % count) self.logger.debug('items=%s' % str(items)) self.logger.debug('error=%s' % str(error)) if not error and count == 1: return items[0] else: return None else: return None def get_cover_path(self, album_id, artwork_track_id): """return cover""" cover_path = None if album_id and artwork_track_id: cover_path = os.path.join( self.__cover_path, '%s_%s.png' % (album_id, artwork_track_id)) self.logger.debug('Cover path:%s' % cover_path) if not os.path.exists(cover_path): #cover doesn't exists self.logger.debug('Cover doesn\'t exist!') cover_path = None return cover_path def search(self, term): """search something on database""" #TODO pass def check_update(self): """check if library needs update, return True if database needs update followed by number of albums, artists, and genres""" #get stats lms_genres_count = int(self.server.request('info total genres ?')) self.__genres_count = lms_genres_count lms_artists_count = int(self.server.request('info total artists ?')) self.__artists_count = lms_artists_count lms_albums_count = int(self.server.request('info total albums ?')) self.__albums_count = lms_albums_count self.logger.debug( 'LMS total artists=%d albums=%d genres=%d' % (lms_artists_count, lms_albums_count, lms_genres_count)) #check if fresh install if not os.path.exists(self.__server_infos_path): #conf file doesn't exist create empty one conf = open(self.__server_infos_path, 'w') conf.write('albums:0\nartists:0\ngenres:0') local_genres_count = 0 local_artists_count = 0 local_albums_count = 0 else: #read from file conf = open(self.__server_infos_path, 'r') for line in conf.readlines(): if line.startswith('artists'): try: local_artists_count = int(line.split(':')[1].strip()) except: local_artists_count = 0 elif line.startswith('albums'): try: local_albums_count = int(line.split(':')[1].strip()) except: local_albums_count = 0 elif line.startswith('genres'): try: local_genres_count = int(line.split(':')[1].strip()) except: local_genres_count = 0 #compare results self.logger.debug( '%d==%d %d==%d %d==%d' % (lms_genres_count, local_genres_count, lms_artists_count, local_artists_count, lms_albums_count, local_albums_count)) if lms_genres_count != local_genres_count or lms_artists_count != local_artists_count or lms_albums_count != local_albums_count: #need update self.logger.debug('Need update') self.__cache_covers(lms_albums_count) def __cache_covers(self, albums_count): """cache covers for thumbnails""" count, albums, error = self.server.request_with_results( 'albums 0 %d tags:j' % albums_count) if not error: self.logger.debug('Updating...') self.cache_covers = CacheCovers(self.server_ip, self.server_port, self.__cover_path, albums) self.cache_covers.start() else: #error self.logger.error('Unable to get albums list')