#!/usr/bin/python # coding: UTF-8 # musicdata service to read from LMS # Written by: Ron Ritchey from __future__ import unicode_literals import json, threading, logging, Queue, time, sys, urllib, pylms, getopt, telnetlib, urlparse from pylms import server import musicdata class musicdata_lms(musicdata.musicdata): def __init__(self, q, server=u'localhost', port=9090, user=u'', pwd=u'', player=u''): super(musicdata_lms, self).__init__(q) self.server = server self.port = port self.user = user self.pwd = pwd self.player = player self.connection_failed = 0 self.timeout = 20 self.idle_state = False self.dataserver = None self.dataplayer = None self.rawserver = None # Now set up a thread to listen to the channel and update our data when # the channel indicates a relevant key has changed data_t = threading.Thread(target=self.run) data_t.daemon = True data_t.start() # Start the idle timer # idle_t = threading.Thread(target=self.idlealert) # idle_t.daemon = True # idle_t.start() # def idlealert(self): # # while True: # # Generate a noidle event every timeout seconds # time.sleep(self.timeout) # # if self.idle_state: # try: # #self.dataclient.noidle() # self.rawclient.write("noidle\n") # except (NameError, IOError, AttributeError): # # If not idle (or not created yet) return to sleeping # pass def connectraw(self): # Try up to 10 times to connect to LMS connection_failed = 0 self.rawserver = None logging.debug(u"Connecting to LMS raw service on {0}:{1}".format(self.server, self.port)) while True: if connection_failed >= 10: logging.debug(u"Could not connect to raw LMS service") break try: # Connection to LMS self.rawserver = telnetlib.Telnet(self.server, self.port) # Subscribe to notification events that should wake up the system to collect data self.rawserver.write("subscribe pause,play,mixer,playlist\n".encode('ascii')) break except IOError: self.rawserver = None connection_failed += 1 time.sleep(1) if self.rawserver is None: raise IOError(u"Could not connect raw to LMS") def connect(self): # Try up to 10 times to connect to LMS self.connection_failed = 0 self.dataserver = None self.dataplayer = None logging.debug(u"Connecting to LMS service on {0}:{1}".format(self.server, self.port)) while True: if self.connection_failed >= 10: logging.debug(u"Could not connect to LMS service") break try: # Connection to LMS self.dataserver = pylms.server.Server(self.server, self.port, self.user, self.pwd) self.dataserver.connect() players = self.dataserver.get_players() for p in players: if p.get_ref() == self.player: self.dataplayer = p break if self.dataplayer is None: if len(self.dataserver.get_players()) > 0: self.dataplayer = self.dataserver.get_players()[0] if self.dataplayer is None: logging.critical(u"Could not find any LMS Players") raise RuntimeError(u"Could not find any LMS Players") self.player = str(self.dataplayer) break except (IOError, AttributeError, IndexError): ### Trying to debug services logging.error(u"LMS connect failure", exc_info=sys.exc_info()) self.dataserver = None self.dataplayer = None self.connection_failed += 1 time.sleep(1) if self.dataserver is None: ### Trying to debug services logging.error(u"LMS dataserver is None", exc_info=sys.exc_info()) raise IOError(u"Could not connect to LMS") else: logging.debug(u"Connected to LMS using player {0}".format(self.dataplayer.get_name())) def run(self): logging.debug(u"LMS musicdata service starting") while True: # Connect to the data service if needed if self.dataserver is None: try: # Try to connect self.connect() self.status() self.sendUpdate() except IOError: self.dataserver = None # On connection error, sleep 5 and then return to top and try again time.sleep(5) continue # Connect directly to the data service (for notifications) if needed if self.rawserver is None: try: # Try to connect self.connectraw() except IOError: self.rawserver = None # On connection error, sleep 5 and then return to top and try again time.sleep(5) continue try: # Wait for notice that state has changed try: #self.idle_state = True msg = self.rawserver.read_until("\n", self.timeout) #self.idle_state = False self.status() self.sendUpdate() except (IOError, EOFError): # Error occurred while trying to read from rawserver # Mark rawserver None so that it is restarted on the next pass self.rawserver = None pass time.sleep(.01) except IOError: self.dataserver = None logging.debug(u"Could not get status from LMS") time.sleep(5) continue def status(self): # Read musicplayer status and update musicdata state = self.dataplayer.get_mode() if state != u"play": self.musicdata[u'state'] = u"stop" else: self.musicdata[u'state'] = u"play" # Update values self.musicdata[u'artist'] = urllib.unquote(str(self.dataplayer.request("artist ?", True))).decode('utf-8') self.musicdata[u'title'] = urllib.unquote(str(self.dataplayer.request("title ?", True))).decode('utf-8') self.musicdata[u'album'] = urllib.unquote(str(self.dataplayer.request("album ?", True))).decode('utf-8') self.musicdata[u'volume'] = self.dataplayer.get_volume() self.musicdata[u'elapsed'] = int(self.dataplayer.get_time_elapsed()) try: self.musicdata[u'length'] = int(self.dataplayer.get_track_duration()) except: self.musicdata[u'length'] = 0 # For backwards compatibility self.musicdata[u'current'] = self.musicdata[u'elapsed'] self.musicdata[u'duration'] = self.musicdata[u'length'] playlist_mode = int(self.dataplayer.request("playlist repeat ?", True)) if playlist_mode == 0: self.musicdata[u'single'] = self.musicdata[u'repeat'] = False elif playlist_mode == 1: self.musicdata[u'single'] = True self.musicdata[u'repeat'] = False elif playlist_mode == 2: self.musicdata[u'single'] = False self.musicdata[u'repeat'] = True else: logging.debug(u"Unexpected value received when querying playlist mode status (e.g. single, repeat)") self.musicdata[u'single'] = self.musicdata[u'repeat'] = False shuffle_mode = int(self.dataplayer.request("playlist shuffle ?", True)) if shuffle_mode == 0: self.musicdata[u'random'] = False elif shuffle_mode == 1 or shuffle_mode == 2: self.musicdata[u'random'] = True else: logging.debug(u"Unexpected value received when querying playlist shuffle status") self.musicdata[u'random'] = False plp = self.musicdata[u'playlist_position'] = int(self.dataplayer.request("playlist index ?"))+1 plc = self.musicdata[u'playlist_length'] = self.dataplayer.playlist_track_count() # For backwards compatibility self.musicdata[u'playlist_count'] = self.musicdata[u'playlist_length'] playlist_display = u"{0}/{1}".format(plp, plc) # If the track count is greater than 1, we are playing from a playlist and can display track position and track count if plc > 1: playlist_display = u"{0}/{1}".format(plp, plc) self.musicdata[u'stream'] = u'not webradio' # if the track count is exactly 1, this is either a short playlist or it is streaming elif plc == 1: try: # if streaming if self.musicdata['length'] == 0.0: playlist_display = u"Radio" self.musicdata[u'stream'] = u'webradio' # it really is a short playlist else: playlist_display = u"{0}/{1}".format(plp, plc) self.musicdata[u'stream'] = u'not webradio' except KeyError: logging.debug(u"In LMS couldn't get valid track information") playlist_display = u"" self.musicdata[u'stream'] = u'not webradio' else: logging.debug(u"In LMS track length is <= 0") playlist_display = u"" self.musicdata[u'stream'] = u'' self.musicdata[u'playlist_display'] = playlist_display self.musicdata[u'musicdatasource'] = u"LMS" url = self.dataplayer.get_track_path().decode() self.musicdata[u'uri'] = url urlp = urlparse.urlparse(url) if urlp.scheme.lower() == u'wimp': self.musicdata[u'actPlayer'] = u'tidal' elif urlp.scheme.lower() == u'http': # Extract out domain name try: self.musicdata[u'actPlayer'] = urlp.netloc.split(u'.')[len(urlp.netloc.split(u'.'))-2] except IndexError: self.musicdata[u'actPlayer'] = urlp.netloc else: self.musicdata[u'actPlayer'] = urlp.scheme # Get bitrate and tracktype if they are available. Try blocks used to prevent array out of bounds exception if values are not found try: self.musicdata[u'bitrate'] = urllib.unquote(str(self.dataplayer.request("songinfo 2 1 url:"+url+" tags:r", True))).decode(u'utf-8').split(u"bitrate:", 1)[1] except: self.musicdata[u'bitrate'] = u"" try: self.musicdata[u'encoding'] = urllib.unquote(str(self.dataplayer.request("songinfo 2 1 url:"+url+" tags:o", True))).decode(u'utf-8').split(u"type:",1)[1] except: self.musicdata[u'encoding'] = u"" self.musicdata[u'tracktype'] = self.musicdata[u'encoding'] # if duration is not available, then suppress its display if int(self.musicdata[u'length']) > 0: timepos = time.strftime(u"%-M:%S", time.gmtime(int(self.musicdata[u'elapsed']))) + "/" + time.strftime("%-M:%S", time.gmtime(int(self.musicdata[u'length']))) remaining = time.strftime(u"%-M:%S", time.gmtime( int(self.musicdata[u'length']) - int(self.musicdata[u'elapsed']) ) ) else: timepos = time.strftime(u"%-M:%S", time.gmtime(int(self.musicdata[u'current']))) remaining = timepos self.musicdata[u'remaining'] = remaining.decode() self.musicdata[u'elapsed_formatted'] = timepos.decode() # For backwards compatibility self.musicdata[u'position'] = self.musicdata[u'elapsed_formatted'] # UNSUPPORTED VARIABLES self.musicdata[u'bitdepth'] = u"" self.musicdata[u'samplerate'] = u"" self.musicdata[u'channels'] = 0 self.validatemusicvars(self.musicdata) if __name__ == u'__main__': logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', filename=u'musicdata_lms.log', level=logging.DEBUG) logging.getLogger().addHandler(logging.StreamHandler()) try: opts, args = getopt.getopt(sys.argv[1:],u"hs:p:u:w:l:",[u"server=",u"port=",u"user=",u"pwd=",u"player="]) except getopt.GetoptError: print u'musicdata_lms.py -s <server> -p <port> -u <user> -w <password> -l <player>' sys.exit(2) # Set defaults server = u'localhost' port = 9090 user = '' pwd= u'' player = u'' for opt, arg in opts: if opt == u'-h': print u'musicdata_lms.py -s <server> -p <port> -u <user> -w <password> -l <player>' sys.exit() elif opt in (u"-s", u"--server"): server = arg elif opt in (u"-p", u"--port"): port = arg elif opt in (u"-u", u"--user"): user = arg elif opt in (u"-w", u"--pwd"): pwd = arg elif opt in (u"-l", u"--player"): player = arg import sys q = Queue.Queue() mdr = musicdata_lms(q, server, port, user, pwd, player) try: start = time.time() while True: if start+120 < time.time(): break; try: item = q.get(timeout=1000) print u"+++++++++" for k,v in item.iteritems(): print u"[{0}] '{1}' type {2}".format(k,v,type(v)) print u"+++++++++" print q.task_done() except Queue.Empty: pass except KeyboardInterrupt: print u'' pass print u"Exiting..."