diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py
index 3dc6887d..e0c36a16 100644
--- a/resources/lib/PlexAPI.py
+++ b/resources/lib/PlexAPI.py
@@ -2690,7 +2690,8 @@ class API():
plexitem = "plex_%s" % playurl
window('%s.runtime' % plexitem, value=str(userdata['Runtime']))
window('%s.type' % plexitem, value=itemtype)
- window('%s.itemid' % plexitem, value=self.getRatingKey())
+ state.PLEX_IDS[tryDecode(playurl)] = self.getRatingKey()
+ # window('%s.itemid' % plexitem, value=self.getRatingKey())
window('%s.playcount' % plexitem, value=str(userdata['PlayCount']))
if itemtype == v.PLEX_TYPE_EPISODE:
diff --git a/resources/lib/json_rpc.py b/resources/lib/json_rpc.py
index 90dc7af5..0e0dadcb 100644
--- a/resources/lib/json_rpc.py
+++ b/resources/lib/json_rpc.py
@@ -362,6 +362,7 @@ def get_episodes(params):
def get_item(playerid):
"""
+ UNRELIABLE on playback startup! (as other JSON and Python Kodi functions)
Returns the following for the currently playing item:
{
u'title': u'Okja',
diff --git a/resources/lib/kodidb_functions.py b/resources/lib/kodidb_functions.py
index 6d31faa0..653a019a 100644
--- a/resources/lib/kodidb_functions.py
+++ b/resources/lib/kodidb_functions.py
@@ -808,7 +808,7 @@ class Kodidb_Functions():
ids.append(row[0])
return ids
- def getIdFromFilename(self, filename, path):
+ def video_id_from_filename(self, filename, path):
"""
Returns the tuple (itemId, type) where
itemId: Kodi DB unique Id for either movie or episode
@@ -884,6 +884,34 @@ class Kodidb_Functions():
return
return itemId, typus
+ def music_id_from_filename(self, filename, path):
+ """
+ Returns the Kodi song_id from the Kodi music database or None if not
+ found OR something went wrong.
+ """
+ query = '''
+ SELECT idPath
+ FROM path
+ WHERE strPath = ?
+ '''
+ self.cursor.execute(query, (path,))
+ path_id = self.cursor.fetchall()
+ if len(path_id) != 1:
+ log.error('Found wrong number of path ids: %s for path %s, abort',
+ path_id, path)
+ return
+ query = '''
+ SELECT idSong
+ FROM song
+ WHERE strFileName = ? AND idPath = ?
+ '''
+ self.cursor.execute(query, (filename, path_id[0]))
+ song_id = self.cursor.fetchall()
+ if len(song_id) != 1:
+ log.info('Found wrong number of songs %s, abort', song_id)
+ return
+ return song_id[0]
+
def getUnplayedItems(self):
"""
VIDEOS
@@ -1522,24 +1550,29 @@ class Kodidb_Functions():
self.cursor.execute(query, (kodi_id, kodi_type))
-def get_kodiid_from_filename(file):
+def kodiid_from_filename(path, kodi_type):
"""
- Returns the tuple (kodiid, type) if we have a video in the database with
- said filename, or (None, None)
+ Returns kodi_id if we have an item in the Kodi video or audio database with
+ said path. Feed with the Kodi itemtype, e.v. 'movie', 'song'
+ Returns None if not possible
"""
- kodiid = None
- typus = None
+ kodi_id = None
try:
- filename = file.rsplit('/', 1)[1]
- path = file.rsplit('/', 1)[0] + '/'
+ filename = path.rsplit('/', 1)[1]
+ path = path.rsplit('/', 1)[0] + '/'
except IndexError:
- filename = file.rsplit('\\', 1)[1]
- path = file.rsplit('\\', 1)[0] + '\\'
- log.debug('Trying to figure out playing item from filename: %s '
- 'and path: %s' % (filename, path))
- with GetKodiDB('video') as kodi_db:
- try:
- kodiid, typus = kodi_db.getIdFromFilename(filename, path)
- except TypeError:
- log.info('No kodi video element found with filename %s' % filename)
- return (kodiid, typus)
+ filename = path.rsplit('\\', 1)[1]
+ path = path.rsplit('\\', 1)[0] + '\\'
+ if kodi_type == v.KODI_TYPE_SONG:
+ with GetKodiDB('music') as kodi_db:
+ try:
+ kodi_id, _ = kodi_db.music_id_from_filename(filename, path)
+ except TypeError:
+ log.info('No Kodi audio db element found for path %s', path)
+ else:
+ with GetKodiDB('video') as kodi_db:
+ try:
+ kodi_id, _ = kodi_db.video_id_from_filename(filename, path)
+ except TypeError:
+ log.info('No kodi video db element found for path %s', path)
+ return kodi_id
diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py
index 3cb82380..d5d2fd00 100644
--- a/resources/lib/kodimonitor.py
+++ b/resources/lib/kodimonitor.py
@@ -11,7 +11,7 @@ import plexdb_functions as plexdb
from utils import window, settings, CatchExceptions, tryDecode, tryEncode, \
plex_command
from PlexFunctions import scrobble
-from kodidb_functions import get_kodiid_from_filename
+from kodidb_functions import kodiid_from_filename
from PlexAPI import API
import json_rpc as js
import state
@@ -185,68 +185,61 @@ class KodiMonitor(Monitor):
u'item': {u'type': u'movie', u'title': u''},
u'player': {u'playerid': 1, u'speed': 1}
}
+ Unfortunately VERY random inputs!
+ E.g. when using Widgets, Kodi doesn't tell us shit
"""
- log.debug('PlayBackStart called with: %s', data)
# Get the type of media we're playing
try:
kodi_type = data['item']['type']
playerid = data['player']['playerid']
- json_data = js.get_item(playerid)
except (TypeError, KeyError):
- log.info('Aborting playback report - item is invalid for updates')
+ log.info('Aborting playback report - item invalid for updates %s',
+ data)
return
+ json_data = js.get_item(playerid)
+ path = json_data.get('file')
+ kodi_id = json_data.get('id')
+ if not path and not kodi_id:
+ log.info('Aborting playback report - no Kodi id or file for %s',
+ json_data)
+ return
+ # Plex id will NOT be set with direct paths
+ plex_id = state.PLEX_IDS.get(path)
try:
- kodi_id = json_data['id']
- kodi_type = json_data['type']
+ plex_type = v.PLEX_TYPE_FROM_KODI_TYPE[kodi_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
+ # No Kodi id returned by Kodi, even if there is one. Ex: Widgets
+ if plex_id and not kodi_id:
+ with plexdb.Get_Plex_DB() as plex_db:
+ plex_dbitem = plex_db.getItem_byId(plex_id)
+ try:
+ kodi_id = plex_dbitem[0]
+ except TypeError:
+ kodi_id = None
+ # If using direct paths and starting playback from a widget
+ if not path.startswith('http'):
+ if not kodi_id:
+ kodi_id = kodiid_from_filename(path, kodi_type)
+ if not plex_id and kodi_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
+ pass
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))
- 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')
+ log.debug('Set the player state: %s', state.PLAYER_STATES[playerid])
def StartDirectPath(self, plex_id, type, currentFile):
"""
diff --git a/resources/lib/plexbmchelper/subscribers.py b/resources/lib/plexbmchelper/subscribers.py
index 4183428d..d45744d3 100644
--- a/resources/lib/plexbmchelper/subscribers.py
+++ b/resources/lib/plexbmchelper/subscribers.py
@@ -1,56 +1,68 @@
-import logging
-import re
-import threading
-
-from xbmc import sleep
+"""
+Manages getting playstate from Kodi and sending it to the PMS as well as
+subscribed Plex Companion clients.
+"""
+from logging import getLogger
+from re import sub
+from threading import Thread, RLock
import downloadutils
-from clientinfo import getXArgsDeviceInfo
from utils import window, kodi_time_to_millis
-import PlexFunctions as pf
import state
import variables as v
import json_rpc as js
###############################################################################
-log = logging.getLogger("PLEX."+__name__)
+LOG = getLogger("PLEX." + __name__)
###############################################################################
# What is Companion controllable?
CONTROLLABLE = {
v.PLEX_TYPE_PHOTO: 'skipPrevious,skipNext,stop',
- v.PLEX_TYPE_AUDIO: 'playPause,stop,volume,shuffle,repeat,seekTo,' \
+ v.PLEX_TYPE_AUDIO: 'playPause,stop,volume,shuffle,repeat,seekTo,'
'skipPrevious,skipNext,stepBack,stepForward',
- v.PLEX_TYPE_VIDEO: 'playPause,stop,volume,audioStream,subtitleStream,' \
- 'seekTo,skipPrevious,skipNext,stepBack,stepForward'
+ v.PLEX_TYPE_VIDEO: 'playPause,stop,volume,shuffle,audioStream,'
+ 'subtitleStream,seekTo,skipPrevious,skipNext,stepBack,stepForward'
}
class SubscriptionManager:
+ """
+ Manages Plex companion subscriptions
+ """
def __init__(self, RequestMgr, player, mgr):
self.serverlist = []
self.subscribers = {}
self.info = {}
- self.lastkey = ""
- self.containerKey = ""
- self.ratingkey = ""
- self.lastplayers = {}
- self.lastinfo = {
- 'video': {},
- 'audio': {},
- 'picture': {}
- }
+ self.containerKey = None
+ self.ratingkey = None
self.server = ""
self.protocol = "http"
self.port = ""
- self.playerprops = {}
- self.doUtils = downloadutils.DownloadUtils().downloadUrl
+ # In order to be able to signal a stop at the end
+ self.last_params = {}
+ self.lastplayers = {}
+
+ self.doUtils = downloadutils.DownloadUtils
self.xbmcplayer = player
self.playqueue = mgr.playqueue
-
self.RequestMgr = RequestMgr
+ @staticmethod
+ def _headers():
+ """
+ Headers are different for Plex Companion!
+ """
+ return {
+ 'Content-type': 'text/plain',
+ 'Connection': 'Keep-Alive',
+ 'Keep-Alive': 'timeout=20',
+ 'X-Plex-Client-Identifier': v.PKC_MACHINE_IDENTIFIER,
+ 'Access-Control-Expose-Headers': 'X-Plex-Client-Identifier',
+ 'X-Plex-Protocol': "1.0"
+ }
+
def getServerByHost(self, host):
if len(self.serverlist) == 1:
return self.serverlist[0]
@@ -61,64 +73,80 @@ class SubscriptionManager:
return {}
def msg(self, players):
- log.debug('players: %s', players)
+ LOG.debug('players: %s', players)
msg = v.XML_HEADER
msg += '' % v.PKC_MACHINE_IDENTIFIER
- msg += self.getTimelineXML(players.get(v.KODI_TYPE_AUDIO),
- v.PLEX_TYPE_AUDIO)
- msg += self.getTimelineXML(players.get(v.KODI_TYPE_PHOTO),
- v.PLEX_TYPE_PHOTO)
- msg += self.getTimelineXML(players.get(v.KODI_TYPE_VIDEO),
- v.PLEX_TYPE_VIDEO)
- msg += "\n"
- log.debug('msg is: %s', msg)
+ msg += ' machineIdentifier="%s">\n' % v.PKC_MACHINE_IDENTIFIER
+ msg += self.get_timeline_xml(players.get(v.KODI_TYPE_AUDIO),
+ v.PLEX_TYPE_AUDIO)
+ msg += self.get_timeline_xml(players.get(v.KODI_TYPE_PHOTO),
+ v.PLEX_TYPE_PHOTO)
+ msg += self.get_timeline_xml(players.get(v.KODI_TYPE_VIDEO),
+ v.PLEX_TYPE_VIDEO)
+ msg += ""
+ LOG.debug('msg is: %s', msg)
return msg
- def getTimelineXML(self, player, ptype):
- if player is None:
- status = 'stopped'
+ def _get_container_key(self, playerid):
+ key = None
+ playlistid = state.PLAYER_STATES[playerid]['playlistid']
+ LOG.debug('type: %s, playlistid: %s', type(playlistid), playlistid)
+ if playlistid != -1:
+ # -1 is Kodi's answer if there is no playlist
+ try:
+ key = self.playqueue.playqueues[playlistid].id
+ except (KeyError, IndexError, TypeError):
+ pass
+ if key is not None:
+ key = '/playQueues/%s' % key
else:
- playerid = player['playerid']
- info = state.PLAYER_STATES[playerid]
- # save this info off so the server update can use it too
- # self.playerprops[playerid] = info
- status = ("paused", "playing")[info['speed']]
- ret = ('\n '
- return ret
+ if state.PLAYER_STATES[playerid]['plex_id']:
+ key = '/library/metadata/%s' % \
+ state.PLAYER_STATES[playerid]['plex_id']
+ return key
+ def get_timeline_xml(self, player, ptype):
+ if player is None:
+ return ' \n' % (CONTROLLABLE[ptype], ptype, ptype)
+ playerid = player['playerid']
+ info = state.PLAYER_STATES[playerid]
+ status = 'paused' if info['speed'] == '0' else 'playing'
+ ret = ' '
+ ret += '/>\n'
return ret
def updateCommandID(self, uuid, commandID):
@@ -142,120 +169,92 @@ class SubscriptionManager:
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')
- and not self.lastplayers):
- return True
+ # Do we need a check to NOT tell about e.g. PVR/TV and Addon playback?
players = js.get_players()
# 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)
+ with RLock():
+ for subscriber in self.subscribers.values():
+ subscriber.send_update(msg, len(players) == 0)
self.notifyServer(players)
self.lastplayers = players
return True
def notifyServer(self, players):
- for typus, p in players.iteritems():
- info = self.playerprops[p.get('playerid')]
- self._sendNotification(info, int(p['playerid']))
- self.lastinfo[typus] = info
- # Cross the one of the list
+ for typus, player in players.iteritems():
+ self._send_pms_notification(
+ player['playerid'], self._get_pms_params(player['playerid']))
try:
del self.lastplayers[typus]
except KeyError:
pass
# Process the players we have left (to signal a stop)
- for typus, p in self.lastplayers.iteritems():
- self.lastinfo[typus]['state'] = 'stopped'
- self._sendNotification(self.lastinfo[typus], int(p['playerid']))
+ for typus, player in self.lastplayers.iteritems():
+ self.last_params['state'] = 'stopped'
+ self._send_pms_notification(player['playerid'], self.last_params)
- def _sendNotification(self, info, playerid):
- playqueue = self.playqueue.playqueues[playerid]
- xargs = getXArgsDeviceInfo(include_token=False)
- params = {
- 'containerKey': self.containerKey or "/library/metadata/900000",
- 'key': self.lastkey or "/library/metadata/900000",
- 'ratingKey': self.ratingkey or "900000",
- 'state': info['state'],
- 'time': info['time'],
- 'duration': info['duration']
+ def _get_pms_params(self, playerid):
+ info = state.PLAYER_STATES[playerid]
+ status = 'paused' if info['speed'] == '0' else 'playing'
+ params = {'state': status,
+ 'ratingKey': self.ratingkey,
+ 'key': '/library/metadata/%s' % self.ratingkey,
+ 'time': kodi_time_to_millis(info['time']),
+ 'duration': kodi_time_to_millis(info['totaltime'])
}
+ if self.containerKey:
+ params['containerKey'] = self.containerKey
+ if self.containerKey is not None and \
+ self.containerKey.startswith('/playQueues/'):
+ params['playQueueVersion'] = info['playQueueVersion']
+ params['playQueueItemID'] = info['playQueueItemID']
+ self.last_params = params
+ return params
+
+ def _send_pms_notification(self, playerid, params):
+ serv = self.getServerByHost(self.server)
+ xargs = self._headers()
+ playqueue = self.playqueue.playqueues[playerid]
if state.PLEX_TRANSIENT_TOKEN:
xargs['X-Plex-Token'] = state.PLEX_TRANSIENT_TOKEN
elif playqueue.plex_transient_token:
xargs['X-Plex-Token'] = playqueue.plex_transient_token
- if info.get('playQueueID'):
- params['containerKey'] = '/playQueues/%s' % info['playQueueID']
- params['playQueueVersion'] = info['playQueueVersion']
- params['playQueueItemID'] = info['playQueueItemID']
- serv = self.getServerByHost(self.server)
url = '%s://%s:%s/:/timeline' % (serv.get('protocol', 'http'),
serv.get('server', 'localhost'),
serv.get('port', '32400'))
- self.doUtils(url, parameters=params, headerOptions=xargs)
- log.debug("Sent server notification with parameters: %s to %s"
- % (params, url))
+ self.doUtils().downloadUrl(
+ url, parameters=params, headerOptions=xargs)
+ # Save to be able to signal a stop at the end
+ LOG.debug("Sent server notification with parameters: %s to %s",
+ params, url)
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
+ subscriber = Subscriber(protocol,
+ host,
+ port,
+ uuid,
+ commandID,
+ self,
+ self.RequestMgr)
+ with RLock():
+ self.subscribers[subscriber.uuid] = subscriber
+ return subscriber
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]
+ with RLock():
+ for subscriber in self.subscribers.values():
+ if subscriber.uuid == uuid or subscriber.host == uuid:
+ subscriber.cleanup()
+ del self.subscribers[subscriber.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):
- # 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:
- 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'
-
- # get the volume from the application
- info['volume'] = js.get_volume()
- info['mute'] = js.get_muted()
-
- info['plex_transient_token'] = playqueue.plex_transient_token
-
- return info
+ with RLock():
+ for subscriber in self.subscribers.values():
+ if subscriber.age > 30:
+ subscriber.cleanup()
+ del self.subscribers[subscriber.uuid]
class Subscriber:
@@ -268,16 +267,13 @@ class Subscriber:
self.commandID = int(commandID) or 0
self.navlocationsent = False
self.age = 0
- self.doUtils = downloadutils.DownloadUtils().downloadUrl
+ self.doUtils = downloadutils.DownloadUtils
self.subMgr = subMgr
self.RequestMgr = RequestMgr
def __eq__(self, other):
return self.uuid == other.uuid
- def tostr(self):
- return "uuid=%s,commandID=%i" % (self.uuid, self.commandID)
-
def cleanup(self):
self.RequestMgr.closeConnection(self.protocol, self.host, self.port)
@@ -289,11 +285,12 @@ class Subscriber:
return True
else:
self.navlocationsent = True
- msg = re.sub(r"INSERTCOMMANDID", str(self.commandID), msg)
- log.debug("sending xml to subscriber %s:\n%s" % (self.tostr(), msg))
+ msg = sub(r"INSERTCOMMANDID", str(self.commandID), msg)
+ LOG.debug("sending xml to subscriber uuid=%s,commandID=%i:\n%s",
+ self.uuid, self.commandID, msg)
url = self.protocol + '://' + self.host + ':' + self.port \
+ "/:/timeline"
- t = threading.Thread(target=self.threadedSend, args=(url, msg))
+ t = Thread(target=self.threadedSend, args=(url, msg))
t.start()
def threadedSend(self, url, msg):
@@ -301,9 +298,8 @@ class Subscriber:
Threaded POST request, because they stall due to PMS response missing
the Content-Length header :-(
"""
- response = self.doUtils(url,
- postBody=msg,
- action_type="POST")
- log.debug('response is: %s', response)
+ response = self.doUtils().downloadUrl(url,
+ postBody=msg,
+ action_type="POST")
if response in [False, None, 401]:
self.subMgr.removeSubscriber(self.uuid)
diff --git a/resources/lib/state.py b/resources/lib/state.py
index 5d08c32f..a115f5bc 100644
--- a/resources/lib/state.py
+++ b/resources/lib/state.py
@@ -112,6 +112,9 @@ PLAYER_STATES = {
2: {},
3: {}
}
+# Dict containing all filenames as keys with plex id as values - used for addon
+# paths for playback (since we're not receiving a Kodi id)
+PLEX_IDS = {}
PLAYED_INFO = {}
# Kodi webserver details
diff --git a/resources/lib/variables.py b/resources/lib/variables.py
index d660f22f..3dace6f4 100644
--- a/resources/lib/variables.py
+++ b/resources/lib/variables.py
@@ -199,6 +199,20 @@ KODITYPE_FROM_PLEXTYPE = {
'XXXXXXX': 'genre'
}
+PLEX_TYPE_FROM_KODI_TYPE = {
+ KODI_TYPE_VIDEO: PLEX_TYPE_VIDEO,
+ KODI_TYPE_MOVIE: PLEX_TYPE_MOVIE,
+ KODI_TYPE_EPISODE: PLEX_TYPE_EPISODE,
+ KODI_TYPE_SEASON: PLEX_TYPE_SEASON,
+ KODI_TYPE_SHOW: PLEX_TYPE_SHOW,
+ KODI_TYPE_CLIP: PLEX_TYPE_CLIP,
+ KODI_TYPE_ARTIST: PLEX_TYPE_ARTIST,
+ KODI_TYPE_ALBUM: PLEX_TYPE_ALBUM,
+ KODI_TYPE_SONG: PLEX_TYPE_SONG,
+ KODI_TYPE_AUDIO: PLEX_TYPE_AUDIO,
+ KODI_TYPE_PHOTO: PLEX_TYPE_PHOTO
+}
+
KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE = {
PLEX_TYPE_VIDEO: KODI_TYPE_VIDEO,
PLEX_TYPE_MOVIE: KODI_TYPE_VIDEO,