Plex Companien (Plexbmc helper) version 0.1

This commit is contained in:
tomkat83 2016-01-22 15:37:20 +01:00
parent e859a807bc
commit 48ba7f0869
16 changed files with 274 additions and 86 deletions

View file

@ -8,7 +8,6 @@ import urlparse
import xbmc
import xbmcaddon
import xbmcgui
#################################################################################################
@ -22,9 +21,6 @@ sys.path.append(base_resource)
import entrypoint
import utils
import PlexAPI
import userclient
#################################################################################################
enableProfiling = False
@ -34,8 +30,8 @@ class Main:
# MAIN ENTRY POINT
def __init__(self):
plx = PlexAPI.PlexAPI()
# Parse parameters
xbmc.log("Full sys.argv received: %s" % sys.argv)
base_url = sys.argv[0]
addon_handle = int(sys.argv[1])
params = urlparse.parse_qs(sys.argv[2][1:])
@ -44,11 +40,15 @@ class Main:
mode = params['mode'][0]
itemid = params.get('id')
if itemid:
itemid = itemid[0]
try:
itemid = itemid[0]
except:
pass
except:
params = {}
mode = ""
xbmc.log("mode: %s, itemid: %s, base_url: %s, addon_handle: %s"
% (mode, itemid, base_url, addon_handle), 2)
modes = {
@ -65,7 +65,8 @@ class Main:
'nextup': entrypoint.getNextUpEpisodes,
'inprogressepisodes': entrypoint.getInProgressEpisodes,
'recentepisodes': entrypoint.getRecentEpisodes,
'refreshplaylist': entrypoint.refreshPlaylist
'refreshplaylist': entrypoint.refreshPlaylist,
'companion': entrypoint.plexCompanion
}
if "extrafanart" in sys.argv[0]:
@ -90,7 +91,9 @@ class Main:
elif mode == "channelsfolder":
folderid = params['folderid'][0]
modes[mode](itemid, folderid)
elif mode == "companion":
resume = params.get('resume', '')
modes[mode](itemid, resume=resume)
else:
modes[mode]()
else:

View file

@ -53,15 +53,13 @@ import requests
import re
import json
import uuid
from urllib import urlencode, quote_plus
try:
import xml.etree.cElementTree as etree
except ImportError:
import xml.etree.ElementTree as etree
from urllib import urlencode, quote_plus
# from Version import __VERSION__
# from Debug import * # dprint(), prettyXML()
@ -1499,6 +1497,7 @@ class PlexAPI():
xml = self.doUtils.downloadUrl(url, headerOptions=headerOptions)
if not xml:
self.logMsg("Error retrieving metadata for %s" % url, -1)
xml = None
return xml

View file

@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
import threading
import traceback
import socket
import requests
import xbmc
import clientinfo
import utils
from plexbmchelper import listener, plexgdm, subscribers
from plexbmchelper.settings import settings
class PlexCompanion(threading.Thread):
def __init__(self):
self._shouldStop = threading.Event()
self.port = int(utils.settings('companionPort'))
ci = clientinfo.ClientInfo()
self.addonName = ci.getAddonName()
self.clientId = ci.getDeviceId()
self.deviceName = ci.getDeviceName()
self.logMsg("----===## Starting PlexBMC Helper ##===----", 1)
# Start GDM for server/client discovery
self.client = plexgdm.plexgdm(debug=settings['gdm_debug'])
self.client.clientDetails(self.clientId, # UUID
self.deviceName, # clientName
self.port,
self.addonName,
'1.0') # Version
self.logMsg("Registration string is: %s "
% self.client.getClientDetails(), 1)
threading.Thread.__init__(self)
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def stopClient(self):
# When emby for kodi terminates
self._shouldStop.set()
def stopped(self):
return self._shouldStop.isSet()
def run(self):
start_count = 0
while True:
try:
httpd = listener.ThreadedHTTPServer(
('', self.port),
listener.MyHandler)
httpd.timeout = 0.95
break
except:
self.logMsg("Unable to start PlexCompanion. Traceback:", -1)
self.logMsg(traceback.print_exc(), -1)
xbmc.sleep(3000)
if start_count == 3:
self.logMsg("Error: Unable to start web helper.", -1)
httpd = False
break
start_count += 1
if not httpd:
return
self.client.start_all()
message_count = 0
is_running = False
while not self.stopped():
try:
httpd.handle_request()
message_count += 1
if message_count > 30:
if self.stopped():
break
if self.client.check_client_registration():
self.logMsg("Client is still registered", 1)
else:
self.logMsg("Client is no longer registered",
1)
self.logMsg("PlexBMC Helper still running on "
"port %s" % self.port, 1)
message_count = 0
if not is_running:
self.logMsg("PleXBMC Helper has started", 0)
is_running = True
if message_count % 1 == 0:
subscribers.subMgr.notify()
settings['serverList'] = self.client.getServerList()
except:
self.logMsg("Error in loop, continuing anyway", 1)
self.logMsg(traceback.print_exc(), 1)
self.client.stop_all()
try:
httpd.socket.shutdown(socket.SHUT_RDWR)
finally:
httpd.socket.close()
requests.dumpConnections()
self.logMsg("----===## STOP PlexBMC Helper ##===----", 0)

View file

@ -218,6 +218,9 @@ class DownloadUtils():
r = s.delete(url, json=postBody, timeout=timeout, headers=header)
elif type == "OPTIONS":
r = s.options(url, json=postBody, timeout=timeout, headers=header)
# For Plex Companion
elif type == "POSTXML":
r = s.post(url, postBody, timeout=timeout, headers=header)
except AttributeError:
# request session does not exists

View file

@ -6,6 +6,7 @@ import json
import os
import sys
import urlparse
import re
import xbmc
import xbmcaddon
@ -26,10 +27,33 @@ import playutils
import api
import PlexAPI
import embydb_functions
#################################################################################################
def plexCompanion(fullurl, resume=""):
regex = re.compile(r'''/(\d+)$''')
itemid = regex.findall(fullurl)
try:
itemid = itemid[0]
except IndexError:
# No matches found, url not like:
# http://192.168.0.2:32400/library/metadata/243480
return False
# TODO: direct play an URL
# Initialize embydb
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby = embydb_functions.Embydb_Functions(embycursor)
# Get dbid using itemid
dbid = emby.getItem_byId(itemid)[0]
embyconn.close()
# Start playing
item = PlexAPI.PlexAPI().GetPlexMetadata(itemid)
pbutils.PlaybackUtils(item).play(itemid, dbid)
def doPlayback(itemid, dbid):
# Get a first XML to get the librarySectionUUID
item = PlexAPI.PlexAPI().GetPlexMetadata(itemid)

View file

@ -4,6 +4,7 @@
import threading
from datetime import datetime, timedelta
import Queue
import xbmc
import xbmcgui
@ -21,7 +22,6 @@ import userclient
import videonodes
import PlexAPI
import Queue
##################################################################################################
@ -46,14 +46,9 @@ class ThreadedGetMetadata(threading.Thread):
self.lock = lock
self.userStop = userStop
self._shouldstop = threading.Event()
self.addonName = clientinfo.ClientInfo().getAddonName()
threading.Thread.__init__(self)
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def run_internal(self):
def run(self):
plx = PlexAPI.PlexAPI()
global getMetadataCount
while self.stopped() is False:
@ -68,25 +63,22 @@ class ThreadedGetMetadata(threading.Thread):
plexXML = plx.GetPlexMetadata(updateItem['itemId'])
except:
raise
updateItem['XML'] = plexXML
# place item into out queue
self.out_queue.put(updateItem)
# check whether valid XML
try:
# .tag works for XML
plexXML.tag
updateItem['XML'] = plexXML
# place item into out queue
self.out_queue.put(updateItem)
# If we don't have a valid XML, don't put that into the queue
except AttributeError:
pass
# Keep track of where we are at
with self.lock:
getMetadataCount += 1
# signals to queue job is done
self.queue.task_done()
def run(self):
try:
self.run_internal()
except Exception as e:
xbmcgui.Dialog().ok(self.addonName,
"A sync thread has exited! "
"You should restart Kodi now. "
"Please report this on the forum.")
raise
def stopThread(self):
self._shouldstop.set()
@ -113,14 +105,9 @@ class ThreadedProcessMetadata(threading.Thread):
self.itemType = itemType
self.userStop = userStop
self._shouldstop = threading.Event()
self.addonName = clientinfo.ClientInfo().getAddonName()
threading.Thread.__init__(self)
def logMsg(self, msg, lvl=1):
className = self.__class__.__name__
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
def run_internal(self):
def run(self):
# Constructs the method name, e.g. itemtypes.Movies
itemFkt = getattr(itemtypes, self.itemType)
global processMetadataCount
@ -152,16 +139,6 @@ class ThreadedProcessMetadata(threading.Thread):
# signals to queue job is done
self.queue.task_done()
def run(self):
try:
self.run_internal()
except Exception as e:
xbmcgui.Dialog().ok(self.addonName,
"A sync thread has exited! "
"You should restart Kodi now. "
"Please report this on the forum.")
raise
def stopThread(self):
self._shouldstop.set()

View file

@ -73,7 +73,9 @@ def jsonrpc(action, arguments = {}):
elif action.lower() == "playmedia":
fullurl=arguments[0]
resume=arguments[1]
xbmc.Player().play("plugin://plugin.video.plexbmc/?mode=5&force="+resume+"&url="+fullurl)
xbmc.Player().play("plugin://plugin.video.plexkodiconnect/"
"?mode=companion&resume=%s&id=%s"
% (resume, fullurl))
return True
elif arguments:
request=json.dumps({ "id" : 1,
@ -127,7 +129,7 @@ def getPlexHeaders():
"X-Plex-Version": settings['version'],
"X-Plex-Client-Identifier": settings['uuid'],
"X-Plex-Provides": "player",
"X-Plex-Product": "PleXBMC",
"X-Plex-Product": "PlexKodiConnect",
"X-Plex-Device-Name": settings['client_name'],
"X-Plex-Platform": "XBMC",
"X-Plex-Model": getPlatform(),

View file

@ -74,7 +74,7 @@ class MyHandler(BaseHTTPRequestHandler):
resp += ' protocolVersion="1"'
resp += ' protocolCapabilities="navigation,playback,timeline"'
resp += ' machineIdentifier="%s"' % settings['uuid']
resp += ' product="PleXBMC"'
resp += ' product="PlexKodiConnect"'
resp += ' platform="%s"' % getPlatform()
resp += ' platformVersion="%s"' % settings['plexbmc_version']
resp += ' deviceClass="pc"'

View file

@ -31,6 +31,7 @@ import re
import threading
import time
import urllib2
import downloadutils
class plexgdm:
@ -140,9 +141,11 @@ class plexgdm:
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:
client_result = downloadutils.DownloadUtils().downloadUrl(
'http://%s:%s/clients' % (media_server, media_port))
# f = urllib2.urlopen('http://%s:%s/clients' % (media_server, media_port))
# client_result = f.read()
if self.client_id in str(client_result):
self.__printDebug("Client registration successful",1)
self.__printDebug("Client data is: %s" % client_result, 3)
return True

View file

@ -7,7 +7,7 @@ settings = {}
try:
guidoc = parse(xbmc.translatePath('special://userdata/guisettings.xml'))
except:
print "Unable to read XBMC's guisettings.xml"
print "Unable to read XBMC's guisettings.xml"
def getGUI(name):
global guidoc
@ -19,24 +19,28 @@ def getGUI(name):
return ""
addon = xbmcaddon.Addon()
plexbmc = xbmcaddon.Addon('plugin.video.plexbmc')
plexbmc = xbmcaddon.Addon('plugin.video.plexkodiconnect')
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')
if plexbmc.getSetting('logLevel') == '2' or \
plexbmc.getSetting('logLevel') == '1':
settings['debug'] = 'true'
settings['gdm_debug'] = 'true'
else:
settings['client_name'] = addon.getSetting('c_name')
settings['debug'] = 'false'
settings['gdm_debug'] = 'false'
settings['client_name'] = plexbmc.getSetting('deviceName')
# 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['uuid'] = plexbmc.getSetting('plex_client_Id')
settings['version'] = plexbmc.getAddonInfo('version')
settings['plexbmc_version'] = plexbmc.getAddonInfo('version')
settings['myplex_user'] = plexbmc.getSetting('myplex_user')
settings['myplex_user'] = plexbmc.getSetting('username')
settings['serverList'] = []
settings['myport'] = 3005
settings['myport'] = addon.getSetting('companionPort')

View file

@ -5,6 +5,7 @@ from xml.dom.minidom import parseString
from functions import *
from settings import settings
from httppersist import requests
import downloadutils
class SubscriptionManager:
def __init__(self):
@ -19,6 +20,7 @@ class SubscriptionManager:
self.port = ""
self.playerprops = {}
self.sentstopped = True
self.download = downloadutils.DownloadUtils()
def getVolume(self):
self.volume = getVolume()
@ -113,7 +115,11 @@ class SubscriptionManager:
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'))
url = serv.get('protocol', 'http') + '://' \
+ serv.get('server', 'localhost') + ':' \
+ serv.get('port', 32400) + "/:/timeline"
self.download.downloadUrl(url, type="GET", parameters=params)
# 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')
@ -175,6 +181,7 @@ class Subscriber:
self.commandID = int(commandID) or 0
self.navlocationsent = False
self.age = 0
self.download = downloadutils.DownloadUtils()
def __eq__(self, other):
return self.uuid == other.uuid
def tostr(self):
@ -191,7 +198,14 @@ class Subscriber:
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):
url = self.protocol + '://' + self.host + ':' + self.port \
+ "/:/timeline"
response = self.download.downloadUrl(url,
postBody=msg,
type="POSTXML")
# if not requests.post(self.host, self.port, "/:/timeline", msg, getPlexHeaders(), self.protocol):
# subMgr.removeSubscriber(self.uuid)
if response in [False, 401]:
subMgr.removeSubscriber(self.uuid)
subMgr = SubscriptionManager()

View file

@ -15,9 +15,39 @@ import xbmcaddon
import xbmcgui
import xbmcvfs
import clientinfo
#################################################################################################
class logDecor(object):
"""
A decorator adding logging capabilities.
Syntax: self.logMsg(message, loglevel)
Loglevel: -2 (Error) to 2 (DB debug)
"""
def __init__(self, f):
"""
If there are no decorator arguments, the function to be decorated is
passed to the constructor.
"""
self.f = f
self.addonName = clientinfo.ClientInfo().getAddonName()
def __call__(self, *args, **kwargs):
"""
The __call__ method is not called until the
decorated function is called.
"""
def decorLog(self, msg, lvl=1):
className = self.__class__.__name__
logMsg("%s %s" % (self.addonName, className), msg, lvl)
# The function itself:
self.f(*args, **kwargs)
def logMsg(title, msg, level=1):
# Get the logLevel set in UserClient

View file

@ -75,4 +75,8 @@
<setting label="30239" type="action" action="RunPlugin(plugin://plugin.video.plexkodiconnect?mode=reset)" option="close" />
<setting id="syncThreadNumber" type="number" label="Number of parallel threads while syncing" default="10" option="int"/>
</category>
<category label="Plex Companion">
<setting id="plexCompanion" label="Enable Plex Companion" type="bool" default="true" />
<setting id="companionPort" label="Plex Companion Port" type="number" default="3005" option="int" visible="eq(-1,true)"/>
</category>
</settings>

View file

@ -4,13 +4,13 @@
import os
import sys
import time
# import time
from datetime import datetime
import xbmc
import xbmcaddon
import xbmcgui
import xbmcvfs
# import xbmcvfs
#################################################################################################
@ -29,9 +29,10 @@ import librarysync
import player
import utils
import videonodes
import websocket_client as wsc
# import websocket_client as wsc
import PlexAPI
import PlexCompanion
#################################################################################################
@ -46,6 +47,7 @@ class Service():
websocket_running = False
library_running = False
kodimonitor_running = False
plexCompanion_running = False
def __init__(self):
@ -106,6 +108,9 @@ class Service():
# ws = wsc.WebSocket_Client()
library = librarysync.LibrarySync()
kplayer = player.Player()
plx = PlexAPI.PlexAPI()
plexCompanion = PlexCompanion.PlexCompanion()
plexCompanionDesired = utils.settings('plexCompanion')
# Sync and progress report
lastProgressUpdate = datetime.today()
@ -223,10 +228,7 @@ class Service():
# No server info set in add-on settings
pass
elif PlexAPI.PlexAPI().CheckConnection(
server,
plexToken
) != 200:
elif plx.CheckConnection(server, plexToken) != 200:
# Server is offline.
# Alert the user and suppress future warning
if self.server_online:
@ -234,10 +236,12 @@ class Service():
utils.window('emby_online', value="false")
xbmcgui.Dialog().notification(
heading="Error connecting",
message="%s Server is unreachable." % self.addonName,
icon="special://home/addons/plugin.video.plexkodiconnect/icon.png",
sound=False)
heading="Error connecting",
message="%s Server is unreachable."
% self.addonName,
icon="special://home/addons/plugin.video."
"plexkodiconnect/icon.png",
sound=False)
self.server_online = False
@ -251,11 +255,12 @@ class Service():
break
# Alert the user that server is online.
xbmcgui.Dialog().notification(
heading="Emby server",
message="Server is online.",
icon="special://home/addons/plugin.video.plexkodiconnect/icon.png",
time=2000,
sound=False)
heading="Emby server",
message="Server is online.",
icon="special://home/addons/plugin.video."
"plexkodiconnect/icon.png",
time=2000,
sound=False)
self.server_online = True
self.logMsg("Server is online and ready.", 1)
@ -266,6 +271,12 @@ class Service():
self.userclient_running = True
user.start()
# Start the Plex Companion thread
if not self.plexCompanion_running and \
plexCompanionDesired == "true":
self.plexCompanion_running = True
plexCompanion.start()
break
if monitor.waitForAbort(1):
@ -278,11 +289,14 @@ class Service():
##### Emby thread is terminating. #####
if self.plexCompanion_running:
plexCompanion.stopClient()
if self.library_running:
library.stopThread()
if self.websocket_running:
ws.stopClient()
# if self.websocket_running:
# ws.stopClient()
if self.userclient_running:
user.stopClient()