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!')
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!')
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
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!')