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 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: try:
kodiid = data['item']['id'] kodi_id = json_data['id']
except (TypeError, KeyError): kodi_type = json_data['type']
log.debug('Did not get a Kodi id from Kodi, darn') except KeyError:
# For direct paths, if we're not streaming something log.info('Aborting playback report - no Kodi id for %s', json_data)
# 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 return
if plex_id is None:
# Get Plex' item id # Get Plex' item id
with plexdb.Get_Plex_DB() as plexcursor: with plexdb.Get_Plex_DB() as plex_db:
plex_dbitem = plexcursor.getItem_byKodiId(kodiid, typus) plex_dbitem = plex_db.getItem_byKodiId(kodi_id, kodi_type)
try: try:
plex_id = plex_dbitem[0] plex_id = plex_dbitem[0]
plex_type = plex_dbitem[2]
except TypeError: except TypeError:
log.info("No Plex id returned for kodiid %s. Aborting playback" # No plex id, hence item not in the library. E.g. clips
" report" % kodiid) 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 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'])
pbmc_server = window('pms_server') ret += ' duration="%s"' % kodi_time_to_millis(info['totaltime'])
if pbmc_server: ret += ' shuffle="%s"' % ("0", "1")[info['shuffled']]
(self.protocol, self.server, self.port) = \ ret += ' repeat="%s"' % v.PLEX_REPEAT_FROM_KODI_REPEAT[info['repeat']]
pbmc_server.split(':') if ptype != v.KODI_TYPE_PHOTO:
self.server = self.server.replace('/', '')
keyid = None
count = 0
while not keyid:
if count > 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')
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 += ' volume="%s"' % info['volume']
ret += ' shuffle="%s"' % info['shuffle'] ret += ' mute="%s"' % ("0", "1")[info['muted']]
ret += ' mute="%s"' % info['mute'] pbmc_server = window('pms_server')
ret += ' repeat="%s"' % info['repeat'] server = self.getServerByHost(self.server)
ret += ' itemType="%s"' % ptype if pbmc_server:
(self.protocol, self.server, self.port) = pbmc_server.split(':')
self.server = self.server.replace('/', '')
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"' % 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: 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,17 +228,16 @@ class SubscriptionManager:
del self.subscribers[sub.uuid] del self.subscribers[sub.uuid]
def getPlayerProperties(self, playerid): def getPlayerProperties(self, playerid):
try:
# Get the playqueue # Get the playqueue
playqueue = self.playqueue.playqueues[playerid] playqueue = self.playqueue.playqueues[playerid]
# get info from the player # get info from the player
props = js.get_player_props(playerid) props = state.PLAYER_STATES[playerid]
info = { info = {
'time': kodi_time_to_millis(props['time']), 'time': kodi_time_to_millis(props['time']),
'duration': kodi_time_to_millis(props['totaltime']), 'duration': kodi_time_to_millis(props['totaltime']),
'state': ("paused", "playing")[int(props['speed'])], 'state': ("paused", "playing")[int(props['speed'])],
'shuffle': ("0", "1")[props.get('shuffled', False)], 'shuffle': ("0", "1")[props.get('shuffled', False)],
'repeat': pf.getPlexRepeat(props.get('repeat')), 'repeat': v.PLEX_REPEAT_FROM_KODI_REPEAT[props.get('repeat')]
} }
pos = props['position'] pos = props['position']
try: try:
@ -257,16 +248,6 @@ class SubscriptionManager:
info['itemType'] = playqueue.items[pos].plex_type or 'null' info['itemType'] = playqueue.items[pos].plex_type or 'null'
except: except:
info['itemType'] = props.get('type') or 'null' info['itemType'] = props.get('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
}
# 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?!?
}