Пример #1
0
    def parse_message(self, message):
        try:
            data = json.loads(message)
            logging.debug(data)
            if "metadata" in data:
                logging.error(data["metadata"])
                md = Metadata()
                map_attributes(data["metadata"], md.__dict__,
                               VOLSPOTIFY_ATTRIBUTE_MAP)
                md.artUrl = self.cover_url(data["metadata"]["albumartId"])
                md.playerName = MYNAME
                self.control.metadata = md
            elif "position_ms" in data:
                pos = float(data["position_ms"]) / 1000
                self.control.metadata.set_position(pos)
            elif "volume" in data:
                logging.debug("ignoring volume data")
            elif "token" in data:
                logging.info("got access_token update")
                self.control.access_token = data["token"]
            else:
                logging.warn("don't know how to handle %s", data)

        except Exception as e:
            logging.error("error while parsing %s (%s)", message, e)
    def test_get_cover(self):
        md = Metadata()
        # A Rush of Blood to the Head, Coldplay
        md.artist = "Coldplay"  # Necessary as unknown song won't be retrieved
        md.albummbid = "219b202d-290e-3960-b626-bf852a63bc50"
        self.assertIsNone(md.artUrl)
        self.assertIsNone(md.externalArtUrl)

        coverartarchive.enrich_metadata(md)

        self.assertIsNone(md.artUrl)
        self.assertIsNotNone(md.externalArtUrl)
Пример #3
0
    def test_same_song(self):
        md1=Metadata("artist1","song1")
        md2=Metadata("artist1","song1", albumTitle="album1")
        md3=Metadata("artist1","song1", albumTitle="album2")
        md4=Metadata("artist2","song1")
        md5=Metadata("","song1")
        
        self.assertTrue(md1.sameSong(md2))
        self.assertTrue(md1.sameSong(md3))
        self.assertTrue(md2.sameSong(md3))

        self.assertFalse(md1.sameSong(md4))
        self.assertFalse(md1.sameSong(md5))
Пример #4
0
    def get_meta(self):
        state = self.get_state()

        song = None
        if state in [STATE_PLAYING, STATE_PAUSED]:
            song = self.client.currentsong()

        md = Metadata()
        md.playerName = "mpd"

        if song is not None:
            map_attributes(song, md.__dict__, MPD_ATTRIBUTE_MAP)

        return md
Пример #5
0
 def __init__(self, auto_pause=True, loop_delay=1, ignore_players=[]):
     self.state_table = {}
     self.auto_pause = auto_pause
     self.metadata_displays = []
     self.last_update = None
     self.loop_delay = loop_delay
     self.active_player = None
     self.ignore_players = ignore_players
     self.metadata = {}
     self.playing = False
     self.connect_dbus()
     self.metadata = Metadata()
     self.metadata_lock = threading.Lock()
     self.volume_control = None
Пример #6
0
def demo():
    from ac2.metadata import Metadata

    lp = LaMetricPush()
    time.sleep(15)
    md = Metadata(artist="demo artist", title="demo title")
    lp.notify(md)
Пример #7
0
    def test_enrich(self):
        # We should be able to get some metadata for this one
        md = Metadata("Bruce Springsteen", "The River")
        lastfm.enrich_metadata(md)

        self.assertIsNotNone(md.externalArtUrl)
        self.assertIsNotNone(md.mbid)
        self.assertIsNotNone(md.artistmbid)
Пример #8
0
 def __init__(self, state="unknown", metadata=None, failed=0):
     self.state = state
     self.failed = failed
     if metadata is not None:
         self.metadata = metadata
     else:
         self.metadata = Metadata()
     self.supported_commands = []
Пример #9
0
    def test_get_cover(self):
        md = Metadata()
        # A Rush of Blood to the Head, Coldplay
        md.artist = "Coldplay"
        md.mbid = "58b961e1-a2ef-4e92-a82b-199b15bb3cd8"
        md.albummbid = "219b202d-290e-3960-b626-bf852a63bc50"
        self.assertIsNone(md.artUrl)
        self.assertIsNone(md.externalArtUrl)

        hifiberry.enrich_metadata(md)
        # Cover might be be in cache at the HiFiBerry musicdb,
        # in this case try again a few seconds later
        if md.externalArtUrl is None:
            sleep(5)
            hifiberry.enrich_metadata(md)

        self.assertIsNone(md.artUrl)
        self.assertIsNotNone(md.externalArtUrl)
Пример #10
0
 def test_enrich(self):
     # We should be able to get some metadata for this one
     md=Metadata("Bruce Springsteen","The River")
     self.md_updated = False
     self.updates = None
     song_id = md.songId()
     self.song_id = None
     
     self.assertIsNone(md.artUrl)
     self.assertIsNone(md.externalArtUrl)
     self.assertFalse(MetaDataTest.md_updated)
     enrich_metadata(md, callback=self)
     
     self.assertIsNotNone(md.externalArtUrl)       
     self.assertIsNotNone(md.mbid)
     self.assertIsNotNone(self.updates)
     self.assertIn("externalArtUrl", self.updates)
     self.assertIn("mbid",self.updates)
     self.assertIn("artistmbid",self.updates)
     self.assertIn("albummbid",self.updates)
     self.assertEqual(self.song_id, song_id)
Пример #11
0
 def test_tags(self):
     
     md1=Metadata("artist1","song1")
     md1.add_tag("tag1")
     md1.add_tag("tag2")
     md1.add_tag("tag3")
     
     self.assertIn("tag1", md1.tags)
     self.assertIn("tag2", md1.tags)
     self.assertIn("tag3", md1.tags)
Пример #12
0
    def __init__(self, port=80, host='0.0.0.0', authtoken=None, debug=False):
        super().__init__()
        self.port = port
        self.host = host
        self.debug = debug
        self.authtoken = authtoken
        self.bottle = Bottle()
        self.route()
        self.system_control = SystemControl()
        self.player_control = None
        self.lastfm_network = None
        self.volume_control = None
        self.volume = 0
        self.thread = None
        self.lovers = []
        self.updaters = []
        self.artwork = ExpiringDict(max_len=100, max_age_seconds=36000000)

        self.notify(Metadata("Artist", "Title", "Album"))
Пример #13
0
    def __init__(self, args={}):
        self.client = None
        self.playername = MYNAME
        self.state = STATE_STOPPED
        self.metadata = Metadata()

        if "port" in args:
            self.port = args["port"]
        else:
            self.port = 5030

        if "host" in args:
            self.host = args["host"]
        else:
            self.host = "localhost"

        self.lastupdated = 0
        self.tokenupdated = 0
        self.token = None
        self.access_token = None
Пример #14
0
 def test_init(self):
     md = Metadata(
         artist="artist", 
         title="title",
         albumArtist="albumartist", 
         albumTitle="albumtitle",
         artUrl="http://test",
         discNumber=1, 
         trackNumber=2,
         playerName="player", 
         playerState="unknown",
         streamUrl="http://stream")
     self.assertEqual(md.artist, "artist")
     self.assertEqual(md.title, "title")
     self.assertEqual(md.albumArtist, "albumartist")
     self.assertEqual(md.albumTitle, "albumtitle")
     self.assertEqual(md.artUrl, "http://test")
     self.assertEqual(md.externalArtUrl, None)
     self.assertEqual(md.discNumber, 1)
     self.assertEqual(md.tracknumber, 2)
     self.assertEqual(md.playerName, "player")
     self.assertEqual(md.playerState, "unknown")
Пример #15
0
    def test_same_artwork(self):
        md1=Metadata("artist1","song1")
        md1.artUrl = "http://art1"

        md2=Metadata("artist1","song1")
        md2.artUrl = "http://art1"
        md2.externalArtUrl = "http://art2"
        
        md3=Metadata("artist1","song1")
        md3.artUrl = "http://art3"
        md3.externalArtUrl = "http://art1"
        
        self.assertTrue(md1.sameArtwork(md1))
        self.assertTrue(md1.sameArtwork(md2))
        self.assertFalse(md1.sameArtwork(md3))
        self.assertFalse(md2.sameArtwork(md3))
Пример #16
0
 def test_guess(self):
     md=Metadata("","Bruce Springsteen - The River")
     md.fix_problems(guess=True)
     
     self.assertEqual(md.artist,"Bruce Springsteen")
     self.assertEqual(md.title,"The River")
     
     md=Metadata("","The River - Bruce Springsteen")
     md.fix_problems(guess=True)
     
     self.assertEqual(md.artist,"Bruce Springsteen")
     self.assertEqual(md.title,"The River")
     
     md=Metadata("","Michael Kiwanuka - You Ain't The Problem")
     md.fix_problems(guess=True)
     
     self.assertEqual(md.artist,"Michael Kiwanuka")
     self.assertEqual(md.title,"You Ain't The Problem")
Пример #17
0
 def test_unknown(self):
     
     md1=Metadata()
     md2=Metadata("","")
     md3=Metadata("None","None")
     md4=Metadata("unknown artist","unknown title")
     md5=Metadata("unknown","unknown")
     md6=Metadata("artist","")
     md7=Metadata(None,"name")
     md8=Metadata("Unknown","song")
     md9=Metadata("artist","unknown")
     md10=Metadata("artist","unknown song")
     md11=Metadata("artist","songs")
     
     self.assertTrue(md1.is_unknown())
     self.assertTrue(md2.is_unknown())
     self.assertTrue(md3.is_unknown())
     self.assertTrue(md4.is_unknown())
     self.assertTrue(md5.is_unknown())
     self.assertTrue(md6.is_unknown())
     self.assertTrue(md7.is_unknown())
     self.assertTrue(md8.is_unknown())
     self.assertTrue(md9.is_unknown())
     self.assertTrue(md10.is_unknown())
     self.assertFalse(md11.is_unknown())
Пример #18
0
    def get_meta(self, name):
        """
        Return the metadata for the given player instance
        """
        try:
            device_prop = self.dbus_get_device_prop_interface(name)
            prop = device_prop.Get(
                "org.mpris.MediaPlayer2.Player", "Metadata")
            try:
                artist = array_to_string(prop.get("xesam:artist"))
            except:
                artist = None

            try:
                title = prop.get("xesam:title")
            except:
                title = None

            try:
                albumArtist = array_to_string(prop.get("xesam:albumArtist"))
            except:
                albumArtist = None

            try:
                albumTitle = prop.get("xesam:album")
            except:
                albumTitle = None

            try:
                artURL = prop.get("mpris:artUrl")
            except:
                artURL = None

            try:
                discNumber = prop.get("xesam:discNumber")
            except:
                discNumber = None

            try:
                trackNumber = prop.get("xesam:trackNumber")
            except:
                trackNumber = None

            md = Metadata(artist, title, albumArtist, albumTitle,
              artURL, discNumber, trackNumber)

            try:
                md.streamUrl = prop.get("xesam:url")
            except:
                pass

            try:
                md.trackId = prop.get("mpris:trackid")
            except:
                pass


            if (name.startswith(MPRIS_PREFIX)):
                md.playerName = name[len(MPRIS_PREFIX):]
            else:
                md.playerName = name

            return md

        except dbus.exceptions.DBusException as e:
            if "ServiceUnknown" in e.__class__.__name__:
                # unfortunately we can't do anything about this and
                # logging doesn't help, therefore just ignoring this case
                pass
                #  logging.warning("service %s disappered, cleaning up", e)
            else:
                logging.warning("no mpris data received %s", e.__class__.__name__)

            md = Metadata()
            md.playerName = self.playername(name)
            return md
    def test_unknown(self):
        md = Metadata()
        coverartarchive.enrich_metadata(md)

        self.assertIsNone(md.artUrl)
        self.assertIsNone(md.externalArtUrl)
Пример #20
0
    def main_loop(self):
        """
        Main loop:
        - monitors state of all players
        - pauses players if a new player starts playback
        """

        finished = False
        md = Metadata()
        active_players = []

        MAX_FAIL = 3

        # Workaround for spotifyd problems
        #        spotify_stopped = 0

        # Workaround for squeezelite mute
        squeezelite_active = 0

        previous_state = ""
        ts = datetime.datetime.now()

        while not (finished):
            additional_delay = 0
            new_player_started = None
            metadata_notified = False
            playing = False
            new_song = False
            state = "unknown"
            last_ts = ts
            ts = datetime.datetime.now()
            duration = (ts - last_ts).total_seconds()

            for p in self.all_players():

                if self.playername(p) in self.ignore_players:
                    continue

                if p not in self.state_table:
                    ps = PlayerState()
                    ps.supported_commands = self.get_supported_commands(p)
                    logging.debug("Player %s supports %s", p,
                                  ps.supported_commands)
                    self.state_table[p] = ps

                thisplayer_state = "unknown"
                try:
                    thisplayer_state = self.get_player_state(p).lower()
                    self.state_table[p].failed = 0
                except:
                    logging.info("Got no state from " + p)
                    state = "unknown"
                    self.state_table[p].failed = \
                        self.state_table[p].failed + 1
                    if self.state_table[p].failed >= MAX_FAIL:
                        playername = self.playername(p)
                        logging.warning("%s failed, trying to restart",
                                        playername)
                        watchdog.restart_service(playername)
                        self.state_table[p].failed = 0

                self.state_table[p].state = thisplayer_state

                # Check if playback started on a player that wasn't
                # playing before
                if thisplayer_state == STATE_PLAYING:
                    playing = True
                    state = "playing"

                    #                    if self.playername(p) == SPOTIFY_NAME:
                    #                        spotify_stopped = 0

                    if self.playername(p) == LMS_NAME:
                        squeezelite_active = 2

                    report_usage(
                        "audiocontrol_playing_{}".format(self.playername(p)),
                        duration)

                    md = self.get_meta(p)

                    if (p not in active_players):
                        new_player_started = p
                        active_players.insert(0, p)

                    md.playerState = thisplayer_state

                    # MPRIS delivers only very few metadata, these will be
                    # enriched with external sources
                    if (md.sameSong(self.metadata)):
                        md.fill_undefined(self.metadata)
                    else:
                        new_song = True

                    self.state_table[p].metadata = md
                    if not (md.sameSong(self.metadata)):
                        logging.debug("updated metadata: \nold %s\nnew %s",
                                      self.metadata, md)
                        # Store this as "current"
                        with self.metadata_lock:
                            self.metadata = md

                        self.metadata_notify(md)
                        logging.debug("notifications about new metadata sent")
                    elif state != previous_state:
                        logging.debug("changed state to playing")
                        self.metadata_notify(md)

                    # Some players deliver artwork after initial metadata
                    if md.artUrl != self.metadata.artUrl:
                        logging.debug("artwork changes from %s to %s",
                                      self.metadata.artUrl, md.artUrl)
                        self.metadata_notify(md)

                    # Add metadata if this is a new song
                    if new_song:
                        enrich_metadata_bg(md, callback=self)
                        logging.debug("metadata updater thread started")

                    # Even if we din't send metadata, this is still
                    # flagged
                    metadata_notified = True
                else:

                    # always keep one player in the active_players
                    # list
                    if len(active_players) > 1:
                        if p in active_players:
                            active_players.remove(p)

                    # update metadata for stopped players from time to time
                    i = randint(0, 600)
                    if (i == 0):
                        md = self.get_meta(p)
                        md.playerState = thisplayer_state
                        self.state_table[p].metadata = md

            self.playing = playing

            # Find active (or last paused) player
            if len(active_players) > 0:
                self.active_player = active_players[0]
            else:
                self.active_player = None

#             # Workaround for wrong state messages by Spotify
#             # Assume Spotify is still playing for 10 seconds if it's the
#             # active (or last stopped) player
#             if self.playername(self.active_player) == SPOTIFY_NAME:
#                 # Less aggressive metadata polling on Spotify as each polling will
#                 # result in an API request
#                 additional_delay = 4
#                 if not(playing):
#                     spotify_stopped += 1 + additional_delay
#                     if spotify_stopped < 26:
#                         if (spotify_stopped % 5) == 0:
#                             logging.debug("spotify workaround %s", spotify_stopped)
#                         playing = True
#

# Workaround for LMS muting the output after stopping the
# player
            if self.volume_control is not None:
                if self.playername(self.active_player) != LMS_NAME:
                    if squeezelite_active > 0:
                        squeezelite_active = squeezelite_active - 1
                        logging.debug(
                            "squeezelite was active before, unmuting")
                        self.volume_control.set_mute(False)

                if not (playing) and squeezelite_active > 0:
                    squeezelite_active = squeezelite_active - 1
                    logging.debug("squeezelite was active before, unmuting")
                    self.volume_control.set_mute(False)

            # There might be no active player, but one that is paused
            # or stopped
            if not (playing) and len(active_players) > 0:
                p = active_players[0]
                md = self.get_meta(p)
                md.playerState = self.state_table[p].state
                state = md.playerState

            if state != previous_state:
                logging.debug("state transition %s -> %s", previous_state,
                              state)
                if not metadata_notified:
                    self.metadata_notify(md)
                for sd in self.state_displays:
                    sd.update_playback_state(state)

            previous_state = state

            if new_player_started is not None:
                if self.auto_pause:
                    logging.info(
                        "new player %s started, pausing other active players",
                        self.playername(active_players[0]))
                    self.pause_inactive(new_player_started)
                else:
                    logging.debug("auto-pause disabled")

            self.last_update = datetime.datetime.now()

            time.sleep(self.loop_delay + additional_delay)
Пример #21
0
    def tmux_scraper(self):
        logging.info('tidalcontrol::tmux_scraper')
        cmd = 'docker exec -ti tidal_connect /usr/bin/tmux capture-pane -pS -10'
        stdout = subprocess.check_output(cmd.split())
        WINDOW_SIZE = 40
        WINDOW_COUNT = 2
        VALUE_MAP = {}

        for line in stdout.decode('utf-8').splitlines():
            if line.startswith('PlaybackState::'):
                VALUE_MAP['state'] = line.split('::')[1]
            # parse props
            if line.startswith('xx', WINDOW_SIZE - 1):
                for window_cnt in range(WINDOW_COUNT):
                    str_keyvals = (line[(WINDOW_SIZE * window_cnt) +
                                        1:(WINDOW_SIZE *
                                           (window_cnt + 1)) - 1].strip())

                    ar_props = str_keyvals.split(':')
                    if len(ar_props) > 1:
                        key = (ar_props[0].replace(' ', '_'))
                        value = ''.join(ar_props[1:]).strip()
                        sess_state_prefix = 'SessionState'
                        if value.startswith(sess_state_prefix):
                            value = value[len(sess_state_prefix):]
                        VALUE_MAP[key] = value
            # parse volume
            if line.endswith('#k'):
                value = line.strip()
                VALUE_MAP["volume"] = value.count("#")
        self.state = VALUE_MAP["state"]

        md = Metadata()
        md.playerName = "Tidal"
        md.artist = VALUE_MAP['artists']
        md.title = VALUE_MAP['title']
        md.albumTitle = VALUE_MAP['album_name']
        md.duration = VALUE_MAP['duration']
        md.artUrl = None
        md.externalArtUrl = None
        '''
        self.artist = artist
        self.title = title
        self.albumArtist = albumArtist
        self.albumTitle = albumTitle
        self.artUrl = artUrl
        self.externalArtUrl = None
        self.discNumber = discNumber
        self.tracknumber = trackNumber
        self.playerName = playerName
        self.playerState = playerState
        self.streamUrl = streamUrl
        self.playCount = None
        self.mbid = None
        self.artistmbid = None
        self.albummbid = None
        self.loved = None
        self.wiki = None
        self.loveSupported = Metadata.loveSupportedDefault
        self.tags = []
        self.skipped = False
        self.host_uuid = None
        self.releaseDate = None
        self.trackid = None
        self.hifiberry_cover_found=False
        self.duration=0
        self.time=0
        self.position=0 # poosition in seconds
        self.positionupdate=time() # last time position has been updated
        '''

        self.meta = md
Пример #22
0
class MPRISController():
    """
    Controller for MPRIS enabled media players
    """
    def __init__(self, auto_pause=True, loop_delay=1, ignore_players=[]):
        self.state_table = {}
        self.auto_pause = auto_pause
        self.metadata_displays = []
        self.last_update = None
        self.loop_delay = loop_delay
        self.active_player = None
        self.ignore_players = ignore_players
        self.metadata = {}
        self.playing = False
        self.connect_dbus()
        self.metadata = Metadata()
        self.metadata_lock = threading.Lock()
        self.volume_control = None

    def register_metadata_display(self, mddisplay):
        self.metadata_displays.append(mddisplay)

    def set_volume_control(self, volume_control):
        self.volume_control = volume_control

    def metadata_notify(self, metadata):
        if metadata.is_unknown() and metadata.playerState == "playing":
            logging.error("Got empty metadata - what's wrong here? %s",
                          metadata)

        for md in self.metadata_displays:
            try:
                logging.debug("metadata_notify: %s %s", md, metadata)
                md.notify_async(copy.copy(metadata))
            except Exception as e:
                logging.warn("could not notify %s: %s", md, e)
                logging.exception(e)

        self.metadata = metadata

    def connect_dbus(self):
        self.bus = dbus.SystemBus()
        self.device_prop_interfaces = {}

    def dbus_get_device_prop_interface(self, name):
        proxy = self.bus.get_object(name, "/org/mpris/MediaPlayer2")
        device_prop = dbus.Interface(proxy, "org.freedesktop.DBus.Properties")
        return device_prop

    def retrievePlayers(self):
        """
        Returns a list of all MPRIS enabled players that are active in
        the system
        """
        return [
            name for name in self.bus.list_names()
            if name.startswith("org.mpris")
        ]

    def retrieveState(self, name):
        """
        Returns the playback state for the given player instance
        """
        try:
            device_prop = self.dbus_get_device_prop_interface(name)
            state = device_prop.Get("org.mpris.MediaPlayer2.Player",
                                    "PlaybackStatus")
            return state
        except Exception as e:
            logging.warn("got exception %s", e)

    def retrieveCommands(self, name):
        commands = {
            "pause": "CanPause",
            "next": "CanGoNext",
            "previous": "CanGoPrevious",
            "play": "CanPlay",
            "seek": "CanSeek"
        }
        try:
            supported_commands = ["stop"]  # Stop must always be supported
            device_prop = self.dbus_get_device_prop_interface(name)
            for command in commands:
                supported = device_prop.Get("org.mpris.MediaPlayer2.Player",
                                            commands[command])
                if supported:
                    supported_commands.append(command)
        except Exception as e:
            logging.warn("got exception %s", e)

        return supported_commands

    def retrieveMeta(self, name):
        """
        Return the metadata for the given player instance
        """
        try:
            device_prop = self.dbus_get_device_prop_interface(name)
            prop = device_prop.Get("org.mpris.MediaPlayer2.Player", "Metadata")
            try:
                artist = array_to_string(prop.get("xesam:artist"))
            except:
                artist = None

            try:
                title = prop.get("xesam:title")
            except:
                title = None

            try:
                albumArtist = array_to_string(prop.get("xesam:albumArtist"))
            except:
                albumArtist = None

            try:
                albumTitle = prop.get("xesam:album")
            except:
                albumTitle = None

            try:
                artURL = prop.get("mpris:artUrl")
            except:
                artURL = None

            try:
                discNumber = prop.get("xesam:discNumber")
            except:
                discNumber = None

            try:
                trackNumber = prop.get("xesam:trackNumber")
            except:
                trackNumber = None

            md = Metadata(artist, title, albumArtist, albumTitle, artURL,
                          discNumber, trackNumber)

            try:
                md.streamUrl = prop.get("xesam:url")
            except:
                pass

            try:
                md.trackId = prop.get("mpris:trackid")
            except:
                pass

            md.playerName = self.playername(name)

            md.fix_problems()

            return md

        except dbus.exceptions.DBusException as e:
            if "ServiceUnknown" in e.__class__.__name__:
                # unfortunately we can't do anything about this and
                # logging doesn't help, therefore just ignoring this case
                pass
                #  logging.warning("service %s disappered, cleaning up", e)
            else:
                logging.warning("no mpris data received %s",
                                e.__class__.__name__)

            md = Metadata()
            md.playerName = self.playername(name)
            return md

    def mpris_command(self, playername, command):
        try:
            if command in mpris_commands:
                proxy = self.bus.get_object(playername,
                                            "/org/mpris/MediaPlayer2")
                player = dbus.Interface(
                    proxy, dbus_interface='org.mpris.MediaPlayer2.Player')

                run_command = getattr(player, command,
                                      lambda: "Unknown command")
                return run_command()
            else:
                logging.error("MPRIS command %s not supported", command)
        except Exception as e:
            logging.error("exception %s while sending MPRIS command %s to %s",
                          e, command, playername)
            return False

    def pause_inactive(self, active_player):
        """
        Automatically pause other player if playback was started
        on a new player
        """
        for p in self.state_table:
            if (p != active_player) and \
                    (self.state_table[p].state == PLAYING):
                logging.info("Pausing " + self.playername(p))
                self.mpris_command(p, MPRIS_PAUSE)

    def pause_all(self):
        for player in self.state_table:
            self.mpris_command(player, MPRIS_PAUSE)

    def print_players(self):
        for p in self.state_table:
            print(self.playername(p))

    def playername(self, mprisname):
        if mprisname is None:
            return
        if (mprisname.startswith(MPRIS_PREFIX)):
            return mprisname[len(MPRIS_PREFIX):]
        else:
            return mprisname

    def send_command(self, command, playerName=None):
        res = None
        if playerName is None:
            if self.active_player is None:
                logging.info("No active player, ignoring %s", command)
                return
            else:
                playerName = self.active_player

        if playerName.startswith(MPRIS_PREFIX):
            res = self.mpris_command(playerName, command)
        else:
            res = self.mpris_command(MPRIS_PREFIX + playerName, command)

        logging.info("sent %s to %s", command, playerName)

        return res

    def activate_player(self, playername):

        command = MPRIS_PLAY
        if playername.startswith(MPRIS_PREFIX):
            res = self.mpris_command(playername, command)
        else:
            res = self.mpris_command(MPRIS_PREFIX + playername, command)

        return res

    def update_metadata_attributes(self, updates, songId):
        logging.debug("received metadata update: %s", updates)

        if self.metadata is None:
            logging.warn("ooops, got an update, but don't have metadata")
            return

        if self.metadata.songId() != songId:
            logging.debug("received update for previous song, ignoring")
            return

        # TODO: Check if this is the same song!
        # Otherwise it might be a delayed update

        with self.metadata_lock:
            for attribute in updates:
                self.metadata.__dict__[attribute] = updates[attribute]

        self.metadata_notify(self.metadata)

    def main_loop(self):
        """
        Main loop:
        - monitors state of all players
        - pauses players if a new player starts playback
        """

        finished = False
        md = Metadata()
        active_players = []

        MAX_FAIL = 3

        # Workaround for spotifyd problems
        spotify_stopped = 0

        # Workaround for squeezelite mute
        squeezelite_active = 0

        previous_state = ""
        ts = datetime.datetime.now()

        while not (finished):
            additional_delay = 0
            new_player_started = None
            metadata_notified = False
            playing = False
            new_song = False
            state = "unknown"
            last_ts = ts
            ts = datetime.datetime.now()
            duration = (ts - last_ts).total_seconds()

            for p in self.retrievePlayers():

                if self.playername(p) in self.ignore_players:
                    continue

                if p not in self.state_table:
                    ps = PlayerState()
                    ps.supported_commands = self.retrieveCommands(p)
                    logging.debug("Player %s supports %s", p,
                                  ps.supported_commands)
                    self.state_table[p] = ps

                thisplayer_state = "unknown"
                try:
                    thisplayer_state = self.retrieveState(p).lower()
                    self.state_table[p].failed = 0
                except:
                    logging.info("Got no state from " + p)
                    state = "unknown"
                    self.state_table[p].failed = \
                        self.state_table[p].failed + 1
                    if self.state_table[p].failed >= MAX_FAIL:
                        playername = self.playername(p)
                        logging.warning("%s failed, trying to restart",
                                        playername)
                        watchdog.restart_service(playername)
                        self.state_table[p].failed = 0

                self.state_table[p].state = thisplayer_state

                # Check if playback started on a player that wasn't
                # playing before
                if thisplayer_state == PLAYING:
                    playing = True
                    state = "playing"

                    if self.playername(p) == SPOTIFY_NAME:
                        spotify_stopped = 0

                    if self.playername(p) == LMS_NAME:
                        squeezelite_active = 2

                    report_usage(
                        "audiocontrol_playing_{}".format(self.playername(p)),
                        duration)

                    md = self.retrieveMeta(p)

                    if (p not in active_players):
                        new_player_started = p
                        active_players.insert(0, p)

                    md.playerState = thisplayer_state

                    # MPRIS delivers only very few metadata, these will be
                    # enriched with external sources
                    if (md.sameSong(self.metadata)):
                        md.fill_undefined(self.metadata)
                    else:
                        new_song = True

                    self.state_table[p].metadata = md
                    if not (md.sameSong(self.metadata)):
                        logging.debug("updated metadata: \nold %s\nnew %s",
                                      self.metadata, md)
                        # Store this as "current"
                        with self.metadata_lock:
                            self.metadata = md

                        self.metadata_notify(md)
                        logging.debug("notifications about new metadata sent")
                    elif state != previous_state:
                        logging.debug("changed state to playing")
                        self.metadata_notify(md)

                    # Add metadata if this is a new song
                    if new_song:
                        enrich_metadata_bg(md, callback=self)
                        logging.debug("metadata updater thread started")

                    # Even if we din't send metadata, this is still
                    # flagged
                    metadata_notified = True
                else:

                    # always keep one player in the active_players
                    # list
                    if len(active_players) > 1:
                        if p in active_players:
                            active_players.remove(p)

                    # update metadata for stopped players from time to time
                    i = randint(0, 600)
                    if (i == 0):
                        md = self.retrieveMeta(p)
                        md.playerState = thisplayer_state
                        self.state_table[p].metadata = md

            self.playing = playing

            # Find active (or last paused) player
            if len(active_players) > 0:
                self.active_player = active_players[0]
            else:
                self.active_player = None

            # Workaround for wrong state messages by Spotify
            # Assume Spotify is still playing for 10 seconds if it's the
            # active (or last stopped) player
            if self.playername(self.active_player) == SPOTIFY_NAME:
                # Less aggressive metadata polling on Spotify as each polling will
                # result in an API request
                additional_delay = 4
                if not (playing):
                    spotify_stopped += 1 + additional_delay
                    if spotify_stopped < 26:
                        if (spotify_stopped % 5) == 0:
                            logging.debug("spotify workaround %s",
                                          spotify_stopped)
                        playing = True

            # Workaround for LMS muting the output after stopping the
            # player
            if self.volume_control is not None:
                if self.playername(self.active_player) != LMS_NAME:
                    if squeezelite_active > 0:
                        squeezelite_active = squeezelite_active - 1
                        logging.debug(
                            "squeezelite was active before, unmuting")
                        self.volume_control.set_mute(False)

                if not (playing) and squeezelite_active > 0:
                    squeezelite_active = squeezelite_active - 1
                    logging.debug("squeezelite was active before, unmuting")
                    self.volume_control.set_mute(False)

            # There might be no active player, but one that is paused
            # or stopped
            if not (playing) and len(active_players) > 0:
                p = active_players[0]
                md = self.retrieveMeta(p)
                md.playerState = self.state_table[p].state
                state = md.playerState

            if state != previous_state:
                logging.debug("state transition %s -> %s", previous_state,
                              state)
                if not metadata_notified:
                    self.metadata_notify(md)

            previous_state = state

            if new_player_started is not None:
                if self.auto_pause:
                    logging.info(
                        "new player %s started, pausing other active players",
                        self.playername(active_players[0]))
                    self.pause_inactive(new_player_started)
                else:
                    logging.debug("auto-pause disabled")

            self.last_update = datetime.datetime.now()

            time.sleep(self.loop_delay + additional_delay)

    # ##
    # ## controller functions
    # ##

    def previous(self):
        self.send_command(MPRIS_PREV)

    def next(self):
        self.send_command(MPRIS_NEXT)

    def playpause(self, pause=None):
        command = None
        if pause is None:
            if self.playing:
                command = MPRIS_PAUSE
            else:
                command = MPRIS_PLAY
        elif pause:
            command = MPRIS_PAUSE
        else:
            command = MPRIS_PLAY

        self.send_command(command)

    def stop(self):
        self.send_command(MPRIS_STOP)

    # ##
    # ## end controller functions
    # ##

    def __str__(self):
        return "mpris"

    def states(self):
        players = []
        for p in self.state_table:
            player = {}
            player["name"] = self.playername(p)
            player["state"] = self.state_table[p].state
            player["artist"] = self.state_table[p].metadata.artist
            player["title"] = self.state_table[p].metadata.title
            player["supported_commands"] = self.state_table[
                p].supported_commands

            players.append(player)

        return {"players": players, "last_updated": str(self.last_update)}
Пример #23
0
 def test_song_id(self):
     md1=Metadata("artist1","song1",albumTitle="abum1")
     md2=Metadata("artist1","song1",albumTitle="abum2")
     md3=Metadata("artist2","song1")
     md4=Metadata("artist2","song1",albumTitle="abum1")
     
     self.assertEqual(md1.songId(),md2.songId())
     self.assertEqual(md3.songId(),md4.songId())
     self.assertNotEqual(md1.songId(),md3.songId())
     self.assertNotEqual(md2.songId(),md3.songId())
     self.assertNotEqual(md1.songId(),md4.songId())
Пример #24
0
class AudioController():
    """
    Controller for MPRIS and non-MPRIS media players
    """
    def __init__(self, auto_pause=True, loop_delay=1, ignore_players=[]):
        self.state_table = {}
        self.auto_pause = auto_pause
        self.metadata_displays = []
        self.last_update = None
        self.loop_delay = loop_delay
        self.active_player = None
        self.ignore_players = ignore_players
        self.metadata = {}
        self.playing = False
        self.metadata = Metadata()
        self.metadata_lock = threading.Lock()
        self.volume_control = None
        self.metadata_processors = []
        self.state_displays = []
        self.players = {}
        self.mpris = MPRIS()
        self.mpris.connect_dbus()

    """
    Register a non-mpris player controls
    """

    def register_nonmpris_player(self, name, controller):
        self.players[name] = controller

    def register_metadata_display(self, mddisplay):
        self.metadata_displays.append(mddisplay)

    def register_state_display(self, statedisplay):
        self.state_displays.append(statedisplay)

    def register_metadata_processor(self, mdproc):
        self.metadata_processors.append(mdproc)

    def set_volume_control(self, volume_control):
        self.volume_control = volume_control

    def metadata_notify(self, metadata):
        if metadata.is_unknown() and metadata.playerState == "playing":
            logging.warning(
                "Metadata without artist, album or title - what's wrong here? %s",
                metadata)

        for md in self.metadata_displays:
            try:
                logging.debug("metadata_notify: %s %s", md, metadata)
                md.notify_async(copy.copy(metadata))
            except Exception as e:
                logging.warning("could not notify %s: %s", md, e)
                logging.exception(e)

        self.metadata = metadata

    def all_players(self):
        """
        Returns a list of MPRIS and non-MPRIS players
        """
        players = list(self.players.keys()) + self.mpris.retrieve_players()
        logging.debug("players: %s", players)
        return players

    def get_player_state(self, name):
        """
        Returns the playback state for the given player instance
        
        It can handle both MPRIS and non-MPRIS players
        """

        if name in self.players.keys():
            return self.players[name].get_state()
        else:
            return self.mpris.retrieve_state(name)

    def get_supported_commands(self, name):
        if name in self.players.keys():
            return self.players[name].get_supported_commands()
        else:
            return self.mpris.get_supported_commands(name)

    def send_command_to_player(self, name, command):
        if name in self.players.keys():
            self.players[name].send_command(command)
        else:
            self.mpris.send_command(name, command)

    def pause_inactive(self, active_player):
        """
        Automatically pause other player if playback was started
        on a new player
        """
        for p in self.state_table:
            if (p != active_player) and \
                    (self.state_table[p].state == STATE_PLAYING):
                logging.info("Pausing " + self.playername(p))
                self.send_command(p, CMD_PAUSE)

    def pause_all(self):
        for player in self.state_table:
            self.send_command(player, CMD_PAUSE)

    def print_players(self):
        for p in self.state_table:
            print(self.playername(p))

    def playername(self, name):
        if name is None:
            return
        if (name.startswith(MPRIS_PREFIX)):
            return name[len(MPRIS_PREFIX):]
        else:
            return name

    def send_command(self, command, playerName=None):
        if playerName is None:
            if self.active_player is None:
                logging.info("No active player, ignoring %s", command)
                return
            else:
                playerName = self.active_player

        res = self.send_command_to_player(playerName, command)
        logging.info("sent %s to %s", command, playerName)

        return res

    def activate_player(self, playername):

        command = CMD_PLAY
        if playername.startswith(MPRIS_PREFIX):
            res = self.send_command_to_player(playername, command)
        else:
            res = self.mpris_command(MPRIS_PREFIX + playername, command)

        return res

    def get_meta(self, name):
        if name in self.players.keys():
            md = self.players[name].get_meta()
        else:
            md = self.mpris.get_meta(name)

        if md is None:
            return None

        md.fix_problems()

        for p in self.metadata_processors:
            p.process_metadata(md)

        return md

    def update_metadata_attributes(self, updates, songId):
        logging.debug("received metadata update: %s", updates)

        if self.metadata is None:
            logging.warning("ooops, got an update, but don't have metadata")
            return

        if self.metadata.songId() != songId:
            logging.debug("received update for previous song, ignoring")
            return

        # TODO: Check if this is the same song!
        # Otherwise it might be a delayed update

        with self.metadata_lock:
            for attribute in updates:
                self.metadata.__dict__[attribute] = updates[attribute]

        self.metadata_notify(self.metadata)

    def main_loop(self):
        """
        Main loop:
        - monitors state of all players
        - pauses players if a new player starts playback
        """

        finished = False
        md = Metadata()
        active_players = []

        MAX_FAIL = 3

        # Workaround for spotifyd problems
        #        spotify_stopped = 0

        # Workaround for squeezelite mute
        squeezelite_active = 0

        previous_state = ""
        ts = datetime.datetime.now()

        while not (finished):
            additional_delay = 0
            new_player_started = None
            metadata_notified = False
            playing = False
            new_song = False
            state = "unknown"
            last_ts = ts
            ts = datetime.datetime.now()
            duration = (ts - last_ts).total_seconds()

            for p in self.all_players():

                if self.playername(p) in self.ignore_players:
                    continue

                if p not in self.state_table:
                    ps = PlayerState()
                    ps.supported_commands = self.get_supported_commands(p)
                    logging.debug("Player %s supports %s", p,
                                  ps.supported_commands)
                    self.state_table[p] = ps

                thisplayer_state = "unknown"
                try:
                    thisplayer_state = self.get_player_state(p).lower()
                    self.state_table[p].failed = 0
                except:
                    logging.info("Got no state from " + p)
                    state = "unknown"
                    self.state_table[p].failed = \
                        self.state_table[p].failed + 1
                    if self.state_table[p].failed >= MAX_FAIL:
                        playername = self.playername(p)
                        logging.warning("%s failed, trying to restart",
                                        playername)
                        watchdog.restart_service(playername)
                        self.state_table[p].failed = 0

                self.state_table[p].state = thisplayer_state

                # Check if playback started on a player that wasn't
                # playing before
                if thisplayer_state == STATE_PLAYING:
                    playing = True
                    state = "playing"

                    #                    if self.playername(p) == SPOTIFY_NAME:
                    #                        spotify_stopped = 0

                    if self.playername(p) == LMS_NAME:
                        squeezelite_active = 2

                    report_usage(
                        "audiocontrol_playing_{}".format(self.playername(p)),
                        duration)

                    md = self.get_meta(p)

                    if (p not in active_players):
                        new_player_started = p
                        active_players.insert(0, p)

                    md.playerState = thisplayer_state

                    # MPRIS delivers only very few metadata, these will be
                    # enriched with external sources
                    if (md.sameSong(self.metadata)):
                        md.fill_undefined(self.metadata)
                    else:
                        new_song = True

                    self.state_table[p].metadata = md
                    if not (md.sameSong(self.metadata)):
                        logging.debug("updated metadata: \nold %s\nnew %s",
                                      self.metadata, md)
                        # Store this as "current"
                        with self.metadata_lock:
                            self.metadata = md

                        self.metadata_notify(md)
                        logging.debug("notifications about new metadata sent")
                    elif state != previous_state:
                        logging.debug("changed state to playing")
                        self.metadata_notify(md)

                    # Some players deliver artwork after initial metadata
                    if md.artUrl != self.metadata.artUrl:
                        logging.debug("artwork changes from %s to %s",
                                      self.metadata.artUrl, md.artUrl)
                        self.metadata_notify(md)

                    # Add metadata if this is a new song
                    if new_song:
                        enrich_metadata_bg(md, callback=self)
                        logging.debug("metadata updater thread started")

                    # Even if we din't send metadata, this is still
                    # flagged
                    metadata_notified = True
                else:

                    # always keep one player in the active_players
                    # list
                    if len(active_players) > 1:
                        if p in active_players:
                            active_players.remove(p)

                    # update metadata for stopped players from time to time
                    i = randint(0, 600)
                    if (i == 0):
                        md = self.get_meta(p)
                        md.playerState = thisplayer_state
                        self.state_table[p].metadata = md

            self.playing = playing

            # Find active (or last paused) player
            if len(active_players) > 0:
                self.active_player = active_players[0]
            else:
                self.active_player = None

#             # Workaround for wrong state messages by Spotify
#             # Assume Spotify is still playing for 10 seconds if it's the
#             # active (or last stopped) player
#             if self.playername(self.active_player) == SPOTIFY_NAME:
#                 # Less aggressive metadata polling on Spotify as each polling will
#                 # result in an API request
#                 additional_delay = 4
#                 if not(playing):
#                     spotify_stopped += 1 + additional_delay
#                     if spotify_stopped < 26:
#                         if (spotify_stopped % 5) == 0:
#                             logging.debug("spotify workaround %s", spotify_stopped)
#                         playing = True
#

# Workaround for LMS muting the output after stopping the
# player
            if self.volume_control is not None:
                if self.playername(self.active_player) != LMS_NAME:
                    if squeezelite_active > 0:
                        squeezelite_active = squeezelite_active - 1
                        logging.debug(
                            "squeezelite was active before, unmuting")
                        self.volume_control.set_mute(False)

                if not (playing) and squeezelite_active > 0:
                    squeezelite_active = squeezelite_active - 1
                    logging.debug("squeezelite was active before, unmuting")
                    self.volume_control.set_mute(False)

            # There might be no active player, but one that is paused
            # or stopped
            if not (playing) and len(active_players) > 0:
                p = active_players[0]
                md = self.get_meta(p)
                md.playerState = self.state_table[p].state
                state = md.playerState

            if state != previous_state:
                logging.debug("state transition %s -> %s", previous_state,
                              state)
                if not metadata_notified:
                    self.metadata_notify(md)
                for sd in self.state_displays:
                    sd.update_playback_state(state)

            previous_state = state

            if new_player_started is not None:
                if self.auto_pause:
                    logging.info(
                        "new player %s started, pausing other active players",
                        self.playername(active_players[0]))
                    self.pause_inactive(new_player_started)
                else:
                    logging.debug("auto-pause disabled")

            self.last_update = datetime.datetime.now()

            time.sleep(self.loop_delay + additional_delay)

    # ##
    # ## controller functions
    # ##

    def previous(self):
        self.send_command(CMD_PREV)

    def next(self):
        self.send_command(CMD_NEXT)

    def playpause(self, pause=None, ignore=None):

        if ignore is not None:
            if self.active_player.lower() == ignore.lower():
                logging.info(
                    "Got a playpquse request that should be ignored (%s)",
                    ignore)
                return

        command = None
        if pause is None:
            if self.playing:
                command = CMD_PAUSE
            else:
                command = CMD_PLAY
        elif pause:
            command = CMD_PAUSE
        else:
            command = CMD_PLAY

        self.send_command(command)

    def stop(self):
        self.send_command(CMD_STOP)

    # ##
    # ## end controller functions
    # ##

    def __str__(self):
        return "mpris"

    def states(self):
        players = []
        for p in self.state_table:
            player = {}
            player["name"] = self.playername(p)
            player["state"] = self.state_table[p].state
            player["artist"] = self.state_table[p].metadata.artist
            player["title"] = self.state_table[p].metadata.title
            player["supported_commands"] = self.state_table[
                p].supported_commands

            players.append(player)

        return {"players": players, "last_updated": str(self.last_update)}