예제 #1
0
class KodiMonitor(Monitor):
    def __init__(self, callback):
        self.mgr = callback
        self.doUtils = DownloadUtils().downloadUrl
        self.xbmcplayer = Player()
        self.playqueue = self.mgr.playqueue
        Monitor.__init__(self)
        log.info("Kodi monitor started.")

    def onScanStarted(self, library):
        log.debug("Kodi library scan %s running." % library)
        if library == "video":
            window('plex_kodiScan', value="true")

    def onScanFinished(self, library):
        log.debug("Kodi library scan %s finished." % library)
        if library == "video":
            window('plex_kodiScan', clear=True)

    def onSettingsChanged(self):
        """
        Monitor the PKC settings for changes made by the user
        """
        log.debug('PKC settings change detected')
        changed = False
        # Reset the window variables from the settings variables
        for settings_value, window_value in WINDOW_SETTINGS.iteritems():
            if window(window_value) != settings(settings_value):
                changed = True
                log.debug('PKC window settings changed: %s is now %s' %
                          (settings_value, settings(settings_value)))
                window(window_value, value=settings(settings_value))
                if settings_value == 'fetch_pms_item_number':
                    log.info('Requesting playlist/nodes refresh')
                    plex_command('RUN_LIB_SCAN', 'views')
        # Reset the state variables in state.py
        for settings_value, state_name in STATE_SETTINGS.iteritems():
            new = settings(settings_value)
            if new == 'true':
                new = True
            elif new == 'false':
                new = False
            if getattr(state, state_name) != new:
                changed = True
                log.debug('PKC state settings %s changed from %s to %s' %
                          (settings_value, getattr(state, state_name), new))
                setattr(state, state_name, new)
        # Special cases, overwrite all internal settings
        state.FULL_SYNC_INTERVALL = int(settings('fullSyncInterval')) * 60
        state.BACKGROUNDSYNC_SAFTYMARGIN = int(
            settings('backgroundsync_saftyMargin'))
        state.SYNC_THREAD_NUMBER = int(settings('syncThreadNumber'))
        # Never set through the user
        # state.KODI_PLEX_TIME_OFFSET = float(settings('kodiplextimeoffset'))
        if changed is True:
            # Assume that the user changed the settings so that we can now find
            # the path to all media files
            state.STOP_SYNC = False
            state.PATH_VERIFIED = False

    @CatchExceptions(warnuser=False)
    def onNotification(self, sender, method, data):

        if data:
            data = loads(data, 'utf-8')
            log.debug("Method: %s Data: %s" % (method, data))

        if method == "Player.OnPlay":
            self.PlayBackStart(data)

        elif method == "Player.OnStop":
            # Should refresh our video nodes, e.g. on deck
            # xbmc.executebuiltin('ReloadSkin()')
            pass

        elif method == "VideoLibrary.OnUpdate":
            # Manually marking as watched/unwatched
            playcount = data.get('playcount')
            item = data.get('item')

            try:
                kodiid = item['id']
                item_type = item['type']
            except (KeyError, TypeError):
                log.info("Item is invalid for playstate update.")
            else:
                # Send notification to the server.
                with plexdb.Get_Plex_DB() as plexcur:
                    plex_dbitem = plexcur.getItem_byKodiId(kodiid, item_type)
                try:
                    itemid = plex_dbitem[0]
                except TypeError:
                    log.error("Could not find itemid in plex database for a "
                              "video library update")
                else:
                    # Stop from manually marking as watched unwatched, with
                    # actual playback.
                    if window('plex_skipWatched%s' % itemid) == "true":
                        # property is set in player.py
                        window('plex_skipWatched%s' % itemid, clear=True)
                    else:
                        # notify the server
                        if playcount > 0:
                            scrobble(itemid, 'watched')
                        else:
                            scrobble(itemid, 'unwatched')

        elif method == "VideoLibrary.OnRemove":
            pass

        elif method == "System.OnSleep":
            # Connection is going to sleep
            log.info("Marking the server as offline. SystemOnSleep activated.")
            window('plex_online', value="sleep")

        elif method == "System.OnWake":
            # Allow network to wake up
            sleep(10000)
            window('plex_onWake', value="true")
            window('plex_online', value="false")

        elif method == "GUI.OnScreensaverDeactivated":
            if settings('dbSyncScreensaver') == "true":
                sleep(5000)
                plex_command('RUN_LIB_SCAN', 'full')

        elif method == "System.OnQuit":
            log.info('Kodi OnQuit detected - shutting down')
            state.STOP_PKC = True

    def PlayBackStart(self, data):
        """
        Called whenever a playback is started
        """
        # Get currently playing file - can take a while. Will be utf-8!
        try:
            currentFile = self.xbmcplayer.getPlayingFile()
        except:
            currentFile = None
            count = 0
            while currentFile is None:
                sleep(100)
                try:
                    currentFile = self.xbmcplayer.getPlayingFile()
                except:
                    pass
                if count == 50:
                    log.info("No current File, cancel OnPlayBackStart...")
                    return
                else:
                    count += 1
        # Just to be on the safe side
        currentFile = tryDecode(currentFile)
        log.debug("Currently playing file is: %s" % currentFile)

        # Get the type of media we're playing
        try:
            typus = data['item']['type']
        except (TypeError, KeyError):
            log.info("Item is invalid for PMS playstate update.")
            return
        log.debug("Playing itemtype is (or appears to be): %s" % typus)

        # Try to get a Kodi ID
        # If PKC was used - native paths, not direct paths
        plex_id = window('plex_%s.itemid' % tryEncode(currentFile))
        # Get rid of the '' if the window property was not set
        plex_id = None if not plex_id else plex_id
        kodiid = None
        if plex_id is None:
            log.debug('Did not get Plex id from window properties')
            try:
                kodiid = data['item']['id']
            except (TypeError, KeyError):
                log.debug('Did not get a Kodi id from Kodi, darn')
        # For direct paths, if we're not streaming something
        # When using Widgets, Kodi doesn't tell us shit so we need this hack
        if (kodiid is None and plex_id is None and typus != 'song'
                and not currentFile.startswith('http')):
            (kodiid, typus) = get_kodiid_from_filename(currentFile)
            if kodiid is None:
                return

        if plex_id is None:
            # Get Plex' item id
            with plexdb.Get_Plex_DB() as plexcursor:
                plex_dbitem = plexcursor.getItem_byKodiId(kodiid, typus)
            try:
                plex_id = plex_dbitem[0]
            except TypeError:
                log.info("No Plex id returned for kodiid %s. Aborting playback"
                         " report" % kodiid)
                return
        log.debug("Found Plex id %s for Kodi id %s for type %s" %
                  (plex_id, kodiid, typus))

        # Switch subtitle tracks if applicable
        subtitle = window('plex_%s.subtitle' % tryEncode(currentFile))
        if window(tryEncode('plex_%s.playmethod' % currentFile)) \
                == 'Transcode' and subtitle:
            if window('plex_%s.subtitle' % currentFile) == 'None':
                self.xbmcplayer.showSubtitles(False)
            else:
                self.xbmcplayer.setSubtitleStream(int(subtitle))

        # Set some stuff if Kodi initiated playback
        if ((settings('useDirectPaths') == "1" and not typus == "song")
                or (typus == "song" and settings('enableMusic') == "true")):
            if self.StartDirectPath(plex_id, typus,
                                    tryEncode(currentFile)) is False:
                log.error('Could not initiate monitoring; aborting')
                return

        # Save currentFile for cleanup later and to be able to access refs
        window('plex_lastPlayedFiled', value=currentFile)
        window('plex_currently_playing_itemid', value=plex_id)
        window("plex_%s.itemid" % tryEncode(currentFile), value=plex_id)
        log.info('Finish playback startup')

    def StartDirectPath(self, plex_id, type, currentFile):
        """
        Set some additional stuff if playback was initiated by Kodi, not PKC
        """
        xml = self.doUtils('{server}/library/metadata/%s' % plex_id)
        try:
            xml[0].attrib
        except:
            log.error('Did not receive a valid XML for plex_id %s.' % plex_id)
            return False
        # Setup stuff, because playback was started by Kodi, not PKC
        api = API(xml[0])
        listitem = api.CreateListItemFromPlexItem()
        api.set_playback_win_props(currentFile, listitem)
        if type == "song" and settings('streamMusic') == "true":
            window('plex_%s.playmethod' % currentFile, value="DirectStream")
        else:
            window('plex_%s.playmethod' % currentFile, value="DirectPlay")
        log.debug('Window properties set for direct paths!')
예제 #2
0
class SubscriptionManager:
    def __init__(self, jsonClass, RequestMgr):
        self.serverlist = []
        self.subscribers = {}
        self.info = {}
        self.lastkey = ""
        self.containerKey = ""
        self.lastratingkey = ""
        self.volume = 0
        self.mute = '0'
        self.server = ""
        self.protocol = "http"
        self.port = ""
        self.playerprops = {}
        self.doUtils = downloadutils.DownloadUtils().downloadUrl
        self.xbmcplayer = Player()

        self.js = jsonClass
        self.RequestMgr = RequestMgr

    def getServerByHost(self, host):
        if len(self.serverlist) == 1:
            return self.serverlist[0]
        for server in self.serverlist:
            if (server.get('serverName') in host
                    or server.get('server') in host):
                return server
        return {}

    def getVolume(self):
        self.volume, self.mute = self.js.getVolume()

    def msg(self, players):
        msg = getXMLHeader()
        msg += '<MediaContainer commandID="INSERTCOMMANDID"'
        if players:
            self.getVolume()
            maintype = plex_audio()
            for p in players.values():
                if p.get('type') == xbmc_video():
                    maintype = plex_video()
                elif p.get('type') == xbmc_photo():
                    maintype = plex_photo()
            self.mainlocation = "fullScreen" + maintype[0:1].upper(
            ) + maintype[1:].lower()
        else:
            self.mainlocation = "navigation"
        msg += ' location="%s">' % self.mainlocation
        msg += self.getTimelineXML(self.js.getAudioPlayerId(players),
                                   plex_audio())
        msg += self.getTimelineXML(self.js.getPhotoPlayerId(players),
                                   plex_photo())
        msg += self.getTimelineXML(self.js.getVideoPlayerId(players),
                                   plex_video())
        msg += "\r\n</MediaContainer>"
        return msg

    def getTimelineXML(self, playerid, ptype):
        if playerid is not None:
            info = self.getPlayerProperties(playerid)
            # save this info off so the server update can use it too
            self.playerprops[playerid] = info
            state = info['state']
            time = info['time']
        else:
            state = "stopped"
            time = 0
        ret = "\r\n" + '  <Timeline state="%s" time="%s" type="%s"' % (
            state, time, ptype)
        if playerid is None:
            ret += ' seekRange="0-0"'
            ret += ' />'
            return ret

        # pbmc_server = str(WINDOW.getProperty('plexbmc.nowplaying.server'))
        # userId = str(WINDOW.getProperty('currUserId'))
        pbmc_server = window('pms_server')
        if pbmc_server:
            (self.protocol, self.server, self.port) = \
                pbmc_server.split(':')
            self.server = self.server.replace('/', '')
        keyid = None
        count = 0
        while not keyid:
            if count > 300:
                break
            keyid = window('Plex_currently_playing_itemid')
            xbmc.sleep(100)
            count += 1
        if keyid:
            self.lastkey = "/library/metadata/%s" % keyid
            self.lastratingkey = keyid
            ret += ' location="%s"' % (self.mainlocation)
            ret += ' key="%s"' % (self.lastkey)
            ret += ' ratingKey="%s"' % (self.lastratingkey)
        serv = self.getServerByHost(self.server)
        if info.get('playQueueID'):
            self.containerKey = "/playQueues/%s" % info.get('playQueueID')
            ret += ' playQueueID="%s"' % info.get('playQueueID')
            ret += ' playQueueVersion="%s"' % info.get('playQueueVersion')
            ret += ' playQueueItemID="%s"' % (info.get('playQueueItemID'))
            ret += ' containerKey="%s"' % self.containerKey
        elif keyid:
            self.containerKey = self.lastkey
            ret += ' containerKey="%s"' % (self.containerKey)

        ret += ' duration="%s"' % info['duration']
        ret += ' seekRange="0-%s"' % info['duration']
        ret += ' controllable="%s"' % self.controllable()
        ret += ' machineIdentifier="%s"' % serv.get('uuid', "")
        ret += ' protocol="%s"' % serv.get('protocol', "http")
        ret += ' address="%s"' % serv.get('server', self.server)
        ret += ' port="%s"' % serv.get('port', self.port)
        ret += ' guid="%s"' % info['guid']
        ret += ' volume="%s"' % info['volume']
        ret += ' shuffle="%s"' % info['shuffle']
        ret += ' mute="%s"' % self.mute
        ret += ' repeat="%s"' % info['repeat']
        # Might need an update in the future
        ret += ' subtitleStreamID="-1"'
        ret += ' audioStreamID="-1"'

        ret += ' />'
        return ret

    def updateCommandID(self, uuid, commandID):
        if commandID and self.subscribers.get(uuid, False):
            self.subscribers[uuid].commandID = int(commandID)

    def notify(self, event=False):
        self.cleanup()
        # Don't tell anyone if we don't know a Plex ID and are still playing
        # (e.g. no stop called). Used for e.g. PVR/TV without PKC usage
        if not window('Plex_currently_playing_itemid'):
            return True
        players = self.js.getPlayers()
        # fetch the message, subscribers or not, since the server
        # will need the info anyway
        msg = self.msg(players)
        if self.subscribers:
            with threading.RLock():
                for sub in self.subscribers.values():
                    sub.send_update(msg, len(players) == 0)
        self.notifyServer(players)
        return True

    def notifyServer(self, players):
        if not players:
            return True
        for p in players.values():
            info = self.playerprops[p.get('playerid')]
            params = {'state': 'stopped'}
            params['containerKey'] = (self.containerKey
                                      or "/library/metadata/900000")
            if info.get('playQueueID'):
                params['containerKey'] = '/playQueues/' + info['playQueueID']
                params['playQueueVersion'] = info['playQueueVersion']
                params['playQueueItemID'] = info['playQueueItemID']
            params['key'] = (self.lastkey or "/library/metadata/900000")
            params['ratingKey'] = (self.lastratingkey or "900000")
            params['state'] = info['state']
            params['time'] = info['time']
            params['duration'] = info['duration']

            serv = self.getServerByHost(self.server)
            url = serv.get('protocol', 'http') + '://' \
                + serv.get('server', 'localhost') + ':' \
                + serv.get('port', '32400') + "/:/timeline"
            self.doUtils(url, parameters=params)
            # requests.getwithparams(serv.get('server', 'localhost'), serv.get('port', 32400), "/:/timeline", params, getPlexHeaders(), serv.get('protocol', 'http'))
            self.logMsg(
                "sent server notification with parameters: %s" % params, 2)

    def controllable(self):
        return "volume,shuffle,repeat,audioStream,videoStream,subtitleStream,skipPrevious,skipNext,seekTo,stepBack,stepForward,stop,playPause"

    def addSubscriber(self, protocol, host, port, uuid, commandID):
        sub = Subscriber(protocol, host, port, uuid, commandID, self,
                         self.RequestMgr)
        with threading.RLock():
            self.subscribers[sub.uuid] = sub
        return sub

    def removeSubscriber(self, uuid):
        with threading.RLock():
            for sub in self.subscribers.values():
                if sub.uuid == uuid or sub.host == uuid:
                    sub.cleanup()
                    del self.subscribers[sub.uuid]

    def cleanup(self):
        with threading.RLock():
            for sub in self.subscribers.values():
                if sub.age > 30:
                    sub.cleanup()
                    del self.subscribers[sub.uuid]

    def getPlayerProperties(self, playerid):
        info = {}
        try:
            # get info from the player
            props = self.js.jsonrpc(
                "Player.GetProperties", {
                    "playerid":
                    playerid,
                    "properties":
                    ["time", "totaltime", "speed", "shuffled", "repeat"]
                })
            self.logMsg(
                self.js.jsonrpc(
                    "Player.GetItem", {
                        "playerid": playerid,
                        "properties":
                        ["file", "showlink", "episode", "season"]
                    }), 2)
            info['time'] = timeToMillis(props['time'])
            info['duration'] = timeToMillis(props['totaltime'])
            info['state'] = ("paused", "playing")[int(props['speed'])]
            info['shuffle'] = ("0", "1")[props.get('shuffled', False)]
            info['repeat'] = pf.getPlexRepeat(props.get('repeat'))
            # New PMS playQueue attributes
            cf = self.xbmcplayer.getPlayingFile()
            info['playQueueID'] = window('playQueueID')
            info['playQueueVersion'] = window('playQueueVersion')
            info['playQueueItemID'] = window('plex_%s.playQueueItemID' % cf)
            info['guid'] = window('plex_%s.guid' % cf)

        except:
            info['time'] = 0
            info['duration'] = 0
            info['state'] = "stopped"
            info['shuffle'] = False
        # get the volume from the application
        info['volume'] = self.volume
        info['mute'] = self.mute

        return info
class KodiMonitor(Monitor):
    def __init__(self, callback):
        self.mgr = callback
        self.doUtils = downloadutils.DownloadUtils().downloadUrl
        self.xbmcplayer = Player()
        self.playqueue = self.mgr.playqueue
        Monitor.__init__(self)
        log.info("Kodi monitor started.")

    def onScanStarted(self, library):
        log.debug("Kodi library scan %s running." % library)
        if library == "video":
            window('plex_kodiScan', value="true")

    def onScanFinished(self, library):
        log.debug("Kodi library scan %s finished." % library)
        if library == "video":
            window('plex_kodiScan', clear=True)

    def onSettingsChanged(self):
        """
        Monitor the PKC settings for changes made by the user
        """
        # settings: window-variable
        items = {
            'logLevel': 'plex_logLevel',
            'enableContext': 'plex_context',
            'plex_restricteduser': '******',
            'dbSyncIndicator': 'dbSyncIndicator',
            'remapSMB': 'remapSMB',
            'replaceSMB': 'replaceSMB',
            'force_transcode_pix': 'plex_force_transcode_pix',
            'fetch_pms_item_number': 'fetch_pms_item_number'
        }
        # Path replacement
        for typus in REMAP_TYPE_FROM_PLEXTYPE.values():
            for arg in ('Org', 'New'):
                key = 'remapSMB%s%s' % (typus, arg)
                items[key] = key
        # Reset the window variables from the settings variables
        for settings_value, window_value in items.iteritems():
            if window(window_value) != settings(settings_value):
                log.debug('PKC settings changed: %s is now %s' %
                          (settings_value, settings(settings_value)))
                window(window_value, value=settings(settings_value))
                if settings_value == 'fetch_pms_item_number':
                    log.info('Requesting playlist/nodes refresh')
                    window('plex_runLibScan', value="views")

    @CatchExceptions(warnuser=False)
    def onNotification(self, sender, method, data):

        if data:
            data = loads(data, 'utf-8')
            log.debug("Method: %s Data: %s" % (method, data))

        if method == "Player.OnPlay":
            self.PlayBackStart(data)

        elif method == "Player.OnStop":
            # Should refresh our video nodes, e.g. on deck
            # xbmc.executebuiltin('ReloadSkin()')
            pass

        elif method == "VideoLibrary.OnUpdate":
            # Manually marking as watched/unwatched
            playcount = data.get('playcount')
            item = data.get('item')
            try:
                kodiid = item['id']
                item_type = item['type']
            except (KeyError, TypeError):
                log.info("Item is invalid for playstate update.")
            else:
                # Send notification to the server.
                with plexdb.Get_Plex_DB() as plexcur:
                    plex_dbitem = plexcur.getItem_byKodiId(kodiid, item_type)
                try:
                    itemid = plex_dbitem[0]
                except TypeError:
                    log.error("Could not find itemid in plex database for a "
                              "video library update")
                else:
                    # Stop from manually marking as watched unwatched, with actual playback.
                    if window('plex_skipWatched%s' % itemid) == "true":
                        # property is set in player.py
                        window('plex_skipWatched%s' % itemid, clear=True)
                    else:
                        # notify the server
                        if playcount != 0:
                            scrobble(itemid, 'watched')
                        else:
                            scrobble(itemid, 'unwatched')

        elif method == "VideoLibrary.OnRemove":
            pass

        elif method == "System.OnSleep":
            # Connection is going to sleep
            log.info("Marking the server as offline. SystemOnSleep activated.")
            window('plex_online', value="sleep")

        elif method == "System.OnWake":
            # Allow network to wake up
            sleep(10000)
            window('plex_onWake', value="true")
            window('plex_online', value="false")

        elif method == "GUI.OnScreensaverDeactivated":
            if settings('dbSyncScreensaver') == "true":
                sleep(5000)
                window('plex_runLibScan', value="full")

    def PlayBackStart(self, data):
        """
        Called whenever a playback is started
        """
        # Get currently playing file - can take a while. Will be utf-8!
        try:
            currentFile = self.xbmcplayer.getPlayingFile()
        except:
            currentFile = None
            count = 0
            while currentFile is None:
                sleep(100)
                try:
                    currentFile = self.xbmcplayer.getPlayingFile()
                except:
                    pass
                if count == 50:
                    log.info("No current File, cancel OnPlayBackStart...")
                    return
                else:
                    count += 1
        # Just to be on the safe side
        currentFile = tryDecode(currentFile)
        log.debug("Currently playing file is: %s" % currentFile)

        # Get the type of media we're playing
        try:
            typus = data['item']['type']
        except (TypeError, KeyError):
            log.info("Item is invalid for PMS playstate update.")
            return
        log.debug("Playing itemtype is (or appears to be): %s" % typus)

        # Try to get a Kodi ID
        # If PKC was used - native paths, not direct paths
        plexid = window('plex_%s.itemid' % tryEncode(currentFile))
        # Get rid of the '' if the window property was not set
        plexid = None if not plexid else plexid
        kodiid = None
        if plexid is None:
            log.debug('Did not get Plex id from window properties')
            try:
                kodiid = data['item']['id']
            except (TypeError, KeyError):
                log.debug('Did not get a Kodi id from Kodi, darn')
        # For direct paths, if we're not streaming something
        # When using Widgets, Kodi doesn't tell us shit so we need this hack
        if (kodiid is None and plexid is None and typus != 'song'
                and not currentFile.startswith('http')):
            (kodiid, typus) = get_kodiid_from_filename(currentFile)
            if kodiid is None:
                return

        if plexid is None:
            # Get Plex' item id
            with plexdb.Get_Plex_DB() as plexcursor:
                plex_dbitem = plexcursor.getItem_byKodiId(kodiid, typus)
            try:
                plexid = plex_dbitem[0]
            except TypeError:
                log.info("No Plex id returned for kodiid %s. Aborting playback"
                         " report" % kodiid)
                return
        log.debug("Found Plex id %s for Kodi id %s for type %s" %
                  (plexid, kodiid, typus))

        # Set some stuff if Kodi initiated playback
        if ((settings('useDirectPaths') == "1" and not typus == "song")
                or (typus == "song" and settings('enableMusic') == "true")):
            if self.StartDirectPath(plexid, typus,
                                    tryEncode(currentFile)) is False:
                log.error('Could not initiate monitoring; aborting')
                return

        # Save currentFile for cleanup later and to be able to access refs
        window('plex_lastPlayedFiled', value=currentFile)
        window('plex_currently_playing_itemid', value=plexid)
        window("plex_%s.itemid" % tryEncode(currentFile), value=plexid)
        log.info('Finish playback startup')

    def StartDirectPath(self, plexid, type, currentFile):
        """
        Set some additional stuff if playback was initiated by Kodi, not PKC
        """
        xml = self.doUtils('{server}/library/metadata/%s' % plexid)
        try:
            xml[0].attrib
        except:
            log.error('Did not receive a valid XML for plexid %s.' % plexid)
            return False
        # Setup stuff, because playback was started by Kodi, not PKC
        api = API(xml[0])
        listitem = api.CreateListItemFromPlexItem()
        api.set_playback_win_props(currentFile, listitem)
        if type == "song" and settings('streamMusic') == "true":
            window('plex_%s.playmethod' % currentFile, value="DirectStream")
        else:
            window('plex_%s.playmethod' % currentFile, value="DirectPlay")
        log.debug('Window properties set for direct paths!')
예제 #4
0
class SubscriptionManager:
    def __init__(self, jsonClass, RequestMgr):
        self.serverlist = []
        self.subscribers = {}
        self.info = {}
        self.lastkey = ""
        self.containerKey = ""
        self.lastratingkey = ""
        self.volume = 0
        self.mute = '0'
        self.server = ""
        self.protocol = "http"
        self.port = ""
        self.playerprops = {}
        self.doUtils = downloadutils.DownloadUtils().downloadUrl
        self.xbmcplayer = Player()

        self.js = jsonClass
        self.RequestMgr = RequestMgr

    def getServerByHost(self, host):
        if len(self.serverlist) == 1:
            return self.serverlist[0]
        for server in self.serverlist:
            if (server.get('serverName') in host or
                    server.get('server') in host):
                return server
        return {}

    def getVolume(self):
        self.volume, self.mute = self.js.getVolume()

    def msg(self, players):
        msg = getXMLHeader()
        msg += '<MediaContainer commandID="INSERTCOMMANDID"'
        if players:
            self.getVolume()
            maintype = plex_audio()
            for p in players.values():
                if p.get('type') == xbmc_video():
                    maintype = plex_video()
                elif p.get('type') == xbmc_photo():
                    maintype = plex_photo()
            self.mainlocation = "fullScreen" + maintype[0:1].upper() + maintype[1:].lower()
        else:
            self.mainlocation = "navigation"
        msg += ' location="%s">' % self.mainlocation
        msg += self.getTimelineXML(self.js.getAudioPlayerId(players), plex_audio())
        msg += self.getTimelineXML(self.js.getPhotoPlayerId(players), plex_photo())
        msg += self.getTimelineXML(self.js.getVideoPlayerId(players), plex_video())
        msg += "\r\n</MediaContainer>"
        return msg
        
    def getTimelineXML(self, playerid, ptype):
        if playerid is not None:
            info = self.getPlayerProperties(playerid)
            # save this info off so the server update can use it too
            self.playerprops[playerid] = info;
            state = info['state']
            time = info['time']
        else:
            state = "stopped"
            time = 0
        ret = "\r\n"+'  <Timeline state="%s" time="%s" type="%s"' % (state, time, ptype)
        if playerid is None:
            ret += ' seekRange="0-0"'
            ret += ' />'
            return ret

        # pbmc_server = str(WINDOW.getProperty('plexbmc.nowplaying.server'))
        # userId = str(WINDOW.getProperty('currUserId'))
        pbmc_server = window('pms_server')
        if pbmc_server:
            (self.protocol, self.server, self.port) = \
                pbmc_server.split(':')
            self.server = self.server.replace('/', '')
        keyid = None
        count = 0
        while not keyid:
            if count > 300:
                break
            keyid = window('Plex_currently_playing_itemid')
            xbmc.sleep(100)
            count += 1
        if keyid:
            self.lastkey = "/library/metadata/%s"%keyid
            self.lastratingkey = keyid
            ret += ' location="%s"' % (self.mainlocation)
            ret += ' key="%s"' % (self.lastkey)
            ret += ' ratingKey="%s"' % (self.lastratingkey)
        serv = self.getServerByHost(self.server)
        if info.get('playQueueID'):
            self.containerKey = "/playQueues/%s" % info.get('playQueueID')
            ret += ' playQueueID="%s"' % info.get('playQueueID')
            ret += ' playQueueVersion="%s"' % info.get('playQueueVersion')
            ret += ' playQueueItemID="%s"' % (info.get('playQueueItemID'))
            ret += ' containerKey="%s"' % self.containerKey
        elif keyid:
            self.containerKey = self.lastkey
            ret += ' containerKey="%s"' % (self.containerKey)

        ret += ' duration="%s"' % info['duration']
        ret += ' seekRange="0-%s"' % info['duration']
        ret += ' controllable="%s"' % self.controllable()
        ret += ' machineIdentifier="%s"' % serv.get('uuid', "")
        ret += ' protocol="%s"' % serv.get('protocol', "http")
        ret += ' address="%s"' % serv.get('server', self.server)
        ret += ' port="%s"' % serv.get('port', self.port)
        ret += ' guid="%s"' % info['guid']
        ret += ' volume="%s"' % info['volume']
        ret += ' shuffle="%s"' % info['shuffle']
        ret += ' mute="%s"' % self.mute
        ret += ' repeat="%s"' % info['repeat']
        # Might need an update in the future
        ret += ' subtitleStreamID="-1"'
        ret += ' audioStreamID="-1"'

        ret += ' />'
        return ret

    def updateCommandID(self, uuid, commandID):
        if commandID and self.subscribers.get(uuid, False):
            self.subscribers[uuid].commandID = int(commandID)            
        
    def notify(self, event = False):
        self.cleanup()
        # Don't tell anyone if we don't know a Plex ID and are still playing
        # (e.g. no stop called). Used for e.g. PVR/TV without PKC usage
        if not window('Plex_currently_playing_itemid'):
            return True
        players = self.js.getPlayers()
        # fetch the message, subscribers or not, since the server
        # will need the info anyway
        msg = self.msg(players)
        if self.subscribers:
            with threading.RLock():
                for sub in self.subscribers.values():
                    sub.send_update(msg, len(players)==0)
        self.notifyServer(players)
        return True
    
    def notifyServer(self, players):
        if not players:
            return True
        for p in players.values():
            info = self.playerprops[p.get('playerid')]
            params = {'state': 'stopped'}
            params['containerKey'] = (self.containerKey or "/library/metadata/900000")
            if info.get('playQueueID'):
                params['containerKey'] = '/playQueues/' + info['playQueueID']
                params['playQueueVersion'] = info['playQueueVersion']
                params['playQueueItemID'] = info['playQueueItemID']
            params['key'] = (self.lastkey or "/library/metadata/900000")
            params['ratingKey'] = (self.lastratingkey or "900000")
            params['state'] = info['state']
            params['time'] = info['time']
            params['duration'] = info['duration']

            serv = self.getServerByHost(self.server)
            url = serv.get('protocol', 'http') + '://' \
                + serv.get('server', 'localhost') + ':' \
                + serv.get('port', '32400') + "/:/timeline"
            self.doUtils(url, type="GET", parameters=params)
            # requests.getwithparams(serv.get('server', 'localhost'), serv.get('port', 32400), "/:/timeline", params, getPlexHeaders(), serv.get('protocol', 'http'))
            self.logMsg("sent server notification with state = %s"
                        % params['state'], 2)

    def controllable(self):
        return "volume,shuffle,repeat,audioStream,videoStream,subtitleStream,skipPrevious,skipNext,seekTo,stepBack,stepForward,stop,playPause"

    def addSubscriber(self, protocol, host, port, uuid, commandID):
        sub = Subscriber(protocol,
                         host,
                         port,
                         uuid,
                         commandID,
                         self,
                         self.RequestMgr)
        with threading.RLock():
            self.subscribers[sub.uuid] = sub
        return sub

    def removeSubscriber(self, uuid):
        with threading.RLock():
            for sub in self.subscribers.values():
                if sub.uuid == uuid or sub.host == uuid:
                    sub.cleanup()
                    del self.subscribers[sub.uuid]

    def cleanup(self):
        with threading.RLock():
            for sub in self.subscribers.values():
                if sub.age > 30:
                    sub.cleanup()
                    del self.subscribers[sub.uuid]
            
    def getPlayerProperties(self, playerid):
        info = {}
        try:
            # get info from the player
            props = self.js.jsonrpc("Player.GetProperties", {"playerid": playerid, "properties": ["time", "totaltime", "speed", "shuffled", "repeat"]})
            self.logMsg(self.js.jsonrpc("Player.GetItem", {"playerid": playerid, "properties": ["file", "showlink", "episode", "season"]}), 2)
            info['time'] = timeToMillis(props['time'])
            info['duration'] = timeToMillis(props['totaltime'])
            info['state'] = ("paused", "playing")[int(props['speed'])]
            info['shuffle'] = ("0","1")[props.get('shuffled', False)]
            info['repeat'] = pf.getPlexRepeat(props.get('repeat'))
            # New PMS playQueue attributes
            cf = self.xbmcplayer.getPlayingFile()
            info['playQueueID'] = window('playQueueID')
            info['playQueueVersion'] = window('playQueueVersion')
            info['playQueueItemID'] = window('plex_%s.playQueueItemID' % cf)
            info['guid'] = window('plex_%s.guid' % cf)

        except:
            info['time'] = 0
            info['duration'] = 0
            info['state'] = "stopped"
            info['shuffle'] = False
        # get the volume from the application
        info['volume'] = self.volume
        info['mute'] = self.mute

        return info
예제 #5
0
class KodiMonitor(Monitor):

    def __init__(self, callback):
        self.mgr = callback
        self.doUtils = downloadutils.DownloadUtils().downloadUrl
        self.xbmcplayer = Player()
        self.playqueue = self.mgr.playqueue
        Monitor.__init__(self)
        log.info("Kodi monitor started.")

    def onScanStarted(self, library):
        log.debug("Kodi library scan %s running." % library)
        if library == "video":
            window('plex_kodiScan', value="true")

    def onScanFinished(self, library):
        log.debug("Kodi library scan %s finished." % library)
        if library == "video":
            window('plex_kodiScan', clear=True)

    def onSettingsChanged(self):
        """
        Monitor the PKC settings for changes made by the user
        """
        # settings: window-variable
        items = {
            'logLevel': 'plex_logLevel',
            'enableContext': 'plex_context',
            'plex_restricteduser': '******',
            'dbSyncIndicator': 'dbSyncIndicator',
            'remapSMB': 'remapSMB',
            'replaceSMB': 'replaceSMB',
            'force_transcode_pix': 'plex_force_transcode_pix',
            'fetch_pms_item_number': 'fetch_pms_item_number'
        }
        # Path replacement
        for typus in REMAP_TYPE_FROM_PLEXTYPE.values():
            for arg in ('Org', 'New'):
                key = 'remapSMB%s%s' % (typus, arg)
                items[key] = key
        # Reset the window variables from the settings variables
        for settings_value, window_value in items.iteritems():
            if window(window_value) != settings(settings_value):
                log.debug('PKC settings changed: %s is now %s'
                          % (settings_value, settings(settings_value)))
                window(window_value, value=settings(settings_value))
                if settings_value == 'fetch_pms_item_number':
                    log.info('Requesting playlist/nodes refresh')
                    window('plex_runLibScan', value="views")

    @CatchExceptions(warnuser=False)
    def onNotification(self, sender, method, data):

        if data:
            data = loads(data, 'utf-8')
            log.debug("Method: %s Data: %s" % (method, data))

        if method == "Player.OnPlay":
            self.PlayBackStart(data)

        elif method == "Player.OnStop":
            # Should refresh our video nodes, e.g. on deck
            # xbmc.executebuiltin('ReloadSkin()')
            pass

        elif method == "VideoLibrary.OnUpdate":
            # Manually marking as watched/unwatched
            playcount = data.get('playcount')
            item = data.get('item')
            try:
                kodiid = item['id']
                item_type = item['type']
            except (KeyError, TypeError):
                log.info("Item is invalid for playstate update.")
            else:
                # Send notification to the server.
                with plexdb.Get_Plex_DB() as plexcur:
                    plex_dbitem = plexcur.getItem_byKodiId(kodiid, item_type)
                try:
                    itemid = plex_dbitem[0]
                except TypeError:
                    log.error("Could not find itemid in plex database for a "
                              "video library update")
                else:
                    # Stop from manually marking as watched unwatched, with actual playback.
                    if window('plex_skipWatched%s' % itemid) == "true":
                        # property is set in player.py
                        window('plex_skipWatched%s' % itemid, clear=True)
                    else:
                        # notify the server
                        if playcount != 0:
                            scrobble(itemid, 'watched')
                        else:
                            scrobble(itemid, 'unwatched')

        elif method == "VideoLibrary.OnRemove":
            pass

        elif method == "System.OnSleep":
            # Connection is going to sleep
            log.info("Marking the server as offline. SystemOnSleep activated.")
            window('plex_online', value="sleep")

        elif method == "System.OnWake":
            # Allow network to wake up
            sleep(10000)
            window('plex_onWake', value="true")
            window('plex_online', value="false")

        elif method == "GUI.OnScreensaverDeactivated":
            if settings('dbSyncScreensaver') == "true":
                sleep(5000)
                window('plex_runLibScan', value="full")

    def PlayBackStart(self, data):
        """
        Called whenever a playback is started
        """
        # Get currently playing file - can take a while. Will be utf-8!
        try:
            currentFile = self.xbmcplayer.getPlayingFile()
        except:
            currentFile = None
            count = 0
            while currentFile is None:
                sleep(100)
                try:
                    currentFile = self.xbmcplayer.getPlayingFile()
                except:
                    pass
                if count == 50:
                    log.info("No current File, cancel OnPlayBackStart...")
                    return
                else:
                    count += 1
        # Just to be on the safe side
        currentFile = tryDecode(currentFile)
        log.debug("Currently playing file is: %s" % currentFile)

        # Get the type of media we're playing
        try:
            typus = data['item']['type']
        except (TypeError, KeyError):
            log.info("Item is invalid for PMS playstate update.")
            return
        log.debug("Playing itemtype is (or appears to be): %s" % typus)

        # Try to get a Kodi ID
        # If PKC was used - native paths, not direct paths
        plexid = window('plex_%s.itemid' % tryEncode(currentFile))
        # Get rid of the '' if the window property was not set
        plexid = None if not plexid else plexid
        kodiid = None
        if plexid is None:
            log.debug('Did not get Plex id from window properties')
            try:
                kodiid = data['item']['id']
            except (TypeError, KeyError):
                log.debug('Did not get a Kodi id from Kodi, darn')
        # For direct paths, if we're not streaming something
        # When using Widgets, Kodi doesn't tell us shit so we need this hack
        if (kodiid is None and plexid is None and typus != 'song'
                and not currentFile.startswith('http')):
            (kodiid, typus) = get_kodiid_from_filename(currentFile)
            if kodiid is None:
                return

        if plexid is None:
            # Get Plex' item id
            with plexdb.Get_Plex_DB() as plexcursor:
                plex_dbitem = plexcursor.getItem_byKodiId(kodiid, typus)
            try:
                plexid = plex_dbitem[0]
            except TypeError:
                log.info("No Plex id returned for kodiid %s. Aborting playback"
                         " report" % kodiid)
                return
        log.debug("Found Plex id %s for Kodi id %s for type %s"
                  % (plexid, kodiid, typus))

        # Set some stuff if Kodi initiated playback
        if ((settings('useDirectPaths') == "1" and not typus == "song")
                or
                (typus == "song" and settings('enableMusic') == "true")):
            if self.StartDirectPath(plexid,
                                    typus,
                                    tryEncode(currentFile)) is False:
                log.error('Could not initiate monitoring; aborting')
                return

        # Save currentFile for cleanup later and to be able to access refs
        window('plex_lastPlayedFiled', value=currentFile)
        window('plex_currently_playing_itemid', value=plexid)
        window("plex_%s.itemid" % tryEncode(currentFile), value=plexid)
        log.info('Finish playback startup')

    def StartDirectPath(self, plexid, type, currentFile):
        """
        Set some additional stuff if playback was initiated by Kodi, not PKC
        """
        xml = self.doUtils('{server}/library/metadata/%s' % plexid)
        try:
            xml[0].attrib
        except:
            log.error('Did not receive a valid XML for plexid %s.' % plexid)
            return False
        # Setup stuff, because playback was started by Kodi, not PKC
        api = API(xml[0])
        listitem = api.CreateListItemFromPlexItem()
        api.set_playback_win_props(currentFile, listitem)
        if type == "song" and settings('streamMusic') == "true":
            window('plex_%s.playmethod' % currentFile, value="DirectStream")
        else:
            window('plex_%s.playmethod' % currentFile, value="DirectPlay")
        log.debug('Window properties set for direct paths!')