Thank you for your donation!


Alternative renderers and metadata
#11
What is this Python script you refer to? Examining it would be the first step in a reverse-engineering process to find out what LMS API is being accessed or what stream decoding method is being used to capture the metadata of the currently playing track. I still haven't found either approach documented on the InterWeb™, which leaves me dubious.

Regards,
Kent
Reply
#12
(06-03-2020, 03:53 PM)TheOldPresbyope Wrote: What is this Python script you refer to? Examining it would be the first step in a reverse-engineering process to find out what LMS API is being accessed or what stream decoding method is being used to capture the metadata of the currently playing track. I still haven't found either approach documented on the InterWeb™, which leaves me dubious.

Regards,
Kent

Try to attach the .py file but with no success. Anyway here's the  portions of code that create the connection with the LMS Server, and the one able to retrieve from it the music info 

1) Constants and initial comments

Code:
LMS_ENABLED = False
LMS_SERVER = "localhost"
LMS_PORT = 9090
LMS_USER = ""
LMS_PASSWORD = ""

# Set this to MAC address of the Player you want to monitor.
# THis should be the MAC of the RaspDac system if using Max2Play with SqueezePlayer
# Note: if you have another Logitech Media Server running in your network, it is entirely
#       possible that your player has decided to join it, instead of the LMS on Max2Play
#       To fix this, go to the SqueezeServer interface and change move the player to the
#       correct server.
LMS_PLAYER = "00:01:02:aa:bb:cc"

Note: don't ask me the meaning of the above comment, is quite obscured for me....

2 ) Establishing Connection:
Code:
        if LMS_ENABLED:
            for i in range (1,ATTEMPTS):
                try:
                    # Connect to the LMS daemon
                    self.lmsserver = pylms.server.Server(LMS_SERVER, LMS_PORT, LMS_USER, LMS_PASSWORD)
                    self.lmsserver.connect()

                    # Find correct player
                    players = self.lmsserver.get_players()
                    for p in players:
                        ### Need to find out how to get the MAC address from player
                        if p.get_ref().lower() == LMS_PLAYER.lower():
                            self.lmsplayer = p
                            break
                    if self.lmsplayer is None:
                        self.lmsplayer = self.lmsserver.get_players()[0]
                        if self.lmsplayer is None:
                            raise Exception('Could not find any LMS player')
                    break
                except (socket_error, AttributeError, IndexError):
                    logging.debug("Connect attempt {0} to LMS server failed".format(i))
                    time.sleep(2)
            else:
                # After the alloted number of attempts did not succeed in connecting
                logging.warning("Unable to connect to LMS service on startup")

3 - Retrieving info from LMS Server

Code:
    def status_lms(self):
        # Try to get status from LMS daemon

        try:
            lms_status = self.lmsplayer.get_mode()
        except:
            # Try to reestablish connection to daemon
            try:
                self.lmsserver = pylms.server.Server(LMS_SERVER, LMS_PORT, LMS_USER, LMS_PASSWORD)
                self.lmsserver.connect()

                # Find correct player
                players = self.lmsserver.get_players()
                for p in players:
                    ### Need to find out how to get the MAC address from player
                    if p.get_ref().lower() == LMS_PLAYER.lower():
                        self.lmsplayer = p
                        break
                if self.lmsplayer is None:
                    self.lmsplayer = self.lmsserver.get_players()[0]
                    if self.lmsplayer is None:
                        raise Exception('Could not find any LMS player')

                lms_status = self.lmsplayer.get_mode()
            except (socket_error, AttributeError, IndexError):
                logging.debug("Could not get status from LMS daemon")
                return { 'state':u"stop", 'artist':u"", 'title':u"", 'album':u"", 'remaining':u"", 'current':0, 'duration':0, 'position':u"", 'volume':0, 'playlist_display':u"", 'playlist_position':0, 'playlist_count':0, 'bitrate':u"", 'type':u"", 'current_time':u""}


          if lms_status == "play":
            import urllib

            artist = urllib.unquote(str(self.lmsplayer.request("artist ?", True))).decode('utf-8')
            title = urllib.unquote(str(self.lmsplayer.request("title ?", True))).decode('utf-8')
            album = urllib.unquote(str(self.lmsplayer.request("album ?", True))).decode('utf-8')
            playlist_position = int(self.lmsplayer.request("playlist index ?"))+1
            playlist_count = self.lmsplayer.playlist_track_count()
            volume = self.lmsplayer.get_volume()
            current = self.lmsplayer.get_time_elapsed()
            duration = self.lmsplayer.get_track_duration()
            url = self.lmsplayer.get_track_path()

            # Get bitrate and tracktype if they are available.  Try blocks used to prevent array out of bounds exception if values are not found
            try:
                bitrate = urllib.unquote(str(self.lmsplayer.request("songinfo 2 1 url:"+url+" tags:r", True))).decode('utf-8').split("bitrate:", 1)[1]
            except:
                bitrate = u""

            try:
                tracktype = urllib.unquote(str(self.lmsplayer.request("songinfo 2 1 url:"+url+" tags:o", True))).decode('utf-8').split("type:",1)[1]
            except:
                tracktype = u""

            playlist_display = "{0}/{1}".format(playlist_position, playlist_count)
            # If the track count is greater than 1, we are playing from a playlist and can display track position and track count
            if self.lmsplayer.playlist_track_count() > 1:
                playlist_display = "{0}/{1}".format(playlist_position, playlist_count)
            # if the track count is exactly 1, this is either a short playlist or it is streaming
            elif self.lmsplayer.playlist_track_count() == 1:
                try:
                    # if streaming
                    if self.lmsplayer.playlist_get_info()[0]['duration'] == 0.0:
                        playlist_display = "Streaming"
                    # it really is a short playlist
                    else:
                        playlist_display = "{0}/{1}".format(playlist_position, playlist_count)
                except KeyError:
                    logging.debug("In LMS couldn't get valid track information")
                    playlist_display = u""
            else:
                logging.debug("In LMS track length is <= 0")
                playlist_display = u""

              # since we are returning the info as a JSON formatted return, convert
              # any None's into reasonable values

            if artist is None: artist = u""
            if title is None: title = u""
            if album is None: album = u""
            if current is None: current = 0
            if volume is None: volume = 0
            if bitrate is None: bitrate = u""
            if tracktype is None: tracktype = u""
            if duration is None: duration = 0

            # if duration is not available, then suppress its display
            if int(duration) > 0:
                timepos = time.strftime("%M:%S", time.gmtime(int(current))) + "/" + time.strftime("%M:%S", time.gmtime(int(duration)))
                remaining = time.strftime("%M:%S", time.gmtime(int(duration) - int(current) ) )

            else:
                timepos = time.strftime("%M:%S", time.gmtime(int(current)))
                remaining = timepos

            return { 'state':u"play", 'artist':artist, 'title':title, 'album':album, 'remaining':remaining, 'current':current, 'duration':duration, 'position':timepos, 'volume':volume, 'playlist_display':playlist_display,'playlist_position':playlist_position, 'playlist_count':playlist_count, 'bitrate':bitrate, 'type':tracktype }
          else:
            return { 'state':u"stop", 'artist':u"", 'title':u"", 'album':u"", 'remaining':u"", 'current':0, 'duration':0, 'position':u"", 'volume':0, 'playlist_display':u"", 'playlist_position':0, 'playlist_count':0, 'bitrate':u"", 'type':u""}

The returned state object is then passed to the rendering part of the script, that uses it to send info to the OLED display

Hope this helps the discussion

Andrea
Reply
#13
Cool. Thanks.

So, searching the InterWEB™ on the class name pylms I infer this script is using jinglemansweep's PyLMS module.

Ok. I see what he's doing. Basically, as a third party, asking LMS what is the current album, artist, track you are sending to a specified player, e.g., using a LMS API.

jinglemansweep refers to a 4-year old document describing the HTTP API on a french website http://tutoriels.domotique-store.fr/cont...-http.html

Don't know why I didn't run across this before (or why the API is not documented in more official places) but that's the breaks.

Regards,
Kent
Reply
#14
Here's the original project on GitHub , it seems to come from Audiophonics...

https://github.com/dhrone/Raspdac-Display
Reply


Forum Jump: