2016-09-03 01:20:19 +10:00
|
|
|
import logging
|
2016-01-15 22:12:52 +11:00
|
|
|
import base64
|
|
|
|
import json
|
|
|
|
import string
|
2016-04-07 02:23:51 +10:00
|
|
|
|
2016-01-15 22:12:52 +11:00
|
|
|
import xbmc
|
2016-04-07 02:23:51 +10:00
|
|
|
|
2017-01-05 06:57:16 +11:00
|
|
|
import plexdb_functions as plexdb
|
2016-04-03 01:46:23 +11:00
|
|
|
|
2016-09-03 01:20:19 +10:00
|
|
|
###############################################################################
|
|
|
|
|
|
|
|
log = logging.getLogger("PLEX."+__name__)
|
|
|
|
|
|
|
|
###############################################################################
|
|
|
|
|
2016-01-15 22:12:52 +11:00
|
|
|
|
|
|
|
def xbmc_photo():
|
|
|
|
return "photo"
|
2016-04-03 01:46:23 +11:00
|
|
|
|
|
|
|
|
2016-01-15 22:12:52 +11:00
|
|
|
def xbmc_video():
|
|
|
|
return "video"
|
2016-04-03 01:46:23 +11:00
|
|
|
|
|
|
|
|
2016-01-15 22:12:52 +11:00
|
|
|
def xbmc_audio():
|
|
|
|
return "audio"
|
|
|
|
|
2016-04-03 01:46:23 +11:00
|
|
|
|
2016-01-15 22:12:52 +11:00
|
|
|
def plex_photo():
|
|
|
|
return "photo"
|
2016-04-03 01:46:23 +11:00
|
|
|
|
|
|
|
|
2016-01-15 22:12:52 +11:00
|
|
|
def plex_video():
|
|
|
|
return "video"
|
2016-04-03 01:46:23 +11:00
|
|
|
|
|
|
|
|
2016-01-15 22:12:52 +11:00
|
|
|
def plex_audio():
|
|
|
|
return "music"
|
|
|
|
|
2016-04-03 01:46:23 +11:00
|
|
|
|
2016-01-15 22:12:52 +11:00
|
|
|
def xbmc_type(plex_type):
|
|
|
|
if plex_type == plex_photo():
|
|
|
|
return xbmc_photo()
|
|
|
|
elif plex_type == plex_video():
|
|
|
|
return xbmc_video()
|
|
|
|
elif plex_type == plex_audio():
|
|
|
|
return xbmc_audio()
|
2016-04-03 01:46:23 +11:00
|
|
|
|
|
|
|
|
2016-01-15 22:12:52 +11:00
|
|
|
def plex_type(xbmc_type):
|
|
|
|
if xbmc_type == xbmc_photo():
|
|
|
|
return plex_photo()
|
|
|
|
elif xbmc_type == xbmc_video():
|
|
|
|
return plex_video()
|
|
|
|
elif xbmc_type == xbmc_audio():
|
|
|
|
return plex_audio()
|
|
|
|
|
2016-04-03 01:46:23 +11:00
|
|
|
|
2016-01-15 22:12:52 +11:00
|
|
|
def getXMLHeader():
|
2017-05-07 02:50:28 +10:00
|
|
|
return '<?xml version="1.0" encoding="UTF-8"?>\n'
|
2016-01-15 22:12:52 +11:00
|
|
|
|
2016-04-03 01:46:23 +11:00
|
|
|
|
2016-01-15 22:12:52 +11:00
|
|
|
def getOKMsg():
|
2017-05-18 04:14:21 +10:00
|
|
|
return getXMLHeader() + '<Response code="200" status="OK" />'
|
2016-01-15 22:12:52 +11:00
|
|
|
|
|
|
|
|
|
|
|
def timeToMillis(time):
|
2016-07-20 16:23:25 +10:00
|
|
|
return (time['hours']*3600 +
|
|
|
|
time['minutes']*60 +
|
|
|
|
time['seconds'])*1000 + time['milliseconds']
|
2016-01-15 22:12:52 +11:00
|
|
|
|
2016-04-03 01:46:23 +11:00
|
|
|
|
2016-01-15 22:12:52 +11:00
|
|
|
def millisToTime(t):
|
|
|
|
millis = int(t)
|
|
|
|
seconds = millis / 1000
|
|
|
|
minutes = seconds / 60
|
|
|
|
hours = minutes / 60
|
|
|
|
seconds = seconds % 60
|
|
|
|
minutes = minutes % 60
|
|
|
|
millis = millis % 1000
|
2016-07-20 16:23:25 +10:00
|
|
|
return {'hours': hours,
|
|
|
|
'minutes': minutes,
|
|
|
|
'seconds': seconds,
|
|
|
|
'milliseconds': millis}
|
2016-01-15 22:12:52 +11:00
|
|
|
|
2016-04-03 01:46:23 +11:00
|
|
|
|
2016-01-15 22:12:52 +11:00
|
|
|
def textFromXml(element):
|
2016-04-03 01:46:23 +11:00
|
|
|
return element.firstChild.data
|
|
|
|
|
|
|
|
|
|
|
|
class jsonClass():
|
|
|
|
|
2016-04-05 18:57:30 +10:00
|
|
|
def __init__(self, requestMgr, settings):
|
|
|
|
self.settings = settings
|
2016-04-03 01:46:23 +11:00
|
|
|
self.requestMgr = requestMgr
|
|
|
|
|
|
|
|
def jsonrpc(self, action, arguments={}):
|
|
|
|
""" put some JSON together for the JSON-RPC APIv6 """
|
|
|
|
if action.lower() == "sendkey":
|
2016-07-20 16:23:25 +10:00
|
|
|
request = json.dumps({
|
|
|
|
"jsonrpc": "2.0",
|
|
|
|
"method": "Input.SendText",
|
|
|
|
"params": {
|
|
|
|
"text": arguments[0],
|
|
|
|
"done": False
|
|
|
|
}
|
|
|
|
})
|
2016-04-03 01:46:23 +11:00
|
|
|
elif action.lower() == "ping":
|
2016-07-20 16:23:25 +10:00
|
|
|
request = json.dumps({
|
|
|
|
"jsonrpc": "2.0",
|
|
|
|
"id": 1,
|
|
|
|
"method": "JSONRPC.Ping"
|
|
|
|
})
|
2016-04-03 01:46:23 +11:00
|
|
|
elif arguments:
|
2016-07-20 16:23:25 +10:00
|
|
|
request = json.dumps({
|
|
|
|
"id": 1,
|
|
|
|
"jsonrpc": "2.0",
|
|
|
|
"method": action,
|
|
|
|
"params": arguments})
|
2016-04-03 01:46:23 +11:00
|
|
|
else:
|
2016-07-20 16:23:25 +10:00
|
|
|
request = json.dumps({
|
|
|
|
"id": 1,
|
|
|
|
"jsonrpc": "2.0",
|
|
|
|
"method": action
|
|
|
|
})
|
2016-04-03 01:46:23 +11:00
|
|
|
|
|
|
|
result = self.parseJSONRPC(xbmc.executeJSONRPC(request))
|
|
|
|
|
|
|
|
if not result and self.settings['webserver_enabled']:
|
2016-07-20 16:23:25 +10:00
|
|
|
# xbmc.executeJSONRPC appears to fail on the login screen, but
|
|
|
|
# going through the network stack works, so let's try the request
|
|
|
|
# again
|
2016-04-03 01:46:23 +11:00
|
|
|
result = self.parseJSONRPC(self.requestMgr.post(
|
|
|
|
"127.0.0.1",
|
|
|
|
self.settings['port'],
|
|
|
|
"/jsonrpc",
|
|
|
|
request,
|
2016-07-20 16:23:25 +10:00
|
|
|
{'Content-Type': 'application/json',
|
|
|
|
'Authorization': 'Basic %s' % string.strip(
|
|
|
|
base64.encodestring('%s:%s'
|
|
|
|
% (self.settings['user'],
|
|
|
|
self.settings['passwd'])))
|
|
|
|
}))
|
2016-04-03 01:46:23 +11:00
|
|
|
return result
|
|
|
|
|
2016-06-27 02:22:26 +10:00
|
|
|
def skipTo(self, plexId, typus):
|
|
|
|
# playlistId = self.getPlaylistId(tryDecode(xbmc_type(typus)))
|
|
|
|
# playerId = self.
|
2017-01-05 06:57:16 +11:00
|
|
|
with plexdb.Get_Plex_DB() as plex_db:
|
|
|
|
plexdb_item = plex_db.getItem_byId(plexId)
|
2016-06-27 02:22:26 +10:00
|
|
|
try:
|
2017-01-05 06:57:16 +11:00
|
|
|
dbid = plexdb_item[0]
|
|
|
|
mediatype = plexdb_item[4]
|
2016-06-27 02:22:26 +10:00
|
|
|
except TypeError:
|
2016-09-03 01:20:19 +10:00
|
|
|
log.info('Couldnt find item %s in Kodi db' % plexId)
|
2016-06-27 02:22:26 +10:00
|
|
|
return
|
2016-09-03 01:20:19 +10:00
|
|
|
log.debug('plexid: %s, kodi id: %s, type: %s'
|
|
|
|
% (plexId, dbid, mediatype))
|
2016-06-27 02:22:26 +10:00
|
|
|
|
2016-04-03 01:46:23 +11:00
|
|
|
def getPlexHeaders(self):
|
|
|
|
h = {
|
2016-05-08 20:18:20 +10:00
|
|
|
"Content-type": "text/xml",
|
2016-04-03 01:46:23 +11:00
|
|
|
"Access-Control-Allow-Origin": "*",
|
|
|
|
"X-Plex-Version": self.settings['version'],
|
|
|
|
"X-Plex-Client-Identifier": self.settings['uuid'],
|
2017-01-08 21:46:19 +11:00
|
|
|
"X-Plex-Provides": "client,controller,player",
|
2016-04-03 01:46:23 +11:00
|
|
|
"X-Plex-Product": "PlexKodiConnect",
|
|
|
|
"X-Plex-Device-Name": self.settings['client_name'],
|
|
|
|
"X-Plex-Platform": "Kodi",
|
2016-04-07 02:23:51 +10:00
|
|
|
"X-Plex-Model": self.settings['platform'],
|
2016-04-03 01:46:23 +11:00
|
|
|
"X-Plex-Device": "PC",
|
|
|
|
}
|
|
|
|
if self.settings['myplex_user']:
|
|
|
|
h["X-Plex-Username"] = self.settings['myplex_user']
|
|
|
|
return h
|
|
|
|
|
|
|
|
def parseJSONRPC(self, jsonraw):
|
|
|
|
if not jsonraw:
|
2016-09-03 01:20:19 +10:00
|
|
|
log.debug("Empty response from Kodi")
|
2016-04-03 01:46:23 +11:00
|
|
|
return {}
|
|
|
|
else:
|
2016-07-20 16:23:25 +10:00
|
|
|
parsed = json.loads(jsonraw)
|
2016-04-03 01:46:23 +11:00
|
|
|
if parsed.get('error', False):
|
2016-09-03 01:20:19 +10:00
|
|
|
log.error("Kodi returned an error: %s" % parsed.get('error'))
|
2016-04-03 01:46:23 +11:00
|
|
|
return parsed.get('result', {})
|
|
|
|
|
|
|
|
def getPlayers(self):
|
|
|
|
info = self.jsonrpc("Player.GetActivePlayers") or []
|
|
|
|
ret = {}
|
|
|
|
for player in info:
|
|
|
|
player['playerid'] = int(player['playerid'])
|
|
|
|
ret[player['type']] = player
|
|
|
|
return ret
|
|
|
|
|
2016-06-27 02:22:26 +10:00
|
|
|
def getPlaylistId(self, typus):
|
|
|
|
"""
|
|
|
|
typus: one of the Kodi types, e.g. audio or video
|
|
|
|
|
|
|
|
Returns None if nothing was found
|
|
|
|
"""
|
|
|
|
for playlist in self.getPlaylists():
|
|
|
|
if playlist.get('type') == typus:
|
|
|
|
return playlist.get('playlistid')
|
|
|
|
|
|
|
|
def getPlaylists(self):
|
|
|
|
"""
|
|
|
|
Returns a list, e.g.
|
|
|
|
[
|
|
|
|
{u'playlistid': 0, u'type': u'audio'},
|
|
|
|
{u'playlistid': 1, u'type': u'video'},
|
|
|
|
{u'playlistid': 2, u'type': u'picture'}
|
|
|
|
]
|
|
|
|
"""
|
|
|
|
return self.jsonrpc('Playlist.GetPlaylists')
|
|
|
|
|
2016-04-03 01:46:23 +11:00
|
|
|
def getPlayerIds(self):
|
|
|
|
ret = []
|
|
|
|
for player in self.getPlayers().values():
|
|
|
|
ret.append(player['playerid'])
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def getVideoPlayerId(self, players=False):
|
|
|
|
if players is None:
|
|
|
|
players = self.getPlayers()
|
|
|
|
return players.get(xbmc_video(), {}).get('playerid', None)
|
|
|
|
|
|
|
|
def getAudioPlayerId(self, players=False):
|
|
|
|
if players is None:
|
|
|
|
players = self.getPlayers()
|
|
|
|
return players.get(xbmc_audio(), {}).get('playerid', None)
|
|
|
|
|
|
|
|
def getPhotoPlayerId(self, players=False):
|
|
|
|
if players is None:
|
|
|
|
players = self.getPlayers()
|
|
|
|
return players.get(xbmc_photo(), {}).get('playerid', None)
|
|
|
|
|
|
|
|
def getVolume(self):
|
2016-07-20 16:23:25 +10:00
|
|
|
answ = self.jsonrpc('Application.GetProperties',
|
|
|
|
{
|
|
|
|
"properties": ["volume", 'muted']
|
|
|
|
})
|
2016-04-03 01:46:23 +11:00
|
|
|
vol = str(answ.get('volume', 100))
|
|
|
|
mute = ("0", "1")[answ.get('muted', False)]
|
|
|
|
return (vol, mute)
|