Major Plex Companion overhaul, part 1

This commit is contained in:
tomkat83 2017-12-10 19:01:22 +01:00
parent c3b5054477
commit b1e2791ca8
7 changed files with 165 additions and 146 deletions

View file

@ -291,15 +291,6 @@ def init_plex_playqueue(itemid, librarySectionUUID, mediatype='movie',
return xml return xml
def getPlexRepeat(kodiRepeat):
plexRepeat = {
'off': '0',
'one': '1',
'all': '2' # does this work?!?
}
return plexRepeat.get(kodiRepeat)
def PMSHttpsEnabled(url): def PMSHttpsEnabled(url):
""" """
Returns True if the PMS can talk https, False otherwise. Returns True if the PMS can talk https, False otherwise.

View file

@ -360,6 +360,22 @@ def get_episodes(params):
return ret 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): def get_player_props(playerid):
""" """
Returns a dict for the active Kodi player with the following values: 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' 'type' [str] the Kodi player type, e.g. 'video'
'time' The current item's time in Kodi time 'time' The current item's time in Kodi time
'totaltime' The current item's total length 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 'shuffled' [bool] True if shuffled
'repeat' [str] 'off', 'one', 'all' 'repeat' [str] 'off', 'one', 'all'
'position' [int] position in playlist (or -1) 'position' [int] position in playlist (or -1)
'playlistid' [int] the Kodi playlist id (or -1) 'playlistid' [int] the Kodi playlist id (or -1)
} }
""" """
ret = jsonrpc('Player.GetProperties').execute({ return jsonrpc('Player.GetProperties').execute({
'playerid': playerid, 'playerid': playerid,
'properties': ['type', 'properties': ['type',
'time', 'time',
@ -383,8 +399,11 @@ def get_player_props(playerid):
'shuffled', 'shuffled',
'repeat', 'repeat',
'position', 'position',
'playlistid']}) 'playlistid',
return ret['result'] 'currentvideostream',
'currentaudiostream',
'subtitleenabled',
'currentsubtitle']})['result']
def current_audiostream(playerid): def current_audiostream(playerid):

View file

@ -13,7 +13,9 @@ from utils import window, settings, CatchExceptions, tryDecode, tryEncode, \
from PlexFunctions import scrobble from PlexFunctions import scrobble
from kodidb_functions import get_kodiid_from_filename from kodidb_functions import get_kodiid_from_filename
from PlexAPI import API from PlexAPI import API
import json_rpc as js
import state import state
import variables as v
############################################################################### ###############################################################################
@ -178,69 +180,48 @@ class KodiMonitor(Monitor):
def PlayBackStart(self, data): 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! log.debug('PlayBackStart called with: %s', data)
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 # Get the type of media we're playing
try: try:
typus = data['item']['type'] kodi_type = data['item']['type']
playerid = data['player']['playerid']
json_data = js.get_item(playerid)
except (TypeError, KeyError): except (TypeError, KeyError):
log.info("Item is invalid for PMS playstate update.") log.info('Aborting playback report - item is invalid for updates')
return return
log.debug("Playing itemtype is (or appears to be): %s" % typus) try:
kodi_id = json_data['id']
# Try to get a Kodi ID kodi_type = json_data['type']
# If PKC was used - native paths, not direct paths except KeyError:
plex_id = window('plex_%s.itemid' % tryEncode(currentFile)) log.info('Aborting playback report - no Kodi id for %s', json_data)
# Get rid of the '' if the window property was not set return
plex_id = None if not plex_id else plex_id # Get Plex' item id
kodiid = None with plexdb.Get_Plex_DB() as plex_db:
if plex_id is None: plex_dbitem = plex_db.getItem_byKodiId(kodi_id, kodi_type)
log.debug('Did not get Plex id from window properties') try:
try: plex_id = plex_dbitem[0]
kodiid = data['item']['id'] plex_type = plex_dbitem[2]
except (TypeError, KeyError): except TypeError:
log.debug('Did not get a Kodi id from Kodi, darn') # No plex id, hence item not in the library. E.g. clips
# For direct paths, if we're not streaming something plex_id = None
# When using Widgets, Kodi doesn't tell us shit so we need this hack plex_type = None
if (kodiid is None and plex_id is None and typus != 'song' state.PLAYER_STATES[playerid].update(js.get_player_props(playerid))
and not currentFile.startswith('http')): state.PLAYER_STATES[playerid]['file'] = json_data['file']
(kodiid, typus) = get_kodiid_from_filename(currentFile) state.PLAYER_STATES[playerid]['kodi_id'] = kodi_id
if kodiid is None: state.PLAYER_STATES[playerid]['kodi_type'] = kodi_type
return state.PLAYER_STATES[playerid]['plex_id'] = plex_id
state.PLAYER_STATES[playerid]['plex_type'] = plex_type
if plex_id is None: log.debug('Set the player state %s', state.PLAYER_STATES[playerid])
# Get Plex' item id # Set other stuff like volume
with plexdb.Get_Plex_DB() as plexcursor: state.PLAYER_STATES[playerid]['volume'] = js.get_volume()
plex_dbitem = plexcursor.getItem_byKodiId(kodiid, typus) state.PLAYER_STATES[playerid]['muted'] = js.get_muted()
try: return
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 # Switch subtitle tracks if applicable
subtitle = window('plex_%s.subtitle' % tryEncode(currentFile)) subtitle = window('plex_%s.subtitle' % tryEncode(currentFile))

View file

@ -37,6 +37,7 @@ class PKC_Player(Player):
Will be called when xbmc starts playing a file. Will be called when xbmc starts playing a file.
Window values need to have been set in Kodimonitor.py Window values need to have been set in Kodimonitor.py
""" """
return
self.stopAll() self.stopAll()
# Get current file (in utf-8!) # Get current file (in utf-8!)

View file

@ -72,70 +72,62 @@ class SubscriptionManager:
msg += self.getTimelineXML(players.get(v.KODI_TYPE_VIDEO), msg += self.getTimelineXML(players.get(v.KODI_TYPE_VIDEO),
v.PLEX_TYPE_VIDEO) v.PLEX_TYPE_VIDEO)
msg += "\n</MediaContainer>" msg += "\n</MediaContainer>"
log.debug('msg is: %s', msg)
return msg return msg
def getTimelineXML(self, player, ptype): def getTimelineXML(self, player, ptype):
if player is None: if player is None:
status = 'stopped' status = 'stopped'
time = 0
else: else:
playerid = player['playerid'] playerid = player['playerid']
info = self.getPlayerProperties(playerid) info = state.PLAYER_STATES[playerid]
# save this info off so the server update can use it too # save this info off so the server update can use it too
self.playerprops[playerid] = info # self.playerprops[playerid] = info
status = info['state'] status = ("paused", "playing")[info['speed']]
time = info['time']
ret = ('\n <Timeline state="%s" controllable="%s" type="%s" ' ret = ('\n <Timeline state="%s" controllable="%s" type="%s" '
'itemType="%s"' % (status, CONTROLLABLE[ptype], ptype, ptype)) 'itemType="%s"' % (status, CONTROLLABLE[ptype], ptype, ptype))
if player is None: if player is None:
ret += ' />' ret += ' />'
return ret return ret
ret += ' time="%s"' % time ret += ' time="%s"' % kodi_time_to_millis(info['time'])
ret += ' duration="%s"' % kodi_time_to_millis(info['totaltime'])
ret += ' shuffle="%s"' % ("0", "1")[info['shuffled']]
ret += ' repeat="%s"' % v.PLEX_REPEAT_FROM_KODI_REPEAT[info['repeat']]
if ptype != v.KODI_TYPE_PHOTO:
ret += ' volume="%s"' % info['volume']
ret += ' mute="%s"' % ("0", "1")[info['muted']]
pbmc_server = window('pms_server') pbmc_server = window('pms_server')
server = self.getServerByHost(self.server)
if pbmc_server: if pbmc_server:
(self.protocol, self.server, self.port) = \ (self.protocol, self.server, self.port) = pbmc_server.split(':')
pbmc_server.split(':')
self.server = self.server.replace('/', '') self.server = self.server.replace('/', '')
keyid = None if info['plex_id']:
count = 0 self.lastkey = "/library/metadata/%s" % info['plex_id']
while not keyid: self.ratingkey = info['plex_id']
if count > 30: ret += ' key="/library/metadata/%s"' % info['plex_id']
break ret += ' ratingKey="%s"' % info['plex_id']
keyid = window('plex_currently_playing_itemid') # PlayQueue stuff
sleep(100) playqueue = self.playqueue.playqueues[playerid]
count += 1 pos = info['position']
if keyid: try:
self.lastkey = "/library/metadata/%s" % keyid ret += ' playQueueItemID="%s"' % playqueue.items[pos].ID or 'null'
self.ratingkey = keyid self.containerKey = "/playQueues/%s" % playqueue.ID or 'null'
ret += ' key="%s"' % self.lastkey ret += ' playQueueID="%s"' % playqueue.ID or 'null'
ret += ' ratingKey="%s"' % self.ratingkey ret += ' playQueueVersion="%s"' % playqueue.version or 'null'
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 ret += ' containerKey="%s"' % self.containerKey
ret += ' guid="%s"' % info['guid'] ret += ' guid="%s"' % playqueue.items[pos].guid or 'null'
elif keyid: except IndexError:
self.containerKey = self.lastkey pass
ret += ' containerKey="%s"' % self.containerKey ret += ' machineIdentifier="%s"' % server.get('uuid', "")
ret += ' protocol="%s"' % server.get('protocol', 'http')
ret += ' duration="%s"' % info['duration'] ret += ' address="%s"' % server.get('server', self.server)
ret += ' machineIdentifier="%s"' % serv.get('uuid', "") ret += ' port="%s"' % server.get('port', self.port)
ret += ' protocol="%s"' % serv.get('protocol', "http") # Temp. token set?
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
if state.PLEX_TRANSIENT_TOKEN: if state.PLEX_TRANSIENT_TOKEN:
ret += ' token="%s"' % state.PLEX_TRANSIENT_TOKEN ret += ' token="%s"' % state.PLEX_TRANSIENT_TOKEN
elif info['plex_transient_token']: elif playqueue.plex_transient_token:
ret += ' token="%s"' % info['plex_transient_token'] ret += ' token="%s"' % playqueue.plex_transient_token
# Might need an update in the future # Might need an update in the future
if ptype == 'video': if ptype == 'video':
ret += ' subtitleStreamID="-1"' ret += ' subtitleStreamID="-1"'
@ -236,37 +228,26 @@ class SubscriptionManager:
del self.subscribers[sub.uuid] del self.subscribers[sub.uuid]
def getPlayerProperties(self, playerid): 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: try:
# Get the playqueue info['playQueueItemID'] = playqueue.items[pos].ID or 'null'
playqueue = self.playqueue.playqueues[playerid] info['guid'] = playqueue.items[pos].guid or 'null'
# get info from the player info['playQueueID'] = playqueue.ID or 'null'
props = js.get_player_props(playerid) info['playQueueVersion'] = playqueue.version or 'null'
info = { info['itemType'] = playqueue.items[pos].plex_type or 'null'
'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'
except: except:
import traceback info['itemType'] = props.get('type') or 'null'
log.error("Traceback:\n%s" % traceback.format_exc())
info = {
'time': 0,
'duration': 0,
'state': 'stopped',
'shuffle': False,
'repeat': 0
}
# get the volume from the application # get the volume from the application
info['volume'] = js.get_volume() info['volume'] = js.get_volume()
@ -323,5 +304,6 @@ class Subscriber:
response = self.doUtils(url, response = self.doUtils(url,
postBody=msg, postBody=msg,
action_type="POST") action_type="POST")
log.debug('response is: %s', response)
if response in [False, None, 401]: if response in [False, None, 401]:
self.subMgr.removeSubscriber(self.uuid) self.subMgr.removeSubscriber(self.uuid)

View file

@ -75,8 +75,33 @@ PLEX_USER_ID = None
# another user playing something! Token identifies user # another user playing something! Token identifies user
PLEX_TRANSIENT_TOKEN = None PLEX_TRANSIENT_TOKEN = None
# Kodi player states # Kodi player states - here, initial values are set
PLAYER_STATES = {} 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 = {} PLAYED_INFO = {}
# Kodi webserver details # Kodi webserver details

View file

@ -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 = { REMAP_TYPE_FROM_PLEXTYPE = {
PLEX_TYPE_MOVIE: 'movie', PLEX_TYPE_MOVIE: 'movie',
PLEX_TYPE_CLIP: 'clip', PLEX_TYPE_CLIP: 'clip',
@ -371,3 +385,9 @@ SORT_METHODS_ALBUMS = (
XML_HEADER = '<?xml version="1.0" encoding="UTF-8"?>\n' XML_HEADER = '<?xml version="1.0" encoding="UTF-8"?>\n'
COMPANION_OK_MESSAGE = XML_HEADER + '<Response code="200" status="OK" />' COMPANION_OK_MESSAGE = XML_HEADER + '<Response code="200" status="OK" />'
PLEX_REPEAT_FROM_KODI_REPEAT = {
'off': '0',
'one': '1',
'all': '2' # does this work?!?
}