diff --git a/resources/lib/PlexCompanion.py b/resources/lib/PlexCompanion.py
index 9cf71bf5..88e472b4 100644
--- a/resources/lib/PlexCompanion.py
+++ b/resources/lib/PlexCompanion.py
@@ -8,8 +8,8 @@ from urllib import urlencode
from xbmc import sleep, executebuiltin
from utils import settings, thread_methods
-from plexbmchelper import listener, plexgdm, subscribers, functions, \
- httppersist, plexsettings
+from plexbmchelper import listener, plexgdm, subscribers, httppersist, \
+ plexsettings
from PlexFunctions import ParseContainerKey, GetPlexMetadata
from PlexAPI import API
from playlist_func import get_pms_playqueue, get_plextype_from_xml
@@ -196,9 +196,8 @@ class PlexCompanion(Thread):
# Start up instances
requestMgr = httppersist.RequestMgr()
- jsonClass = functions.jsonClass(requestMgr, self.settings)
subscriptionManager = subscribers.SubscriptionManager(
- jsonClass, requestMgr, self.player, self.mgr)
+ requestMgr, self.player, self.mgr)
queue = Queue.Queue(maxsize=100)
self.queue = queue
@@ -211,7 +210,6 @@ class PlexCompanion(Thread):
httpd = listener.ThreadedHTTPServer(
client,
subscriptionManager,
- jsonClass,
self.settings,
queue,
('', self.settings['myport']),
diff --git a/resources/lib/json_rpc.py b/resources/lib/json_rpc.py
index 46da124f..9cb0d679 100644
--- a/resources/lib/json_rpc.py
+++ b/resources/lib/json_rpc.py
@@ -3,7 +3,7 @@ Collection of functions using the Kodi JSON RPC interface.
See http://kodi.wiki/view/JSON-RPC_API
"""
from json import loads, dumps
-from utils import milliseconds_to_kodi_time
+from utils import millis_to_kodi_time
from xbmc import executeJSONRPC
@@ -49,7 +49,7 @@ def get_players():
'picture': ...
}
"""
- info = jsonrpc("Player.GetActivePlayers").execute()['result'] or []
+ info = jsonrpc("Player.GetActivePlayers").execute()['result']
ret = {}
for player in info:
player['playerid'] = int(player['playerid'])
@@ -152,7 +152,7 @@ def seek_to(offset):
for playerid in get_player_ids():
jsonrpc("Player.Seek").execute(
{"playerid": playerid,
- "value": milliseconds_to_kodi_time(offset)})
+ "value": millis_to_kodi_time(offset)})
def smallforward():
@@ -240,6 +240,13 @@ def input_back():
return jsonrpc("Input.Back").execute()
+def input_sendtext(text):
+ """
+ Tells Kodi the user sent text [unicode]
+ """
+ return jsonrpc("Input.SendText").execute({'test': text, 'done': False})
+
+
def playlist_get_items(playlistid, properties):
"""
playlistid: [int] id of the Kodi playlist
@@ -350,6 +357,33 @@ def get_episodes(params):
return ret
+def get_player_props(playerid):
+ """
+ Returns a dict for the active Kodi player with the following values:
+ {
+ '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
+ '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({
+ 'playerid': playerid,
+ 'properties': ['type',
+ 'time',
+ 'totaltime',
+ 'speed',
+ 'shuffled',
+ 'repeat',
+ 'position',
+ 'playlistid']})
+ return ret['result']
+
+
def current_audiostream(playerid):
"""
Returns a dict of the active audiostream for playerid [int]:
@@ -402,3 +436,10 @@ def subtitle_enabled(playerid):
except (KeyError, TypeError):
ret = False
return ret
+
+
+def ping():
+ """
+ Pings the JSON RPC interface
+ """
+ return jsonrpc('JSONRPC.Ping').execute()
diff --git a/resources/lib/plexbmchelper/functions.py b/resources/lib/plexbmchelper/functions.py
deleted file mode 100644
index 784a1e77..00000000
--- a/resources/lib/plexbmchelper/functions.py
+++ /dev/null
@@ -1,244 +0,0 @@
-import logging
-import base64
-import json
-import string
-
-import xbmc
-
-import plexdb_functions as plexdb
-
-###############################################################################
-
-log = logging.getLogger("PLEX."+__name__)
-
-###############################################################################
-
-
-def xbmc_photo():
- return "photo"
-
-
-def xbmc_video():
- return "video"
-
-
-def xbmc_audio():
- return "audio"
-
-
-def plex_photo():
- return "photo"
-
-
-def plex_video():
- return "video"
-
-
-def plex_audio():
- return "music"
-
-
-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()
-
-
-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()
-
-
-def getXMLHeader():
- return '\n'
-
-
-def getOKMsg():
- return getXMLHeader() + ''
-
-
-def timeToMillis(time):
- return (time['hours']*3600 +
- time['minutes']*60 +
- time['seconds'])*1000 + time['milliseconds']
-
-
-def millisToTime(t):
- millis = int(t)
- seconds = millis / 1000
- minutes = seconds / 60
- hours = minutes / 60
- seconds = seconds % 60
- minutes = minutes % 60
- millis = millis % 1000
- return {'hours': hours,
- 'minutes': minutes,
- 'seconds': seconds,
- 'milliseconds': millis}
-
-
-def textFromXml(element):
- return element.firstChild.data
-
-
-class jsonClass():
-
- def __init__(self, requestMgr, settings):
- self.settings = settings
- self.requestMgr = requestMgr
-
- def jsonrpc(self, action, arguments={}):
- """ put some JSON together for the JSON-RPC APIv6 """
- if action.lower() == "sendkey":
- request = json.dumps({
- "jsonrpc": "2.0",
- "method": "Input.SendText",
- "params": {
- "text": arguments[0],
- "done": False
- }
- })
- elif action.lower() == "ping":
- request = json.dumps({
- "jsonrpc": "2.0",
- "id": 1,
- "method": "JSONRPC.Ping"
- })
- elif arguments:
- request = json.dumps({
- "id": 1,
- "jsonrpc": "2.0",
- "method": action,
- "params": arguments})
- else:
- request = json.dumps({
- "id": 1,
- "jsonrpc": "2.0",
- "method": action
- })
-
- result = self.parseJSONRPC(xbmc.executeJSONRPC(request))
-
- if not result and self.settings['webserver_enabled']:
- # xbmc.executeJSONRPC appears to fail on the login screen, but
- # going through the network stack works, so let's try the request
- # again
- result = self.parseJSONRPC(self.requestMgr.post(
- "127.0.0.1",
- self.settings['port'],
- "/jsonrpc",
- request,
- {'Content-Type': 'application/json',
- 'Authorization': 'Basic %s' % string.strip(
- base64.encodestring('%s:%s'
- % (self.settings['user'],
- self.settings['passwd'])))
- }))
- return result
-
- def skipTo(self, plexId, typus):
- # playlistId = self.getPlaylistId(tryDecode(xbmc_type(typus)))
- # playerId = self.
- with plexdb.Get_Plex_DB() as plex_db:
- plexdb_item = plex_db.getItem_byId(plexId)
- try:
- dbid = plexdb_item[0]
- mediatype = plexdb_item[4]
- except TypeError:
- log.info('Couldnt find item %s in Kodi db' % plexId)
- return
- log.debug('plexid: %s, kodi id: %s, type: %s'
- % (plexId, dbid, mediatype))
-
- def getPlexHeaders(self):
- h = {
- "Content-type": "text/xml",
- "Access-Control-Allow-Origin": "*",
- "X-Plex-Version": self.settings['version'],
- "X-Plex-Client-Identifier": self.settings['uuid'],
- "X-Plex-Provides": "client,controller,player",
- "X-Plex-Product": "PlexKodiConnect",
- "X-Plex-Device-Name": self.settings['client_name'],
- "X-Plex-Platform": "Kodi",
- "X-Plex-Model": self.settings['platform'],
- "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:
- log.debug("Empty response from Kodi")
- return {}
- else:
- parsed = json.loads(jsonraw)
- if parsed.get('error', False):
- log.error("Kodi returned an error: %s" % parsed.get('error'))
- 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
-
- 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')
-
- 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):
- answ = self.jsonrpc('Application.GetProperties',
- {
- "properties": ["volume", 'muted']
- })
- vol = str(answ.get('volume', 100))
- mute = ("0", "1")[answ.get('muted', False)]
- return (vol, mute)
diff --git a/resources/lib/plexbmchelper/listener.py b/resources/lib/plexbmchelper/listener.py
index c07e9c00..e177212f 100644
--- a/resources/lib/plexbmchelper/listener.py
+++ b/resources/lib/plexbmchelper/listener.py
@@ -8,9 +8,9 @@ from urlparse import urlparse, parse_qs
from xbmc import sleep
from companion import process_command
from utils import window
-
-from functions import *
-
+import json_rpc as js
+from clientinfo import getXArgsDeviceInfo
+import variables as v
###############################################################################
@@ -82,7 +82,6 @@ class MyHandler(BaseHTTPRequestHandler):
def answer_request(self, sendData):
self.serverlist = self.server.client.getServerList()
subMgr = self.server.subscriptionManager
- js = self.server.jsonClass
settings = self.server.settings
try:
@@ -105,7 +104,7 @@ class MyHandler(BaseHTTPRequestHandler):
% settings['version'])
elif request_path == "verify":
self.response("XBMC JSON connection test:\n" +
- js.jsonrpc("ping"))
+ js.ping())
elif "resources" == request_path:
resp = ('%s'
''
@@ -121,15 +120,15 @@ class MyHandler(BaseHTTPRequestHandler):
' deviceClass="pc"'
'/>'
''
- % (getXMLHeader(),
+ % (v.XML_HEADER,
settings['client_name'],
settings['uuid'],
settings['platform'],
settings['plexbmc_version']))
log.debug("crafted resources response: %s" % resp)
- self.response(resp, js.getPlexHeaders())
+ self.response(resp, getXArgsDeviceInfo())
elif "/subscribe" in request_path:
- self.response(getOKMsg(), js.getPlexHeaders())
+ self.response(v.COMPANION_OK_MESSAGE, getXArgsDeviceInfo())
protocol = params.get('protocol', False)
host = self.client_address[0]
port = params.get('port', False)
@@ -147,7 +146,7 @@ class MyHandler(BaseHTTPRequestHandler):
self.response(
sub(r"INSERTCOMMANDID",
str(commandID),
- subMgr.msg(js.getPlayers())),
+ subMgr.msg(js.get_players())),
{
'X-Plex-Client-Identifier': settings['uuid'],
'Access-Control-Expose-Headers':
@@ -156,14 +155,14 @@ class MyHandler(BaseHTTPRequestHandler):
'Content-Type': 'text/xml'
})
elif "/unsubscribe" in request_path:
- self.response(getOKMsg(), js.getPlexHeaders())
+ self.response(v.COMPANION_OK_MESSAGE, getXArgsDeviceInfo())
uuid = self.headers.get('X-Plex-Client-Identifier', False) \
or self.client_address[0]
subMgr.removeSubscriber(uuid)
else:
# Throw it to companion.py
process_command(request_path, params, self.server.queue)
- self.response('', js.getPlexHeaders())
+ self.response('', getXArgsDeviceInfo())
subMgr.notify()
except:
log.error('Error encountered. Traceback:')
@@ -174,17 +173,16 @@ class MyHandler(BaseHTTPRequestHandler):
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
daemon_threads = True
- def __init__(self, client, subscriptionManager, jsonClass, settings,
+ def __init__(self, client, subscriptionManager, settings,
queue, *args, **kwargs):
"""
client: Class handle to plexgdm.plexgdm. We can thus ask for an up-to-
date serverlist without instantiating anything
- same for SubscriptionManager and jsonClass
+ same for SubscriptionManager
"""
self.client = client
self.subscriptionManager = subscriptionManager
- self.jsonClass = jsonClass
self.settings = settings
self.queue = queue
HTTPServer.__init__(self, *args, **kwargs)
diff --git a/resources/lib/plexbmchelper/subscribers.py b/resources/lib/plexbmchelper/subscribers.py
index 82d4d833..5345fea4 100644
--- a/resources/lib/plexbmchelper/subscribers.py
+++ b/resources/lib/plexbmchelper/subscribers.py
@@ -2,12 +2,15 @@ import logging
import re
import threading
+from xbmc import sleep
+
import downloadutils
from clientinfo import getXArgsDeviceInfo
-from utils import window
+from utils import window, kodi_time_to_millis
import PlexFunctions as pf
import state
-from functions import *
+import variables as v
+import json_rpc as js
###############################################################################
@@ -17,7 +20,7 @@ log = logging.getLogger("PLEX."+__name__)
class SubscriptionManager:
- def __init__(self, jsonClass, RequestMgr, player, mgr):
+ def __init__(self, RequestMgr, player, mgr):
self.serverlist = []
self.subscribers = {}
self.info = {}
@@ -30,8 +33,6 @@ class SubscriptionManager:
'audio': {},
'picture': {}
}
- self.volume = 0
- self.mute = '0'
self.server = ""
self.protocol = "http"
self.port = ""
@@ -40,7 +41,6 @@ class SubscriptionManager:
self.xbmcplayer = player
self.playqueue = mgr.playqueue
- self.js = jsonClass
self.RequestMgr = RequestMgr
def getServerByHost(self, host):
@@ -52,32 +52,34 @@ class SubscriptionManager:
return server
return {}
- def getVolume(self):
- self.volume, self.mute = self.js.getVolume()
-
def msg(self, players):
- msg = getXMLHeader()
+ log.debug('players: %s', players)
+ msg = v.XML_HEADER
msg += '' % window('plex_client_Id')
- msg += self.getTimelineXML(self.js.getAudioPlayerId(players), plex_audio())
- msg += self.getTimelineXML(self.js.getPhotoPlayerId(players), plex_photo())
- msg += self.getTimelineXML(self.js.getVideoPlayerId(players), plex_video())
+ 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"
return msg
- def getTimelineXML(self, playerid, ptype):
- if playerid is not None:
+ def getTimelineXML(self, player, ptype):
+ if player is None:
+ status = 'stopped'
+ time = 0
+ else:
+ playerid = player['playerid']
info = self.getPlayerProperties(playerid)
# save this info off so the server update can use it too
self.playerprops[playerid] = info
status = info['state']
time = info['time']
- else:
- status = "stopped"
- time = 0
ret = ('\n '
return ret
@@ -89,10 +91,10 @@ class SubscriptionManager:
keyid = None
count = 0
while not keyid:
- if count > 300:
+ if count > 30:
break
keyid = window('plex_currently_playing_itemid')
- xbmc.sleep(100)
+ sleep(100)
count += 1
if keyid:
self.lastkey = "/library/metadata/%s" % keyid
@@ -119,7 +121,7 @@ class SubscriptionManager:
ret += ' port="%s"' % serv.get('port', self.port)
ret += ' volume="%s"' % info['volume']
ret += ' shuffle="%s"' % info['shuffle']
- ret += ' mute="%s"' % self.mute
+ ret += ' mute="%s"' % info['mute']
ret += ' repeat="%s"' % info['repeat']
ret += ' itemType="%s"' % ptype
if state.PLEX_TRANSIENT_TOKEN:
@@ -145,7 +147,7 @@ class SubscriptionManager:
if (not window('plex_currently_playing_itemid')
and not self.lastplayers):
return True
- players = self.js.getPlayers()
+ players = js.get_players()
# fetch the message, subscribers or not, since the server
# will need the info anyway
msg = self.msg(players)
@@ -233,27 +235,15 @@ class SubscriptionManager:
# Get the playqueue
playqueue = self.playqueue.playqueues[playerid]
# get info from the player
- props = self.js.jsonrpc(
- "Player.GetProperties",
- {"playerid": playerid,
- "properties": ["type",
- "time",
- "totaltime",
- "speed",
- "shuffled",
- "repeat"]})
+ props = js.get_player_props(playerid)
info = {
- 'time': timeToMillis(props['time']),
- 'duration': timeToMillis(props['totaltime']),
+ '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')),
}
- # Get the playlist position
- pos = self.js.jsonrpc(
- "Player.GetProperties",
- {"playerid": playerid,
- "properties": ["position"]})['position']
+ pos = props['position']
try:
info['playQueueItemID'] = playqueue.items[pos].ID or 'null'
info['guid'] = playqueue.items[pos].guid or 'null'
@@ -274,8 +264,8 @@ class SubscriptionManager:
}
# get the volume from the application
- info['volume'] = self.volume
- info['mute'] = self.mute
+ info['volume'] = js.get_volume()
+ info['mute'] = js.get_muted()
info['plex_transient_token'] = playqueue.plex_transient_token
diff --git a/resources/lib/utils.py b/resources/lib/utils.py
index 5184b4d2..9ccc80b9 100644
--- a/resources/lib/utils.py
+++ b/resources/lib/utils.py
@@ -179,9 +179,15 @@ def dialog(typus, *args, **kwargs):
return types[typus](*args, **kwargs)
-def milliseconds_to_kodi_time(milliseconds):
+def millis_to_kodi_time(milliseconds):
"""
- Converts time in milliseconds to the time dict used by the Kodi JSON RPC
+ Converts time in milliseconds to the time dict used by the Kodi JSON RPC:
+ {
+ 'hours': [int],
+ 'minutes': [int],
+ 'seconds'[int],
+ 'milliseconds': [int]
+ }
Pass in the time in milliseconds as an int
"""
seconds = milliseconds / 1000
@@ -196,6 +202,22 @@ def milliseconds_to_kodi_time(milliseconds):
'milliseconds': milliseconds}
+def kodi_time_to_millis(time):
+ """
+ Converts the Kodi time dict
+ {
+ 'hours': [int],
+ 'minutes': [int],
+ 'seconds'[int],
+ 'milliseconds': [int]
+ }
+ to milliseconds [int]
+ """
+ return (time['hours']*3600 +
+ time['minutes']*60 +
+ time['seconds'])*1000 + time['milliseconds']
+
+
def tryEncode(uniString, encoding='utf-8'):
"""
Will try to encode uniString (in unicode) to encoding. This possibly
diff --git a/resources/lib/variables.py b/resources/lib/variables.py
index 856197d3..525bb29c 100644
--- a/resources/lib/variables.py
+++ b/resources/lib/variables.py
@@ -361,3 +361,8 @@ SORT_METHODS_ALBUMS = (
'SORT_METHOD_ARTIST',
'SORT_METHOD_ALBUM',
)
+
+
+XML_HEADER = '\n'
+
+COMPANION_OK_MESSAGE = XML_HEADER + ''