コード例 #1
0
ファイル: pylmsplaylist.py プロジェクト: tangb/PyLMS
    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
コード例 #2
0
ファイル: pylmslibrary.py プロジェクト: tangb/PyLMS
    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
コード例 #3
0
ファイル: pylmslibrary.py プロジェクト: mce35/agocontrol
 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
コード例 #4
0
    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
コード例 #5
0
ファイル: pylmsplaylist.py プロジェクト: mce35/agocontrol
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
コード例 #6
0
ファイル: pylmslibrary.py プロジェクト: mce35/agocontrol
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')
コード例 #7
0
ファイル: pylmsplaylist.py プロジェクト: tangb/PyLMS
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
コード例 #8
0
ファイル: pylmslibrary.py プロジェクト: tangb/PyLMS
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')