Add plexbmc.helper

This commit is contained in:
tomkat83 2016-01-15 12:12:52 +01:00
parent accd8f4ac4
commit e859a807bc
8 changed files with 1019 additions and 12 deletions

View file

@ -1258,7 +1258,6 @@ class TVShows(Items):
kodicursor.execute(query, (title, plot, rating, writer, premieredate,
runtime, director, season, episode, title, airsBeforeSeason,
airsBeforeEpisode, seasonid, episodeid))
self.logMsg("Checkpoint 1", 2)
else:
query = ' '.join((
@ -1272,10 +1271,8 @@ class TVShows(Items):
airsBeforeEpisode, episodeid))
# Update the checksum in emby table
self.logMsg("Checkpoint 2", 2)
emby_db.updateReference(itemid, checksum)
# Update parentid reference
self.logMsg("Checkpoint 3", 2)
emby_db.updateParentId(itemid, seasonid)
##### OR ADD THE EPISODE #####
@ -1327,7 +1324,6 @@ class TVShows(Items):
"WHERE idPath = ?"
))
kodicursor.execute(query, (path, None, None, 1, pathid))
self.logMsg("Checkpoint 4", 2)
# Update the file
query = ' '.join((
@ -1336,24 +1332,17 @@ class TVShows(Items):
"WHERE idFile = ?"
))
kodicursor.execute(query, (pathid, filename, dateadded, fileid))
self.logMsg("Checkpoint 5", 2)
# Process cast
people = API.getPeopleList()
kodi_db.addPeople(episodeid, people, "episode")
# Process artwork
self.logMsg("Checkpoint 6", 2)
artworks = API.getAllArtwork()
self.logMsg("Checkpoint 7", 2)
artwork.addOrUpdateArt(artworks['Primary'], episodeid, "episode", "thumb", kodicursor)
self.logMsg("Checkpoint 8", 2)
# Process stream details
streams = API.getMediaStreams()
self.logMsg("Checkpoint 9", 2)
kodi_db.addStreams(fileid, streams, runtime)
self.logMsg("Checkpoint 7", 2)
# Process playstates
kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
self.logMsg("Checkpoint 8", 2)
if not self.directpath and resume:
# Create additional entry for widgets. This is only required for plugin/episode.
temppathid = kodi_db.getPath("plugin://plugin.video.plexkodiconnect.tvshows/")
@ -1364,7 +1353,6 @@ class TVShows(Items):
"SET idPath = ?, strFilename = ?, dateAdded = ?",
"WHERE idFile = ?"
))
self.logMsg("Checkpoint 9", 2)
kodicursor.execute(query, (temppathid, filename, dateadded, tempfileid))
kodi_db.addPlaystate(tempfileid, resume, runtime, playcount, dateplayed)

View file

@ -0,0 +1 @@
# Dummy file to make this directory a package.

View file

@ -0,0 +1,195 @@
import base64
import inspect
import json
import string
import traceback
import xbmc
from settings import settings
from httppersist import requests
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 getPlatform():
if xbmc.getCondVisibility('system.platform.osx'):
return "MacOSX"
elif xbmc.getCondVisibility('system.platform.atv2'):
return "AppleTV2"
elif xbmc.getCondVisibility('system.platform.ios'):
return "iOS"
elif xbmc.getCondVisibility('system.platform.windows'):
return "Windows"
elif xbmc.getCondVisibility('system.platform.raspberrypi'):
return "RaspberryPi"
elif xbmc.getCondVisibility('system.platform.linux'):
return "Linux"
elif xbmc.getCondVisibility('system.platform.android'):
return "Android"
return "Unknown"
def printDebug( msg, functionname=True ):
if settings['debug']:
if functionname is False:
print str(msg)
else:
print "PleXBMC Helper -> " + inspect.stack()[1][3] + ": " + str(msg)
""" communicate with XBMC """
def jsonrpc(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" : self.arguments[0], "done" : False }} )
elif action.lower() == "ping":
request=json.dumps({ "jsonrpc" : "2.0",
"id" : 1 ,
"method" : "JSONRPC.Ping" })
elif action.lower() == "playmedia":
fullurl=arguments[0]
resume=arguments[1]
xbmc.Player().play("plugin://plugin.video.plexbmc/?mode=5&force="+resume+"&url="+fullurl)
return True
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})
printDebug("Sending request to XBMC without network stack: %s" % request)
result = parseJSONRPC(xbmc.executeJSONRPC(request))
if not result and 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 = parseJSONRPC(requests.post(
"127.0.0.1",
settings['port'],
"/jsonrpc",
request,
{ 'Content-Type' : 'application/json',
'Authorization' : 'Basic ' + string.strip(base64.encodestring(settings['user'] + ':' + settings['passwd'])) }))
return result
def parseJSONRPC(jsonraw):
if not jsonraw:
printDebug("Empty response from XBMC")
return {}
else:
printDebug("Response from XBMC: %s" % jsonraw)
parsed=json.loads(jsonraw)
if parsed.get('error', False):
print "XBMC returned an error: %s" % parsed.get('error')
return parsed.get('result', {})
def getXMLHeader():
return '<?xml version="1.0" encoding="utf-8"?>'+"\r\n"
def getOKMsg():
return getXMLHeader() + '<Response code="200" status="OK" />'
def getPlexHeaders():
h = {
"Content-type": "application/x-www-form-urlencoded",
"Access-Control-Allow-Origin": "*",
"X-Plex-Version": settings['version'],
"X-Plex-Client-Identifier": settings['uuid'],
"X-Plex-Provides": "player",
"X-Plex-Product": "PleXBMC",
"X-Plex-Device-Name": settings['client_name'],
"X-Plex-Platform": "XBMC",
"X-Plex-Model": getPlatform(),
"X-Plex-Device": "PC",
}
if settings['myplex_user']:
h["X-Plex-Username"] = settings['myplex_user']
return h
def getServerByHost(host):
list = settings['serverList']
if len(list) == 1:
return list[0]
for server in list:
if server.get('serverName') in host or server.get('server') in host:
return server
return {}
def getPlayers():
info = jsonrpc("Player.GetActivePlayers") or []
ret = {}
for player in info:
player['playerid'] = int(player['playerid'])
ret[player['type']] = player
return ret
def getPlayerIds():
ret = []
for player in getPlayers().values():
ret.append(player['playerid'])
return ret
def getVideoPlayerId(players = False):
if players is None:
players = getPlayers()
return players.get(xbmc_video(), {}).get('playerid', None)
def getAudioPlayerId(players = False):
if players is None:
players = getPlayers()
return players.get(xbmc_audio(), {}).get('playerid', None)
def getPhotoPlayerId(players = False):
if players is None:
players = getPlayers()
return players.get(xbmc_photo(), {}).get('playerid', None)
def getVolume():
return str(jsonrpc('Application.GetProperties', { "properties": [ "volume" ] }).get('volume', 100))
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

View file

@ -0,0 +1,76 @@
import httplib
import traceback
import string
class RequestMgr:
def __init__(self):
self.conns = {}
def getConnection(self, protocol, host, port):
conn = self.conns.get(protocol+host+str(port), False)
if not conn:
if protocol=="https":
conn = httplib.HTTPSConnection(host, port)
else:
conn = httplib.HTTPConnection(host, port)
self.conns[protocol+host+str(port)] = conn
return conn
def closeConnection(self, protocol, host, port):
conn = self.conns.get(protocol+host+str(port), False)
if conn:
conn.close()
self.conns.pop(protocol+host+str(port), None)
def dumpConnections(self):
for conn in self.conns.values():
conn.close()
self.conns = {}
def post(self, host, port, path, body, header={}, protocol="http"):
conn = None
try:
conn = self.getConnection(protocol, host, port)
header['Connection'] = "keep-alive"
conn.request("POST", path, body, header)
data = conn.getresponse()
if int(data.status) >= 400:
print "HTTP response error: " + str(data.status)
# this should return false, but I'm hacking it since iOS returns 404 no matter what
return data.read() or True
else:
return data.read() or True
except:
print "Unable to connect to %s\nReason:" % host
traceback.print_exc()
self.conns.pop(protocol+host+str(port), None)
if conn:
conn.close()
return False
def getwithparams(self, host, port, path, params, header={}, protocol="http"):
newpath = path + '?'
pairs = []
for key in params:
pairs.append(str(key)+'='+str(params[key]))
newpath += string.join(pairs, '&')
return self.get(host, port, newpath, header, protocol)
def get(self, host, port, path, header={}, protocol="http"):
try:
conn = self.getConnection(protocol, host, port)
header['Connection'] = "keep-alive"
conn.request("GET", path, headers=header)
data = conn.getresponse()
if int(data.status) >= 400:
print "HTTP response error: " + str(data.status)
return False
else:
return data.read() or True
except:
print "Unable to connect to %s\nReason: %s" % (host, traceback.print_exc())
self.conns.pop(protocol+host+str(port), None)
conn.close()
return False
requests = RequestMgr()

View file

@ -0,0 +1,190 @@
import re
import traceback
import xbmc
from SocketServer import ThreadingMixIn
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from urlparse import urlparse, parse_qs
from settings import settings
from functions import *
from subscribers import subMgr
class MyHandler(BaseHTTPRequestHandler):
protocol_version = 'HTTP/1.1'
def log_message(s, format, *args):
# I have my own logging, suppressing BaseHTTPRequestHandler's
#printDebug(format % args)
return True
def do_HEAD(s):
printDebug( "Serving HEAD request..." )
s.answer_request(0)
def do_GET(s):
printDebug( "Serving GET request..." )
s.answer_request(1)
def do_OPTIONS(s):
s.send_response(200)
s.send_header('Content-Length', '0')
s.send_header('X-Plex-Client-Identifier', settings['uuid'])
s.send_header('Content-Type', 'text/plain')
s.send_header('Connection', 'close')
s.send_header('Access-Control-Max-Age', '1209600')
s.send_header('Access-Control-Allow-Origin', '*')
s.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, DELETE, PUT, HEAD')
s.send_header('Access-Control-Allow-Headers', 'x-plex-version, x-plex-platform-version, x-plex-username, x-plex-client-identifier, x-plex-target-client-identifier, x-plex-device-name, x-plex-platform, x-plex-product, accept, x-plex-device')
s.end_headers()
s.wfile.close()
def response(s, body, headers = {}, code = 200):
try:
s.send_response(code)
for key in headers:
s.send_header(key, headers[key])
s.send_header('Content-Length', len(body))
s.send_header('Connection', "close")
s.end_headers()
s.wfile.write(body)
s.wfile.close()
except:
pass
def answer_request(s, sendData):
try:
request_path=s.path[1:]
request_path=re.sub(r"\?.*","",request_path)
url = urlparse(s.path)
paramarrays = parse_qs(url.query)
params = {}
for key in paramarrays:
params[key] = paramarrays[key][0]
printDebug ( "request path is: [%s]" % ( request_path,) )
printDebug ( "params are: %s" % params )
subMgr.updateCommandID(s.headers.get('X-Plex-Client-Identifier', s.client_address[0]), params.get('commandID', False))
if request_path=="version":
s.response("PleXBMC Helper Remote Redirector: Running\r\nVersion: %s" % settings['version'])
elif request_path=="verify":
result=jsonrpc("ping")
s.response("XBMC JSON connection test:\r\n"+result)
elif "resources" == request_path:
resp = getXMLHeader()
resp += "<MediaContainer>"
resp += "<Player"
resp += ' title="%s"' % settings['client_name']
resp += ' protocol="plex"'
resp += ' protocolVersion="1"'
resp += ' protocolCapabilities="navigation,playback,timeline"'
resp += ' machineIdentifier="%s"' % settings['uuid']
resp += ' product="PleXBMC"'
resp += ' platform="%s"' % getPlatform()
resp += ' platformVersion="%s"' % settings['plexbmc_version']
resp += ' deviceClass="pc"'
resp += "/>"
resp += "</MediaContainer>"
printDebug("crafted resources response: %s" % resp)
s.response(resp, getPlexHeaders())
elif "/subscribe" in request_path:
s.response(getOKMsg(), getPlexHeaders())
protocol = params.get('protocol', False)
host = s.client_address[0]
port = params.get('port', False)
uuid = s.headers.get('X-Plex-Client-Identifier', "")
commandID = params.get('commandID', 0)
subMgr.addSubscriber(protocol, host, port, uuid, commandID)
elif "/poll" in request_path:
if params.get('wait', False) == '1':
xbmc.sleep(950)
commandID = params.get('commandID', 0)
s.response(re.sub(r"INSERTCOMMANDID", str(commandID), subMgr.msg(getPlayers())), {
'X-Plex-Client-Identifier': settings['uuid'],
'Access-Control-Expose-Headers': 'X-Plex-Client-Identifier',
'Access-Control-Allow-Origin': '*',
'Content-Type': 'text/xml'
})
elif "/unsubscribe" in request_path:
s.response(getOKMsg(), getPlexHeaders())
uuid = s.headers.get('X-Plex-Client-Identifier', False) or s.client_address[0]
subMgr.removeSubscriber(uuid)
elif request_path == "player/playback/setParameters":
s.response(getOKMsg(), getPlexHeaders())
if 'volume' in params:
volume = int(params['volume'])
printDebug("adjusting the volume to %s%%" % volume)
jsonrpc("Application.SetVolume", {"volume": volume})
elif "/playMedia" in request_path:
s.response(getOKMsg(), getPlexHeaders())
resume = params.get('viewOffset', params.get('offset', "0"))
protocol = params.get('protocol', "http")
address = params.get('address', s.client_address[0])
server = getServerByHost(address)
port = params.get('port', server.get('port', '32400'))
fullurl = protocol+"://"+address+":"+port+params['key']
printDebug("playMedia command -> fullurl: %s" % fullurl)
jsonrpc("playmedia", [fullurl, resume])
subMgr.lastkey = params['key']
subMgr.server = server.get('server', 'localhost')
subMgr.port = port
subMgr.protocol = protocol
subMgr.notify()
elif request_path == "player/playback/play":
s.response(getOKMsg(), getPlexHeaders())
for playerid in getPlayerIds():
jsonrpc("Player.PlayPause", {"playerid" : playerid, "play": True})
elif request_path == "player/playback/pause":
s.response(getOKMsg(), getPlexHeaders())
for playerid in getPlayerIds():
jsonrpc("Player.PlayPause", {"playerid" : playerid, "play": False})
elif request_path == "player/playback/stop":
s.response(getOKMsg(), getPlexHeaders())
for playerid in getPlayerIds():
jsonrpc("Player.Stop", {"playerid" : playerid})
elif request_path == "player/playback/seekTo":
s.response(getOKMsg(), getPlexHeaders())
for playerid in getPlayerIds():
jsonrpc("Player.Seek", {"playerid":playerid, "value":millisToTime(params.get('offset', 0))})
subMgr.notify()
elif request_path == "player/playback/stepForward":
s.response(getOKMsg(), getPlexHeaders())
for playerid in getPlayerIds():
jsonrpc("Player.Seek", {"playerid":playerid, "value":"smallforward"})
subMgr.notify()
elif request_path == "player/playback/stepBack":
s.response(getOKMsg(), getPlexHeaders())
for playerid in getPlayerIds():
jsonrpc("Player.Seek", {"playerid":playerid, "value":"smallbackward"})
subMgr.notify()
elif request_path == "player/playback/skipNext":
s.response(getOKMsg(), getPlexHeaders())
for playerid in getPlayerIds():
jsonrpc("Player.Seek", {"playerid":playerid, "value":"bigforward"})
subMgr.notify()
elif request_path == "player/playback/skipPrevious":
s.response(getOKMsg(), getPlexHeaders())
for playerid in getPlayerIds():
jsonrpc("Player.Seek", {"playerid":playerid, "value":"bigbackward"})
subMgr.notify()
elif request_path == "player/navigation/moveUp":
s.response(getOKMsg(), getPlexHeaders())
jsonrpc("Input.Up")
elif request_path == "player/navigation/moveDown":
s.response(getOKMsg(), getPlexHeaders())
jsonrpc("Input.Down")
elif request_path == "player/navigation/moveLeft":
s.response(getOKMsg(), getPlexHeaders())
jsonrpc("Input.Left")
elif request_path == "player/navigation/moveRight":
s.response(getOKMsg(), getPlexHeaders())
jsonrpc("Input.Right")
elif request_path == "player/navigation/select":
s.response(getOKMsg(), getPlexHeaders())
jsonrpc("Input.Select")
elif request_path == "player/navigation/home":
s.response(getOKMsg(), getPlexHeaders())
jsonrpc("Input.Home")
elif request_path == "player/navigation/back":
s.response(getOKMsg(), getPlexHeaders())
jsonrpc("Input.Back")
except:
traceback.print_exc()
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
daemon_threads = True

View file

@ -0,0 +1,318 @@
"""
PlexGDM.py - Version 0.2
This class implements the Plex GDM (G'Day Mate) protocol to discover
local Plex Media Servers. Also allow client registration into all local
media servers.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
MA 02110-1301, USA.
"""
__author__ = 'DHJ (hippojay) <plex@h-jay.com>'
import socket
import struct
import sys
import re
import threading
import time
import urllib2
class plexgdm:
def __init__(self, debug=False):
self.discover_message = 'M-SEARCH * HTTP/1.0'
self.client_header = '* HTTP/1.0'
self.client_data = None
self.client_id = None
self._multicast_address = '239.0.0.250'
self.discover_group = (self._multicast_address, 32414)
self.client_register_group = (self._multicast_address, 32413)
self.client_update_port = 32412
self.server_list = []
self.discovery_interval = 120
self._discovery_is_running = False
self._registration_is_running = False
self.discovery_complete = False
self.client_registered = False
self.debug = debug
def __printDebug(self, message, level=1):
if self.debug:
print "PlexGDM: %s" % message
def clientDetails(self, c_id, c_name, c_post, c_product, c_version):
self.client_data = "Content-Type: plex/media-player\r\nResource-Identifier: %s\r\nName: %s\r\nPort: %s\r\nProduct: %s\r\nVersion: %s\r\nProtocol: plex\r\nProtocol-Version: 1\r\nProtocol-Capabilities: navigation,playback,timeline\r\nDevice-Class: HTPC" % ( c_id, c_name, c_post, c_product, c_version )
self.client_id = c_id
def getClientDetails(self):
if not self.client_data:
self.__printDebug("Client data has not been initialised. Please use PlexGDM.clientDetails()")
return self.client_data
def client_update (self):
update_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
#Set socket reuse, may not work on all OSs.
try:
update_sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
except:
pass
#Attempt to bind to the socket to recieve and send data. If we can;t do this, then we cannot send registration
try:
update_sock.bind(('0.0.0.0',self.client_update_port))
except:
self.__printDebug( "Error: Unable to bind to port [%s] - client will not be registered" % self.client_update_port, 0)
return
update_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 255)
status = update_sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, socket.inet_aton(self._multicast_address) + socket.inet_aton('0.0.0.0'))
update_sock.setblocking(0)
self.__printDebug("Sending registration data: HELLO %s\r\n%s" % (self.client_header, self.client_data), 3)
#Send initial client registration
try:
update_sock.sendto("HELLO %s\r\n%s" % (self.client_header, self.client_data), self.client_register_group)
except:
self.__printDebug( "Error: Unable to send registeration message" , 0)
#Now, listen for client discovery reguests and respond.
while self._registration_is_running:
try:
data, addr = update_sock.recvfrom(1024)
self.__printDebug("Recieved UDP packet from [%s] containing [%s]" % (addr, data.strip()), 3)
except socket.error, e:
pass
else:
if "M-SEARCH * HTTP/1." in data:
self.__printDebug("Detected client discovery request from %s. Replying" % ( addr ,) , 2)
try:
update_sock.sendto("HTTP/1.0 200 OK\r\n%s" % self.client_data, addr)
except:
self.__printDebug( "Error: Unable to send client update message",0)
self.__printDebug("Sending registration data: HTTP/1.0 200 OK\r\n%s" % (self.client_data), 3)
self.client_registered = True
time.sleep(0.5)
self.__printDebug("Client Update loop stopped",1)
#When we are finished, then send a final goodbye message to deregister cleanly.
self.__printDebug("Sending registration data: BYE %s\r\n%s" % (self.client_header, self.client_data), 3)
try:
update_sock.sendto("BYE %s\r\n%s" % (self.client_header, self.client_data), self.client_register_group)
except:
self.__printDebug( "Error: Unable to send client update message" ,0)
self.client_registered = False
def check_client_registration(self):
if self.client_registered and self.discovery_complete:
if not self.server_list:
self.__printDebug("Server list is empty. Unable to check",2)
return False
try:
media_server=self.server_list[0]['server']
media_port=self.server_list[0]['port']
self.__printDebug("Checking server [%s] on port [%s]" % (media_server, media_port) ,2)
f = urllib2.urlopen('http://%s:%s/clients' % (media_server, media_port))
client_result = f.read()
if self.client_id in client_result:
self.__printDebug("Client registration successful",1)
self.__printDebug("Client data is: %s" % client_result, 3)
return True
else:
self.__printDebug("Client registration not found",1)
self.__printDebug("Client data is: %s" % client_result, 3)
except:
self.__printDebug("Unable to check status")
pass
return False
def getServerList (self):
return self.server_list
def discover(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# Set a timeout so the socket does not block indefinitely
sock.settimeout(0.6)
# Set the time-to-live for messages to 1 for local network
ttl = struct.pack('b', 1)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl)
returnData = []
try:
# Send data to the multicast group
self.__printDebug("Sending discovery messages: %s" % self.discover_message, 2)
sent = sock.sendto(self.discover_message, self.discover_group)
# Look for responses from all recipients
while True:
try:
data, server = sock.recvfrom(1024)
self.__printDebug("Received data from %s, %s" % server, 3)
self.__printDebug("Data received is:\r\n %s" % data, 3)
returnData.append( { 'from' : server,
'data' : data } )
except socket.timeout:
break
except:
# if we can't send our discovery query, just abort and try again
# on the next loop
return
finally:
sock.close()
self.discovery_complete = True
discovered_servers = []
if returnData:
for response in returnData:
update = { 'server' : response.get('from')[0] }
#Check if we had a positive HTTP response
if "200 OK" in response.get('data'):
for each in response.get('data').split('\r\n'):
update['discovery'] = "auto"
update['owned']='1'
update['master']= 1
update['role']='master'
update['class']=None
if "Content-Type:" in each:
update['content-type'] = each.split(':')[1].strip()
elif "Resource-Identifier:" in each:
update['uuid'] = each.split(':')[1].strip()
elif "Name:" in each:
update['serverName'] = each.split(':')[1].strip()
elif "Port:" in each:
update['port'] = each.split(':')[1].strip()
elif "Updated-At:" in each:
update['updated'] = each.split(':')[1].strip()
elif "Version:" in each:
update['version'] = each.split(':')[1].strip()
elif "Server-Class:" in each:
update['class'] = each.split(':')[1].strip()
discovered_servers.append(update)
self.server_list = discovered_servers
if not self.server_list:
self.__printDebug("No servers have been discovered",1)
else:
self.__printDebug("Number of servers Discovered: %s" % len(self.server_list),1)
for items in self.server_list:
self.__printDebug("Server Discovered: %s" % items['serverName'] ,2)
def setInterval(self, interval):
self.discovery_interval = interval
def stop_all(self):
self.stop_discovery()
self.stop_registration()
def stop_discovery(self):
if self._discovery_is_running:
self.__printDebug("Discovery shutting down", 1)
self._discovery_is_running = False
self.discover_t.join()
del self.discover_t
else:
self.__printDebug("Discovery not running", 1)
def stop_registration(self):
if self._registration_is_running:
self.__printDebug("Registration shutting down", 1)
self._registration_is_running = False
self.register_t.join()
del self.register_t
else:
self.__printDebug("Registration not running", 1)
def run_discovery_loop(self):
#Run initial discovery
self.discover()
discovery_count=0
while self._discovery_is_running:
discovery_count+=1
if discovery_count > self.discovery_interval:
self.discover()
discovery_count=0
time.sleep(1)
def start_discovery(self, daemon = False):
if not self._discovery_is_running:
self.__printDebug("Discovery starting up", 1)
self._discovery_is_running = True
self.discover_t = threading.Thread(target=self.run_discovery_loop)
self.discover_t.setDaemon(daemon)
self.discover_t.start()
else:
self.__printDebug("Discovery already running", 1)
def start_registration(self, daemon = False):
if not self._registration_is_running:
self.__printDebug("Registration starting up", 1)
self._registration_is_running = True
self.register_t = threading.Thread(target=self.client_update)
self.register_t.setDaemon(daemon)
self.register_t.start()
else:
self.__printDebug("Registration already running", 1)
def start_all(self, daemon = False):
self.start_discovery(daemon)
self.start_registration(daemon)
#Example usage
if __name__ == '__main__':
client = plexgdm(debug=3)
client.clientDetails("Test-Name", "Test Client", "3003", "Test-App", "1.2.3")
client.start_all()
while not client.discovery_complete:
print "Waiting for results"
time.sleep(1)
time.sleep(20)
print client.getServerList()
if client.check_client_registration():
print "Successfully registered"
else:
print "Unsuccessfully registered"
client.stop_all()

View file

@ -0,0 +1,42 @@
import uuid
import xbmc
import xbmcaddon
from xml.dom.minidom import parse
settings = {}
try:
guidoc = parse(xbmc.translatePath('special://userdata/guisettings.xml'))
except:
print "Unable to read XBMC's guisettings.xml"
def getGUI(name):
global guidoc
if guidoc is None:
return False
try:
return guidoc.getElementsByTagName(name)[0].firstChild.nodeValue
except:
return ""
addon = xbmcaddon.Addon()
plexbmc = xbmcaddon.Addon('plugin.video.plexbmc')
settings['debug'] = addon.getSetting('debug') == "true"
settings['gdm_debug'] = addon.getSetting('gdm_debug') == "true"
if addon.getSetting('use_xbmc_name') == "true":
settings['client_name'] = getGUI('devicename')
else:
settings['client_name'] = addon.getSetting('c_name')
# XBMC web server settings
settings['webserver_enabled'] = (getGUI('webserver') == "true")
settings['port'] = int(getGUI('webserverport'))
settings['user'] = getGUI('webserverusername')
settings['passwd'] = getGUI('webserverpassword')
settings['uuid'] = str(addon.getSetting('uuid')) or str(uuid.uuid4())
addon.setSetting('uuid', settings['uuid'])
settings['version'] = addon.getAddonInfo('version')
settings['plexbmc_version'] = plexbmc.getAddonInfo('version')
settings['myplex_user'] = plexbmc.getSetting('myplex_user')
settings['serverList'] = []
settings['myport'] = 3005

View file

@ -0,0 +1,197 @@
import re
import threading
import xbmcgui
from xml.dom.minidom import parseString
from functions import *
from settings import settings
from httppersist import requests
class SubscriptionManager:
def __init__(self):
self.subscribers = {}
self.info = {}
self.lastkey = ""
self.lastratingkey = ""
self.volume = 0
self.guid = ""
self.server = ""
self.protocol = "http"
self.port = ""
self.playerprops = {}
self.sentstopped = True
def getVolume(self):
self.volume = getVolume()
def msg(self, players):
msg = getXMLHeader()
msg += '<MediaContainer commandID="INSERTCOMMANDID"'
if players:
self.getVolume()
maintype = plex_audio()
for p in players.values():
if p.get('type') == xbmc_video():
maintype = plex_video()
elif p.get('type') == xbmc_photo():
maintype = plex_photo()
self.mainlocation = "fullScreen" + maintype[0:1].upper() + maintype[1:].lower()
else:
self.mainlocation = "navigation"
msg += ' location="%s">' % self.mainlocation
msg += self.getTimelineXML(getAudioPlayerId(players), plex_audio())
msg += self.getTimelineXML(getPhotoPlayerId(players), plex_photo())
msg += self.getTimelineXML(getVideoPlayerId(players), plex_video())
msg += "\r\n</MediaContainer>"
return msg
def getTimelineXML(self, playerid, ptype):
if playerid is not None:
info = self.getPlayerProperties(playerid)
# save this info off so the server update can use it too
self.playerprops[playerid] = info;
state = info['state']
time = info['time']
else:
state = "stopped"
time = 0
ret = "\r\n"+'<Timeline location="%s" state="%s" time="%s" type="%s"' % (self.mainlocation, state, time, ptype)
if playerid is not None:
WINDOW = xbmcgui.Window(10000)
pbmc_server = str(WINDOW.getProperty('plexbmc.nowplaying.server'))
keyid = str(WINDOW.getProperty('plexbmc.nowplaying.id'))
if keyid:
self.lastkey = "/library/metadata/%s"%keyid
self.lastratingkey = keyid
if pbmc_server:
(self.server, self.port) = pbmc_server.split(':')
serv = getServerByHost(self.server)
ret += ' duration="%s"' % info['duration']
ret += ' seekRange="0-%s"' % info['duration']
ret += ' controllable="%s"' % self.controllable()
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 += ' guid="%s"' % info['guid']
ret += ' containerKey="%s"' % (self.lastkey or "/library/metadata/900000")
ret += ' key="%s"' % (self.lastkey or "/library/metadata/900000")
ret += ' ratingKey="%s"' % (self.lastratingkey or "900000")
ret += ' volume="%s"' % info['volume']
ret += ' shuffle="%s"' % info['shuffle']
ret += '/>'
return ret
def updateCommandID(self, uuid, commandID):
if commandID and self.subscribers.get(uuid, False):
self.subscribers[uuid].commandID = int(commandID)
def notify(self, event = False):
self.cleanup()
players = getPlayers()
# 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)
self.notifyServer(players)
return True
def notifyServer(self, players):
if not players and self.sentstopped: return True
params = {'state': 'stopped'}
for p in players.values():
info = self.playerprops[p.get('playerid')]
params = {}
params['containerKey'] = (self.lastkey or "/library/metadata/900000")
params['key'] = (self.lastkey or "/library/metadata/900000")
params['ratingKey'] = (self.lastratingkey or "900000")
params['state'] = info['state']
params['time'] = info['time']
params['duration'] = info['duration']
serv = getServerByHost(self.server)
requests.getwithparams(serv.get('server', 'localhost'), serv.get('port', 32400), "/:/timeline", params, getPlexHeaders(), serv.get('protocol', 'http'))
printDebug("sent server notification with state = %s" % params['state'])
WINDOW = xbmcgui.Window(10000)
WINDOW.setProperty('plexbmc.nowplaying.sent', '1')
if players:
self.sentstopped = False
else:
self.sentstopped = True
def controllable(self):
return "playPause,play,stop,skipPrevious,skipNext,volume,stepBack,stepForward,seekTo"
def addSubscriber(self, protocol, host, port, uuid, commandID):
sub = Subscriber(protocol, host, port, uuid, commandID)
with threading.RLock():
self.subscribers[sub.uuid] = sub
return sub
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]
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):
info = {}
try:
# get info from the player
props = jsonrpc("Player.GetProperties", {"playerid": playerid, "properties": ["time", "totaltime", "speed", "shuffled"]})
printDebug(jsonrpc("Player.GetItem", {"playerid": playerid, "properties": ["file", "showlink", "episode", "season"]}))
info['time'] = timeToMillis(props['time'])
info['duration'] = timeToMillis(props['totaltime'])
info['state'] = ("paused", "playing")[int(props['speed'])]
info['shuffle'] = ("0","1")[props.get('shuffled', False)]
except:
info['time'] = 0
info['duration'] = 0
info['state'] = "stopped"
info['shuffle'] = False
# get the volume from the application
info['volume'] = self.volume
info['guid'] = self.guid
return info
class Subscriber:
def __init__(self, protocol, host, port, uuid, commandID):
self.protocol = protocol or "http"
self.host = host
self.port = port or 32400
self.uuid = uuid or host
self.commandID = int(commandID) or 0
self.navlocationsent = False
self.age = 0
def __eq__(self, other):
return self.uuid == other.uuid
def tostr(self):
return "uuid=%s,commandID=%i" % (self.uuid, self.commandID)
def cleanup(self):
requests.closeConnection(self.protocol, self.host, self.port)
def send_update(self, msg, is_nav):
self.age += 1
if not is_nav:
self.navlocationsent = False
elif self.navlocationsent:
return True
else:
self.navlocationsent = True
msg = re.sub(r"INSERTCOMMANDID", str(self.commandID), msg)
printDebug("sending xml to subscriber %s: %s" % (self.tostr(), msg))
if not requests.post(self.host, self.port, "/:/timeline", msg, getPlexHeaders(), self.protocol):
subMgr.removeSubscriber(self.uuid)
subMgr = SubscriptionManager()