From b1e2791ca8b5afa7b5efc1f9c7f25e23de66eafa Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Sun, 10 Dec 2017 19:01:22 +0100 Subject: [PATCH] Major Plex Companion overhaul, part 1 --- resources/lib/PlexFunctions.py | 9 -- resources/lib/json_rpc.py | 27 ++++- resources/lib/kodimonitor.py | 97 +++++++--------- resources/lib/player.py | 1 + resources/lib/plexbmchelper/subscribers.py | 128 +++++++++------------ resources/lib/state.py | 29 ++++- resources/lib/variables.py | 20 ++++ 7 files changed, 165 insertions(+), 146 deletions(-) diff --git a/resources/lib/PlexFunctions.py b/resources/lib/PlexFunctions.py index 94cfcda5..ce8d9624 100644 --- a/resources/lib/PlexFunctions.py +++ b/resources/lib/PlexFunctions.py @@ -291,15 +291,6 @@ def init_plex_playqueue(itemid, librarySectionUUID, mediatype='movie', return xml -def getPlexRepeat(kodiRepeat): - plexRepeat = { - 'off': '0', - 'one': '1', - 'all': '2' # does this work?!? - } - return plexRepeat.get(kodiRepeat) - - def PMSHttpsEnabled(url): """ Returns True if the PMS can talk https, False otherwise. diff --git a/resources/lib/json_rpc.py b/resources/lib/json_rpc.py index 7667fc89..90dc7af5 100644 --- a/resources/lib/json_rpc.py +++ b/resources/lib/json_rpc.py @@ -360,6 +360,22 @@ def get_episodes(params): return ret +def get_item(playerid): + """ + Returns the following for the currently playing item: + { + u'title': u'Okja', + u'type': u'movie', + u'id': 258, + u'file': u'smb://...movie.mkv', + u'label': u'Okja' + } + """ + return jsonrpc('Player.GetItem').execute({ + 'playerid': playerid, + 'properties': ['title', 'file']})['result']['item'] + + def get_player_props(playerid): """ Returns a dict for the active Kodi player with the following values: @@ -367,14 +383,14 @@ def get_player_props(playerid): 'type' [str] the Kodi player type, e.g. 'video' 'time' The current item's time in Kodi time 'totaltime' The current item's total length in Kodi time - 'speed' [int] playback speed, defaults to 0 + 'speed' [int] playback speed, 0 is paused, 1 is playing 'shuffled' [bool] True if shuffled 'repeat' [str] 'off', 'one', 'all' 'position' [int] position in playlist (or -1) 'playlistid' [int] the Kodi playlist id (or -1) } """ - ret = jsonrpc('Player.GetProperties').execute({ + return jsonrpc('Player.GetProperties').execute({ 'playerid': playerid, 'properties': ['type', 'time', @@ -383,8 +399,11 @@ def get_player_props(playerid): 'shuffled', 'repeat', 'position', - 'playlistid']}) - return ret['result'] + 'playlistid', + 'currentvideostream', + 'currentaudiostream', + 'subtitleenabled', + 'currentsubtitle']})['result'] def current_audiostream(playerid): diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index 6bc0639a..3cb82380 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -13,7 +13,9 @@ from utils import window, settings, CatchExceptions, tryDecode, tryEncode, \ from PlexFunctions import scrobble from kodidb_functions import get_kodiid_from_filename from PlexAPI import API +import json_rpc as js import state +import variables as v ############################################################################### @@ -178,69 +180,48 @@ class KodiMonitor(Monitor): def PlayBackStart(self, data): """ - Called whenever a playback is started + Called whenever a playback is started. Example data: + { + u'item': {u'type': u'movie', u'title': u''}, + u'player': {u'playerid': 1, u'speed': 1} + } """ - # 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) - + log.debug('PlayBackStart called with: %s', data) # Get the type of media we're playing try: - typus = data['item']['type'] + kodi_type = data['item']['type'] + playerid = data['player']['playerid'] + json_data = js.get_item(playerid) except (TypeError, KeyError): - log.info("Item is invalid for PMS playstate update.") + log.info('Aborting playback report - item is invalid for updates') 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)) + try: + kodi_id = json_data['id'] + kodi_type = json_data['type'] + except KeyError: + log.info('Aborting playback report - no Kodi id for %s', json_data) + return + # Get Plex' item id + with plexdb.Get_Plex_DB() as plex_db: + plex_dbitem = plex_db.getItem_byKodiId(kodi_id, kodi_type) + try: + plex_id = plex_dbitem[0] + plex_type = plex_dbitem[2] + except TypeError: + # No plex id, hence item not in the library. E.g. clips + plex_id = None + plex_type = None + state.PLAYER_STATES[playerid].update(js.get_player_props(playerid)) + state.PLAYER_STATES[playerid]['file'] = json_data['file'] + state.PLAYER_STATES[playerid]['kodi_id'] = kodi_id + state.PLAYER_STATES[playerid]['kodi_type'] = kodi_type + state.PLAYER_STATES[playerid]['plex_id'] = plex_id + state.PLAYER_STATES[playerid]['plex_type'] = plex_type + log.debug('Set the player state %s', state.PLAYER_STATES[playerid]) + # Set other stuff like volume + state.PLAYER_STATES[playerid]['volume'] = js.get_volume() + state.PLAYER_STATES[playerid]['muted'] = js.get_muted() + return # Switch subtitle tracks if applicable subtitle = window('plex_%s.subtitle' % tryEncode(currentFile)) diff --git a/resources/lib/player.py b/resources/lib/player.py index 85d971d2..e1d8e2ac 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -37,6 +37,7 @@ class PKC_Player(Player): Will be called when xbmc starts playing a file. Window values need to have been set in Kodimonitor.py """ + return self.stopAll() # Get current file (in utf-8!) diff --git a/resources/lib/plexbmchelper/subscribers.py b/resources/lib/plexbmchelper/subscribers.py index ebdb8fe5..4183428d 100644 --- a/resources/lib/plexbmchelper/subscribers.py +++ b/resources/lib/plexbmchelper/subscribers.py @@ -72,70 +72,62 @@ class SubscriptionManager: msg += self.getTimelineXML(players.get(v.KODI_TYPE_VIDEO), v.PLEX_TYPE_VIDEO) msg += "\n" + log.debug('msg is: %s', msg) return msg def getTimelineXML(self, player, ptype): if player is None: status = 'stopped' - time = 0 else: playerid = player['playerid'] - info = self.getPlayerProperties(playerid) + info = state.PLAYER_STATES[playerid] # save this info off so the server update can use it too - self.playerprops[playerid] = info - status = info['state'] - time = info['time'] + # self.playerprops[playerid] = info + status = ("paused", "playing")[info['speed']] ret = ('\n 30: - break - keyid = window('plex_currently_playing_itemid') - sleep(100) - count += 1 - if keyid: - self.lastkey = "/library/metadata/%s" % keyid - self.ratingkey = keyid - ret += ' key="%s"' % self.lastkey - ret += ' ratingKey="%s"' % self.ratingkey - 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') + if info['plex_id']: + self.lastkey = "/library/metadata/%s" % info['plex_id'] + self.ratingkey = info['plex_id'] + ret += ' key="/library/metadata/%s"' % info['plex_id'] + ret += ' ratingKey="%s"' % info['plex_id'] + # PlayQueue stuff + playqueue = self.playqueue.playqueues[playerid] + pos = info['position'] + try: + ret += ' playQueueItemID="%s"' % playqueue.items[pos].ID or 'null' + self.containerKey = "/playQueues/%s" % playqueue.ID or 'null' + ret += ' playQueueID="%s"' % playqueue.ID or 'null' + ret += ' playQueueVersion="%s"' % playqueue.version or 'null' ret += ' containerKey="%s"' % self.containerKey - ret += ' guid="%s"' % info['guid'] - elif keyid: - self.containerKey = self.lastkey - ret += ' containerKey="%s"' % self.containerKey - - ret += ' duration="%s"' % info['duration'] - 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 += ' volume="%s"' % info['volume'] - ret += ' shuffle="%s"' % info['shuffle'] - ret += ' mute="%s"' % info['mute'] - ret += ' repeat="%s"' % info['repeat'] - ret += ' itemType="%s"' % ptype + ret += ' guid="%s"' % playqueue.items[pos].guid or 'null' + except IndexError: + pass + ret += ' machineIdentifier="%s"' % server.get('uuid', "") + ret += ' protocol="%s"' % server.get('protocol', 'http') + ret += ' address="%s"' % server.get('server', self.server) + ret += ' port="%s"' % server.get('port', self.port) + # Temp. token set? if state.PLEX_TRANSIENT_TOKEN: ret += ' token="%s"' % state.PLEX_TRANSIENT_TOKEN - elif info['plex_transient_token']: - ret += ' token="%s"' % info['plex_transient_token'] + elif playqueue.plex_transient_token: + ret += ' token="%s"' % playqueue.plex_transient_token # Might need an update in the future if ptype == 'video': ret += ' subtitleStreamID="-1"' @@ -236,37 +228,26 @@ class SubscriptionManager: del self.subscribers[sub.uuid] def getPlayerProperties(self, playerid): + # Get the playqueue + playqueue = self.playqueue.playqueues[playerid] + # get info from the player + props = state.PLAYER_STATES[playerid] + info = { + 'time': kodi_time_to_millis(props['time']), + 'duration': kodi_time_to_millis(props['totaltime']), + 'state': ("paused", "playing")[int(props['speed'])], + 'shuffle': ("0", "1")[props.get('shuffled', False)], + 'repeat': v.PLEX_REPEAT_FROM_KODI_REPEAT[props.get('repeat')] + } + pos = props['position'] try: - # Get the playqueue - playqueue = self.playqueue.playqueues[playerid] - # get info from the player - props = js.get_player_props(playerid) - info = { - 'time': kodi_time_to_millis(props['time']), - 'duration': kodi_time_to_millis(props['totaltime']), - 'state': ("paused", "playing")[int(props['speed'])], - 'shuffle': ("0", "1")[props.get('shuffled', False)], - 'repeat': pf.getPlexRepeat(props.get('repeat')), - } - pos = props['position'] - try: - info['playQueueItemID'] = playqueue.items[pos].ID or 'null' - info['guid'] = playqueue.items[pos].guid or 'null' - info['playQueueID'] = playqueue.ID or 'null' - info['playQueueVersion'] = playqueue.version or 'null' - info['itemType'] = playqueue.items[pos].plex_type or 'null' - except: - info['itemType'] = props.get('type') or 'null' + info['playQueueItemID'] = playqueue.items[pos].ID or 'null' + info['guid'] = playqueue.items[pos].guid or 'null' + info['playQueueID'] = playqueue.ID or 'null' + info['playQueueVersion'] = playqueue.version or 'null' + info['itemType'] = playqueue.items[pos].plex_type or 'null' except: - import traceback - log.error("Traceback:\n%s" % traceback.format_exc()) - info = { - 'time': 0, - 'duration': 0, - 'state': 'stopped', - 'shuffle': False, - 'repeat': 0 - } + info['itemType'] = props.get('type') or 'null' # get the volume from the application info['volume'] = js.get_volume() @@ -323,5 +304,6 @@ class Subscriber: response = self.doUtils(url, postBody=msg, action_type="POST") + log.debug('response is: %s', response) if response in [False, None, 401]: self.subMgr.removeSubscriber(self.uuid) diff --git a/resources/lib/state.py b/resources/lib/state.py index 2020fc71..c88a8224 100644 --- a/resources/lib/state.py +++ b/resources/lib/state.py @@ -75,8 +75,33 @@ PLEX_USER_ID = None # another user playing something! Token identifies user PLEX_TRANSIENT_TOKEN = None -# Kodi player states -PLAYER_STATES = {} +# Kodi player states - here, initial values are set +PLAYER_STATES = { + 1: { + 'type': 'movie', + 'time': 0, + 'totaltime': 0, + 'speed': 0, + 'shuffled': False, + 'repeat': '0', + 'position': -1, + 'playlistid': -1, + 'currentvideostream': -1, + 'currentaudiostream': -1, + 'subtitleenabled': False, + 'currentsubtitle': -1, + ###### + 'file': '', + 'kodi_id': None, + 'kodi_type': None, + 'plex_id': None, + 'plex_type': None, + 'volume': 100, + 'muted': False + }, + 2: {}, + 3: {} +} PLAYED_INFO = {} # Kodi webserver details diff --git a/resources/lib/variables.py b/resources/lib/variables.py index 9dcc4560..d660f22f 100644 --- a/resources/lib/variables.py +++ b/resources/lib/variables.py @@ -214,6 +214,20 @@ KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE = { } +KODI_PLAYLIST_TYPE_FROM_KODI_TYPE = { + KODI_TYPE_VIDEO: KODI_TYPE_VIDEO, + KODI_TYPE_MOVIE: KODI_TYPE_VIDEO, + KODI_TYPE_EPISODE: KODI_TYPE_VIDEO, + KODI_TYPE_SEASON: KODI_TYPE_VIDEO, + KODI_TYPE_SHOW: KODI_TYPE_VIDEO, + KODI_TYPE_CLIP: KODI_TYPE_VIDEO, + KODI_TYPE_ARTIST: KODI_TYPE_AUDIO, + KODI_TYPE_ALBUM: KODI_TYPE_AUDIO, + KODI_TYPE_SONG: KODI_TYPE_AUDIO, + KODI_TYPE_AUDIO: KODI_TYPE_AUDIO, + KODI_TYPE_PHOTO: KODI_TYPE_PHOTO +} + REMAP_TYPE_FROM_PLEXTYPE = { PLEX_TYPE_MOVIE: 'movie', PLEX_TYPE_CLIP: 'clip', @@ -371,3 +385,9 @@ SORT_METHODS_ALBUMS = ( XML_HEADER = '\n' COMPANION_OK_MESSAGE = XML_HEADER + '' + +PLEX_REPEAT_FROM_KODI_REPEAT = { + 'off': '0', + 'one': '1', + 'all': '2' # does this work?!? +}