Overhaul Part 1

This commit is contained in:
tomkat83 2016-01-29 20:07:21 +01:00
parent 99895ec49f
commit 8912a0b601
16 changed files with 719 additions and 465 deletions

View file

@ -91,8 +91,7 @@ class Main:
folderid = params['folderid'][0] folderid = params['folderid'][0]
modes[mode](itemid, folderid) modes[mode](itemid, folderid)
elif mode == "companion": elif mode == "companion":
resume = params.get('resume', '')[0] modes[mode](itemid, params=sys.argv[2])
modes[mode](itemid, resume=resume)
else: else:
modes[mode]() modes[mode]()
else: else:

View file

@ -55,6 +55,8 @@ import re
import json import json
from urllib import urlencode, quote_plus from urllib import urlencode, quote_plus
from PlexFunctions import PlexToKodiTimefactor
try: try:
import xml.etree.cElementTree as etree import xml.etree.cElementTree as etree
except ImportError: except ImportError:
@ -198,9 +200,9 @@ class PlexAPI():
'avatar': avatar, 'avatar': avatar,
'token': token 'token': token
} }
utils.settings('plexLogin', value=username) utils.settings('plexLogin', username)
utils.settings('plexToken', value=token) utils.settings('plexToken', token)
utils.settings('plexhome', value=home) utils.settings('plexhome', home)
return result return result
def CheckPlexTvSignin(self, identifier): def CheckPlexTvSignin(self, identifier):
@ -339,8 +341,8 @@ class PlexAPI():
verify=sslverify, verify=sslverify,
timeout=timeout) timeout=timeout)
except requests.exceptions.ConnectionError as e: except requests.exceptions.ConnectionError as e:
self.logMsg("Server is offline or cannot be reached. Url: %s." self.logMsg("Server is offline or cannot be reached. Url: %s "
"Header: %s. Error message: %s" "Header: %s Error message: %s"
% (url, header, e), -1) % (url, header, e), -1)
return False return False
except requests.exceptions.ReadTimeout: except requests.exceptions.ReadTimeout:
@ -781,8 +783,8 @@ class PlexAPI():
""" """
# Get addon infos # Get addon infos
xargs = { xargs = {
"Content-type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
"Access-Control-Allow-Origin": "*", # "Access-Control-Allow-Origin": "*",
'X-Plex-Language': 'en', 'X-Plex-Language': 'en',
'X-Plex-Device': self.addonName, 'X-Plex-Device': self.addonName,
'X-Plex-Client-Platform': self.platform, 'X-Plex-Client-Platform': self.platform,
@ -1371,8 +1373,6 @@ class API():
def __init__(self, item): def __init__(self, item):
self.item = item self.item = item
# which child in the XML response shall we look at?
self.child = 0
# which media part in the XML response shall we look at? # which media part in the XML response shall we look at?
self.part = 0 self.part = 0
self.clientinfo = clientinfo.ClientInfo() self.clientinfo = clientinfo.ClientInfo()
@ -1383,18 +1383,6 @@ class API():
self.jumpback = int(utils.settings('resumeJumpBack')) self.jumpback = int(utils.settings('resumeJumpBack'))
def setChildNumber(self, number=0):
"""
Which child in the XML response shall we look at and work with?
"""
self.child = int(number)
def getChildNumber(self):
"""
Returns the child in the XML response that we're currently looking at
"""
return self.child
def setPartNumber(self, number=0): def setPartNumber(self, number=0):
""" """
Sets the part number to work with (used to deal with Movie with several Sets the part number to work with (used to deal with Movie with several
@ -1435,12 +1423,9 @@ class API():
def getType(self): def getType(self):
""" """
Returns the type of media, e.g. 'movie' Returns the type of media, e.g. 'movie' or 'clip' for trailers
""" """
item = self.item return self.item['type']
item = item[self.child].attrib
itemtype = item['type']
return itemtype
def getChecksum(self): def getChecksum(self):
""" """
@ -1450,47 +1435,58 @@ class API():
item = self.item item = self.item
# XML # XML
try: try:
item = item[self.child].attrib item = item[0].attrib
# JSON # JSON
except KeyError: except (AttributeError, KeyError):
pass pass
# Include a letter to prohibit saving as an int! # Include a letter to prohibit saving as an int!
checksum = "K%s%s" % (self.getKey(), checksum = "K%s%s" % (self.getRatingKey(),
item.get('updatedAt', '')) item.get('updatedAt', ''))
return checksum return checksum
def getKey(self): def getRatingKey(self):
""" """
Can be used on both XML and JSON Can be used on both XML and JSON
Returns the Plex unique movie id as a str, not int Returns the Plex key such as '246922' as a string
""" """
item = self.item item = self.item
# XML # XML
try: try:
item = item[self.child].attrib item = item[0].attrib
# JSON # JSON
except KeyError: except (AttributeError, KeyError):
pass pass
key = item['ratingKey'] key = item['ratingKey']
return str(key) return str(key)
def getKey(self):
"""
Can be used on both XML and JSON
Returns the Plex key such as '/library/metadata/246922'
"""
item = self.item
# XML
try:
item = item[0].attrib
# JSON
except (AttributeError, KeyError):
pass
key = item['key']
return str(key)
def getIndex(self): def getIndex(self):
""" """
Returns the 'index' of an PMS XML reply. Depicts e.g. season number. Returns the 'index' of an PMS XML reply. Depicts e.g. season number.
""" """
item = self.item[self.child].attrib item = self.item[0].attrib
index = item['index'] index = item['index']
return str(index) return str(index)
def getDateCreated(self): def getDateCreated(self):
""" """
Returns the date when this library item was created Returns the date when this library item was created
Input:
index child number as int; normally =0
""" """
item = self.item item = self.item[0].attrib
item = item[self.child].attrib
dateadded = item['addedAt'] dateadded = item['addedAt']
dateadded = self.convert_date(dateadded) dateadded = self.convert_date(dateadded)
return dateadded return dateadded
@ -1517,7 +1513,13 @@ class API():
resume = 0 resume = 0
rating = 0 rating = 0
item = item[self.child].attrib # XML
try:
item = item[0].attrib
# JSON
except (AttributeError, KeyError):
pass
try: try:
playcount = int(item['viewCount']) playcount = int(item['viewCount'])
except KeyError: except KeyError:
@ -1558,7 +1560,7 @@ class API():
writer = [] writer = []
cast = [] cast = []
producer = [] producer = []
for child in item[self.child]: for child in item[0]:
if child.tag == 'Director': if child.tag == 'Director':
director.append(child.attrib['tag']) director.append(child.attrib['tag'])
elif child.tag == 'Writer': elif child.tag == 'Writer':
@ -1594,7 +1596,7 @@ class API():
'Role': 'Actor', 'Role': 'Actor',
'Producer': 'Producer' 'Producer': 'Producer'
} }
for child in item[self.child]: for child in item[0]:
if child.tag in people_of_interest.keys(): if child.tag in people_of_interest.keys():
name = child.attrib['tag'] name = child.attrib['tag']
name_id = child.attrib['id'] name_id = child.attrib['id']
@ -1626,7 +1628,7 @@ class API():
""" """
item = self.item item = self.item
genre = [] genre = []
for child in item[self.child]: for child in item[0]:
if child.tag == 'Genre': if child.tag == 'Genre':
genre.append(child.attrib['tag']) genre.append(child.attrib['tag'])
return genre return genre
@ -1638,7 +1640,7 @@ class API():
Return IMDB, e.g. "imdb://tt0903624?lang=en". Returns None if not found Return IMDB, e.g. "imdb://tt0903624?lang=en". Returns None if not found
""" """
item = self.item item = self.item
item = item[self.child].attrib item = item[0].attrib
try: try:
item = item['guid'] item = item['guid']
except KeyError: except KeyError:
@ -1663,12 +1665,14 @@ class API():
sorttitle = title, if no sorttitle is found sorttitle = title, if no sorttitle is found
""" """
item = self.item item = self.item
# XML # XML
try: try:
item = item[self.child].attrib item = item[0].attrib
# JSON # JSON
except KeyError: except (AttributeError, KeyError):
pass pass
try: try:
title = item['title'] title = item['title']
except: except:
@ -1684,7 +1688,7 @@ class API():
Returns the plot or None. Returns the plot or None.
""" """
item = self.item item = self.item
item = item[self.child].attrib item = item[0].attrib
try: try:
plot = item['summary'] plot = item['summary']
except: except:
@ -1696,7 +1700,7 @@ class API():
Returns a shorter tagline or None Returns a shorter tagline or None
""" """
item = self.item item = self.item
item = item[self.child].attrib item = item[0].attrib
try: try:
tagline = item['tagline'] tagline = item['tagline']
except KeyError: except KeyError:
@ -1708,7 +1712,7 @@ class API():
Returns the audience rating or None Returns the audience rating or None
""" """
item = self.item item = self.item
item = item[self.child].attrib item = item[0].attrib
try: try:
rating = item['audienceRating'] rating = item['audienceRating']
except KeyError: except KeyError:
@ -1720,7 +1724,7 @@ class API():
Returns the production(?) year ("year") or None Returns the production(?) year ("year") or None
""" """
item = self.item item = self.item
item = item[self.child].attrib item = item[0].attrib
try: try:
year = item['year'] year = item['year']
except KeyError: except KeyError:
@ -1737,9 +1741,14 @@ class API():
Output: Output:
resume, runtime as floats. 0.0 if not found resume, runtime as floats. 0.0 if not found
""" """
time_factor = 1.0 / 1000.0 # millisecond -> seconds time_factor = PlexToKodiTimefactor()
item = self.item
item = item[self.child].attrib # XML
try:
item = self.item[0].attrib
# JSON
except (AttributeError, KeyError):
pass
try: try:
runtime = float(item['duration']) runtime = float(item['duration'])
@ -1769,7 +1778,7 @@ class API():
""" """
# Convert more complex cases # Convert more complex cases
item = self.item item = self.item
item = item[self.child].attrib item = item[0].attrib
try: try:
mpaa = item['contentRating'] mpaa = item['contentRating']
except KeyError: except KeyError:
@ -1785,7 +1794,7 @@ class API():
""" """
item = self.item item = self.item
country = [] country = []
for child in item[self.child]: for child in item[0]:
if child.tag == 'Country': if child.tag == 'Country':
country.append(child.attrib['tag']) country.append(child.attrib['tag'])
return country return country
@ -1795,7 +1804,7 @@ class API():
Returns the "originallyAvailableAt" or None Returns the "originallyAvailableAt" or None
""" """
item = self.item item = self.item
item = item[self.child].attrib item = item[0].attrib
try: try:
premiere = item['originallyAvailableAt'] premiere = item['originallyAvailableAt']
except: except:
@ -1808,7 +1817,7 @@ class API():
""" """
item = self.item item = self.item
studio = [] studio = []
item = item[self.child].attrib item = item[0].attrib
try: try:
studio.append(self.getStudio(item['studio'])) studio.append(self.getStudio(item['studio']))
except KeyError: except KeyError:
@ -1849,67 +1858,36 @@ class API():
Episode number, Plex: 'index' Episode number, Plex: 'index'
] ]
""" """
item = self.item[self.child].attrib item = self.item[0].attrib
key = item['grandparentRatingKey'] key = item['grandparentRatingKey']
title = item['grandparentTitle'] title = item['grandparentTitle']
season = item['parentIndex'] season = item['parentIndex']
episode = item['index'] episode = item['index']
return key, title, season, episode return str(key), title, str(season), str(episode)
def getFilePath(self):
"""
returns the path to the Plex object, e.g. "/library/metadata/221803"
"""
item = self.item
item = item[self.child].attrib
try:
filepath = item['key']
except KeyError:
filepath = ""
# Plex: do we need this?
else:
if "\\\\" in filepath:
# append smb protocol
filepath = filepath.replace("\\\\", "smb://")
filepath = filepath.replace("\\", "/")
if item.get('VideoType'):
videotype = item['VideoType']
# Specific format modification
if 'Dvd'in videotype:
filepath = "%s/VIDEO_TS/VIDEO_TS.IFO" % filepath
elif 'Bluray' in videotype:
filepath = "%s/BDMV/index.bdmv" % filepath
if "\\" in filepath:
# Local path scenario, with special videotype
filepath = filepath.replace("/", "\\")
return filepath
def addPlexCredentialsToUrl(self, url, arguments={}): def addPlexCredentialsToUrl(self, url, arguments={}):
""" """
Takes an URL and optional arguments (also to be URL-encoded); returns Takes an URL and optional arguments (also to be URL-encoded); returns
an extended URL with e.g. the Plex token included. an extended URL with e.g. the Plex token included.
arguments overrule everything
""" """
token = {'X-Plex-Token': self.token} token = {'X-Plex-Token': self.token}
xargs = PlexAPI().getXArgsDeviceInfo(options=token) xargs = PlexAPI().getXArgsDeviceInfo(options=token)
xargs.update(arguments) xargs.update(arguments)
url = "%s?%s" % (url, urlencode(xargs)) if '?' not in url:
url = "%s?%s" % (url, urlencode(xargs))
else:
url = "%s&%s" % (url, urlencode(xargs))
return url return url
def getBitrate(self): def GetPlayQueueItemID(self):
""" """
Returns the bitrate as an int. The Part bitrate is returned; if not Returns current playQueueItemID for the item.
available in the Plex XML, the Media bitrate is returned
If not found, empty str is returned
""" """
item = self.item return self.item.get('playQueueItemID')
try:
bitrate = item[self.child][0][self.part].attrib['bitrate']
except KeyError:
bitrate = item[self.child][0].attrib['bitrate']
bitrate = int(bitrate)
return bitrate
def getDataFromPartOrMedia(self, key): def getDataFromPartOrMedia(self, key):
""" """
@ -1918,8 +1896,8 @@ class API():
If all fails, None is returned. If all fails, None is returned.
""" """
media = self.item[self.child][0].attrib media = self.item['_children'][0]
part = self.item[self.child][0][self.part].attrib part = media['_children'][self.part]
try: try:
try: try:
value = part[key] value = part[key]
@ -2025,12 +2003,12 @@ class API():
subtitlelanguages = [] subtitlelanguages = []
aspectratio = None aspectratio = None
try: try:
aspectratio = item[self.child][0].attrib['aspectRatio'] aspectratio = item[0][0].attrib['aspectRatio']
except KeyError: except KeyError:
pass pass
# TODO: what if several Media tags exist?!? # TODO: what if several Media tags exist?!?
# Loop over parts # Loop over parts
for child in item[self.child][0]: for child in item[0][0]:
container = child.attrib['container'].lower() container = child.attrib['container'].lower()
# Loop over Streams # Loop over Streams
for grandchild in child: for grandchild in child:
@ -2105,6 +2083,13 @@ class API():
server = self.server server = self.server
item = self.item item = self.item
# XML
try:
item = item[0].attrib
# JSON
except (AttributeError, KeyError):
pass
maxHeight = 10000 maxHeight = 10000
maxWidth = 10000 maxWidth = 10000
customquery = "" customquery = ""
@ -2126,7 +2111,6 @@ class API():
} }
# Process backdrops # Process backdrops
# Get background artwork URL # Get background artwork URL
item = item[self.child].attrib
try: try:
background = item['art'] background = item['art']
background = "%s%s" % (server, background) background = "%s%s" % (server, background)
@ -2259,7 +2243,7 @@ class API():
xargs = PlexAPI().getXArgsDeviceInfo(options=options) xargs = PlexAPI().getXArgsDeviceInfo(options=options)
# For Direct Playing # For Direct Playing
if action == "DirectPlay": if action == "DirectPlay":
path = self.item[self.child][0][self.part].attrib['key'] path = self.item['_children'][0]['_children'][self.partNumber]['key']
transcodePath = self.server + path transcodePath = self.server + path
# Be sure to have exactly ONE '?' in the path (might already have # Be sure to have exactly ONE '?' in the path (might already have
# been returned, e.g. trailers!) # been returned, e.g. trailers!)
@ -2273,15 +2257,7 @@ class API():
# For Direct Streaming or Transcoding # For Direct Streaming or Transcoding
transcodePath = self.server + \ transcodePath = self.server + \
'/video/:/transcode/universal/start.m3u8?' '/video/:/transcode/universal/start.m3u8?'
partCount = 0 path = self.getDataFromPartOrMedia('key')
for parts in self.item[self.child][0]:
partCount = partCount + 1
# Movie consists of several parts; grap one part
if partCount > 1:
path = self.item[self.child][0][self.part].attrib['key']
# Movie consists of only one part
else:
path = self.item[self.child].attrib['key']
args = { args = {
'path': path, 'path': path,
'mediaIndex': 0, # Probably refering to XML reply sheme 'mediaIndex': 0, # Probably refering to XML reply sheme
@ -2337,9 +2313,9 @@ class API():
mapping = {} mapping = {}
item = self.item item = self.item
itemid = self.getKey() itemid = self.getRatingKey()
try: try:
mediastreams = item[self.child][0][0] mediastreams = item[0][0][0]
except (TypeError, KeyError, IndexError): except (TypeError, KeyError, IndexError):
return return
@ -2372,14 +2348,14 @@ class API():
Returns raw API metadata XML dump for a playlist with e.g. trailers. Returns raw API metadata XML dump for a playlist with e.g. trailers.
""" """
item = self.item item = self.item
key = self.getKey() key = self.getRatingKey()
try: try:
uuid = item.attrib['librarySectionUUID'] uuid = item.attrib['librarySectionUUID']
# if not found: probably trying to start a trailer directly # if not found: probably trying to start a trailer directly
# Hence no playlist needed # Hence no playlist needed
except KeyError: except KeyError:
return None return None
mediatype = item[self.child].tag.lower() mediatype = item[0].tag.lower()
trailerNumber = utils.settings('trailerNumber') trailerNumber = utils.settings('trailerNumber')
if not trailerNumber: if not trailerNumber:
trailerNumber = '3' trailerNumber = '3'
@ -2407,4 +2383,4 @@ class API():
""" """
Returns the parts of the specified video child in the XML response Returns the parts of the specified video child in the XML response
""" """
return self.item[self.child][0] return self.item[0][0]

View file

@ -1,9 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from urllib import urlencode from urllib import urlencode
from ast import literal_eval
from urlparse import urlparse, parse_qs
import re
from xbmcaddon import Addon from xbmcaddon import Addon
from downloadutils import DownloadUtils import downloadutils
from utils import logMsg from utils import logMsg
@ -11,6 +14,13 @@ addonName = Addon().getAddonInfo('name')
title = "%s %s" % (addonName, __name__) title = "%s %s" % (addonName, __name__)
def PlexToKodiTimefactor():
"""
Kodi measures time in seconds, but Plex in milliseconds
"""
return 1.0 / 1000.0
def GetItemClassFromType(itemType): def GetItemClassFromType(itemType):
classes = { classes = {
'movie': 'Movies', 'movie': 'Movies',
@ -21,6 +31,42 @@ def GetItemClassFromType(itemType):
return classes[itemType] return classes[itemType]
def GetPlexKeyNumber(plexKey):
"""
Deconstructs e.g. '/library/metadata/xxxx' to the tuple
('library/metadata', 'xxxx')
Returns ('','') if nothing is found
"""
regex = re.compile(r'''/(.+)/(\d+)$''')
try:
result = regex.findall(plexKey)[0]
except IndexError:
result = ('', '')
return result
def ParseContainerKey(containerKey):
"""
Parses e.g. /playQueues/3045?own=1&repeat=0&window=200 to:
'playQueues', '3045', {'window': ['200'], 'own': ['1'], 'repeat': ['0']}
Output hence: library, key, query (query as a special dict)
"""
result = urlparse(containerKey)
library, key = GetPlexKeyNumber(result.path)
query = parse_qs(result.query)
return library, key, query
def LiteralEval(string):
"""
Turns a string e.g. in a dict, safely :-)
"""
return literal_eval(string)
def GetMethodFromPlexType(plexType): def GetMethodFromPlexType(plexType):
methods = { methods = {
'movie': 'add_update', 'movie': 'add_update',
@ -46,18 +92,25 @@ def EmbyItemtypes():
return ['Movie', 'Series', 'Season', 'Episode'] return ['Movie', 'Series', 'Season', 'Episode']
def XbmcPhoto(): def GetPlayQueue(playQueueID):
return "photo" """
def XbmcVideo(): Fetches the PMS playqueue with the playQueueID as a JSON
return "video"
def XbmcAudio(): Returns False if something went wrong
return "audio" """
def PlexPhoto(): url = "{server}/playQueues/%s" % playQueueID
return "photo" headerOptions = {'Accept': 'application/json'}
def PlexVideo(): json = downloadutils.DownloadUtils().downloadUrl(url, headerOptions=headerOptions)
return "video" try:
def PlexAudio(): json = json.json()
return "music" except:
return False
try:
json['_children']
json['playQueueID']
except KeyError:
return False
return json
def GetPlexMetadata(key): def GetPlexMetadata(key):
@ -87,7 +140,7 @@ def GetPlexMetadata(key):
} }
url = url + '?' + urlencode(arguments) url = url + '?' + urlencode(arguments)
headerOptions = {'Accept': 'application/xml'} headerOptions = {'Accept': 'application/xml'}
xml = DownloadUtils().downloadUrl(url, headerOptions=headerOptions) xml = downloadutils.DownloadUtils().downloadUrl(url, headerOptions=headerOptions)
# Did we receive a valid XML? # Did we receive a valid XML?
try: try:
xml.tag xml.tag
@ -108,7 +161,7 @@ def GetAllPlexChildren(key):
""" """
result = [] result = []
url = "{server}/library/metadata/%s/children" % key url = "{server}/library/metadata/%s/children" % key
jsondata = DownloadUtils().downloadUrl(url) jsondata = downloadutils.DownloadUtils().downloadUrl(url)
try: try:
result = jsondata['_children'] result = jsondata['_children']
except KeyError: except KeyError:
@ -125,7 +178,7 @@ def GetPlexSectionResults(viewId, headerOptions={}):
""" """
result = [] result = []
url = "{server}/library/sections/%s/all" % viewId url = "{server}/library/sections/%s/all" % viewId
jsondata = DownloadUtils().downloadUrl(url, headerOptions=headerOptions) jsondata = downloadutils.DownloadUtils().downloadUrl(url, headerOptions=headerOptions)
try: try:
result = jsondata['_children'] result = jsondata['_children']
except TypeError: except TypeError:
@ -146,37 +199,38 @@ def GetPlexSectionResults(viewId, headerOptions={}):
return result return result
def GetPlexUpdatedItems(viewId, unixTime, headerOptions={}): def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None,
""" headerOptions={}):
Returns a list (raw JSON or XML API dump) of all Plex items in the Plex
section with key = viewId AFTER the unixTime
"""
result = []
url = "{server}/library/sections/%s/allLeaves?updatedAt>=%s" \
% (viewId, unixTime)
jsondata = DownloadUtils().downloadUrl(url, headerOptions=headerOptions)
try:
result = jsondata['_children']
except KeyError:
logMsg(title,
"Error retrieving all items for Plex section %s and time %s"
% (viewId, unixTime), -1)
return result
def GetAllPlexLeaves(viewId, headerOptions={}):
""" """
Returns a list (raw JSON or XML API dump) of all Plex subitems for the Returns a list (raw JSON or XML API dump) of all Plex subitems for the
key. key.
(e.g. /library/sections/2/allLeaves pointing to all TV shows) (e.g. /library/sections/2/allLeaves pointing to all TV shows)
Input: Input:
viewId Id of Plex library, e.g. '2' viewId Id of Plex library, e.g. '2'
headerOptions to override the download headers lastViewedAt Unix timestamp; only retrieves PMS items viewed
since that point of time until now.
updatedAt Unix timestamp; only retrieves PMS items updated
by the PMS since that point of time until now.
headerOptions to override any download headers
If lastViewedAt and updatedAt=None, ALL PMS items are returned.
Warning: lastViewedAt and updatedAt are combined with AND by the PMS!
Relevant "master time": PMS server. I guess this COULD lead to problems,
e.g. when server and client are in different time zones.
""" """
result = [] result = []
url = "{server}/library/sections/%s/allLeaves" % viewId args = []
jsondata = DownloadUtils().downloadUrl(url, headerOptions=headerOptions) url = "{server}/library/sections/%s/allLeaves?" % viewId
if lastViewedAt:
args.append('lastViewedAt>=%s' % lastViewedAt)
if updatedAt:
args.append('updatedAt>=%s' % updatedAt)
args = '&'.join(args)
jsondata = downloadutils.DownloadUtils().downloadUrl(
url+args, headerOptions=headerOptions)
try: try:
result = jsondata['_children'] result = jsondata['_children']
except TypeError: except TypeError:
@ -213,7 +267,7 @@ def GetPlexCollections(mediatype):
""" """
collections = [] collections = []
url = "{server}/library/sections" url = "{server}/library/sections"
jsondata = DownloadUtils().downloadUrl(url) jsondata = downloadutils.DownloadUtils().downloadUrl(url)
try: try:
result = jsondata['_children'] result = jsondata['_children']
except KeyError: except KeyError:

View file

@ -31,36 +31,65 @@ import embydb_functions
################################################################################################# #################################################################################################
# For logging only
title = " %s %s" % (clientinfo.ClientInfo().getAddonName(), __name__)
def plexCompanion(fullurl, params=None):
params = PlexFunctions.LiteralEval(params[26:])
utils.logMsg("entrypoint - plexCompanion",
"params is: %s" % params, -1)
# {'protocol': 'http',
# 'containerKey': '/playQueues/3045?own=1&repeat=0&window=200',
# 'offset': '0',
# 'commandID': '20',
# 'token': 'transient-0243a39f-4c7d-495f-a5c8-6991b622b5a6',
# 'key': '/library/metadata/470',
# 'address': '192.168.0.2',
# 'machineIdentifier': '3eb2fc28af89500e000db2e07f8e8234d159f2c4',
# 'type': 'video',
# 'port': '32400'}
if (params.get('machineIdentifier') !=
utils.window('plex_machineIdentifier')):
utils.logMsg(
title,
"Command was not for us, machineIdentifier controller: %s, "
"our machineIdentifier : %s"
% (params.get('machineIdentifier'),
utils.window('plex_machineIdentifier')), -1)
return
utils.window('plex_key', params.get('key'))
library, key, query = PlexFunctions(params.get('containerKey'))
# Construct a container key that works always (get rid of playlist args)
utils.window('plex_containerKey', '/'+library+'/'+key)
# Assume it's video when something goes wrong
playbackType = params.get('type', 'video')
if 'playQueues' in library:
utils.logMsg(title, "Playing a playQueue. Query was: %s" % query, 1)
# Playing a playlist that we need to fetch from PMS
playQueue = PlexFunctions.GetPlayQueue(key)
if not playQueue:
utils.logMsg(
title, "Error getting PMS playlist for key %s" % key, -1)
return
# Set window properties to make them available for other threads
utils.window('plex_playQueueID', playQueue['playQueueID'])
utils.window('plex_playQueueVersion', playQueue['playQueueVersion'])
utils.window('plex_playQueueShuffled', playQueue['playQueueShuffled'])
utils.window(
'plex_playQueueSelectedItemID',
playQueue['playQueueSelectedItemID'])
utils.window(
'plex_playQueueSelectedItemOffset',
playQueue['playQueueSelectedItemOffset'])
pbutils.PlaybackUtils(playQueue['_children']).StartPlay(
resume=playQueue['playQueueSelectedItemOffset'],
resumeItem=playQueue['playQueueSelectedItemID'])
def plexCompanion(fullurl, resume=None):
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
utils.logMsg("entrypoint - plexCompanion",
"Could not parse url: %s" % fullurl, -1)
return False
# Initialize embydb
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby = embydb_functions.Embydb_Functions(embycursor)
# Get dbid using itemid
# Works only for library items, not e.g. for trailers
try:
dbid = emby.getItem_byId(itemid)[0]
except TypeError:
# Trailers and the like
dbid = None
embyconn.close()
# Fix resume timing
if resume:
if resume == '0':
resume = None
else:
resume = round(float(resume) / 1000.0, 6)
# Start playing # Start playing
item = PlexFunctions.GetPlexMetadata(itemid) item = PlexFunctions.GetPlexMetadata(itemid)
pbutils.PlaybackUtils(item).play(itemid, dbid, seektime=resume) pbutils.PlaybackUtils(item).play(itemid, dbid, seektime=resume)

View file

@ -42,6 +42,7 @@ class InitialSetup():
clientId = self.clientInfo.getDeviceId() clientId = self.clientInfo.getDeviceId()
serverid = self.userClient.getServerId() serverid = self.userClient.getServerId()
myplexlogin, plexhome, plexLogin, plexToken = self.plx.GetPlexLoginFromSettings() myplexlogin, plexhome, plexLogin, plexToken = self.plx.GetPlexLoginFromSettings()
dialog = xbmcgui.Dialog()
# Optionally sign into plex.tv. Will not be called on very first run # Optionally sign into plex.tv. Will not be called on very first run
# as plexToken will be '' # as plexToken will be ''
@ -49,7 +50,6 @@ class InitialSetup():
chk = self.plx.CheckConnection('plex.tv', plexToken) chk = self.plx.CheckConnection('plex.tv', plexToken)
# HTTP Error: unauthorized # HTTP Error: unauthorized
if chk == 401: if chk == 401:
dialog = xbmcgui.Dialog()
dialog.ok( dialog.ok(
self.addonName, self.addonName,
'Could not login to plex.tv.', 'Could not login to plex.tv.',
@ -60,7 +60,6 @@ class InitialSetup():
plexLogin = result['username'] plexLogin = result['username']
plexToken = result['token'] plexToken = result['token']
elif chk is False or chk >= 400: elif chk is False or chk >= 400:
dialog = xbmcgui.Dialog()
dialog.ok( dialog.ok(
self.addonName, self.addonName,
'Problems connecting to plex.tv.', 'Problems connecting to plex.tv.',
@ -81,12 +80,8 @@ class InitialSetup():
plexLogin = result['username'] plexLogin = result['username']
plexToken = result['token'] plexToken = result['token']
# Get g_PMS list of servers (saved to plx.g_PMS) # Get g_PMS list of servers (saved to plx.g_PMS)
serverNum = 1 while True:
while serverNum > 0: tokenDict = {'MyPlexToken': plexToken} if plexToken else {}
if plexToken:
tokenDict = {'MyPlexToken': plexToken}
else:
tokenDict = {}
# Populate g_PMS variable with the found Plex servers # Populate g_PMS variable with the found Plex servers
self.plx.discoverPMS(clientId, self.plx.discoverPMS(clientId,
None, None,
@ -100,8 +95,12 @@ class InitialSetup():
# Get a nicer list # Get a nicer list
dialoglist = [] dialoglist = []
# Exit if no servers found # Exit if no servers found
serverNum = len(serverlist) if len(serverlist) == 0:
if serverNum == 0: dialog.ok(
self.addonName,
'Could not find any Plex server in the network.'
'Aborting...'
)
break break
for server in serverlist: for server in serverlist:
if server['local'] == '1': if server['local'] == '1':
@ -109,7 +108,6 @@ class InitialSetup():
dialoglist.append(str(server['name']) + ' (nearby)') dialoglist.append(str(server['name']) + ' (nearby)')
else: else:
dialoglist.append(str(server['name'])) dialoglist.append(str(server['name']))
dialog = xbmcgui.Dialog()
resp = dialog.select( resp = dialog.select(
'Choose your Plex server', 'Choose your Plex server',
dialoglist) dialoglist)
@ -119,11 +117,11 @@ class InitialSetup():
server['port'] server['port']
# Deactive SSL verification if the server is local! # Deactive SSL verification if the server is local!
if server['local'] == '1': if server['local'] == '1':
self.addon.setSetting('sslverify', 'false') utils.settings('sslverify', 'false')
self.logMsg("Setting SSL verify to false, because server is " self.logMsg("Setting SSL verify to false, because server is "
"local", 1) "local", 1)
else: else:
self.addon.setSetting('sslverify', 'true') utils.settings('sslverify', 'true')
self.logMsg("Setting SSL verify to true, because server is " self.logMsg("Setting SSL verify to true, because server is "
"not local", 1) "not local", 1)
chk = self.plx.CheckConnection(url, server['accesstoken']) chk = self.plx.CheckConnection(url, server['accesstoken'])
@ -142,7 +140,6 @@ class InitialSetup():
break break
# Problems connecting # Problems connecting
elif chk >= 400 or chk is False: elif chk >= 400 or chk is False:
dialog = xbmcgui.Dialog()
resp = dialog.yesno(self.addonName, resp = dialog.yesno(self.addonName,
'Problems connecting to server.', 'Problems connecting to server.',
'Pick another server?') 'Pick another server?')
@ -158,20 +155,19 @@ class InitialSetup():
xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId) xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId)
return return
# Write to Kodi settings file # Write to Kodi settings file
self.addon.setSetting('plex_machineIdentifier', activeServer) utils.settings('plex_machineIdentifier', activeServer)
self.addon.setSetting('ipaddress', server['ip']) utils.settings('ipaddress', server['ip'])
self.addon.setSetting('port', server['port']) utils.settings('port', server['port'])
if server['scheme'] == 'https': if server['scheme'] == 'https':
self.addon.setSetting('https', 'true') utils.settings('https', 'true')
else: else:
self.addon.setSetting('https', 'false') utils.settings('https', 'false')
self.logMsg("Wrote to Kodi user settings file:", 0) self.logMsg("Wrote to Kodi user settings file:", 0)
self.logMsg("PMS machineIdentifier: %s, ip: %s, port: %s, https: %s " self.logMsg("PMS machineIdentifier: %s, ip: %s, port: %s, https: %s "
% (activeServer, server['ip'], server['port'], % (activeServer, server['ip'], server['port'],
server['scheme']), 0) server['scheme']), 0)
##### ADDITIONAL PROMPTS ##### ##### ADDITIONAL PROMPTS #####
dialog = xbmcgui.Dialog()
directPaths = dialog.yesno( directPaths = dialog.yesno(
heading="%s: Playback Mode" % self.addonName, heading="%s: Playback Mode" % self.addonName,
line1=( line1=(

View file

@ -263,7 +263,7 @@ class Movies(Items):
API = PlexAPI.API(itemList) API = PlexAPI.API(itemList)
for itemNumber in range(len(itemList)): for itemNumber in range(len(itemList)):
API.setChildNumber(itemNumber) API.setChildNumber(itemNumber)
itemid = API.getKey() itemid = API.getRatingKey()
# Get key and db entry on the Kodi db side # Get key and db entry on the Kodi db side
fileid = self.emby_db.getItem_byId(itemid)[1] fileid = self.emby_db.getItem_byId(itemid)[1]
# Grab the user's viewcount, resume points etc. from PMS' answer # Grab the user's viewcount, resume points etc. from PMS' answer
@ -276,6 +276,7 @@ class Movies(Items):
userdata['LastPlayedDate']) userdata['LastPlayedDate'])
def add_update(self, item, viewtag=None, viewid=None): def add_update(self, item, viewtag=None, viewid=None):
self.logMsg("Entering add_update", 1)
# Process single movie # Process single movie
kodicursor = self.kodicursor kodicursor = self.kodicursor
emby_db = self.emby_db emby_db = self.emby_db
@ -286,7 +287,7 @@ class Movies(Items):
# If the item already exist in the local Kodi DB we'll perform a full item update # If the item already exist in the local Kodi DB we'll perform a full item update
# If the item doesn't exist, we'll add it to the database # If the item doesn't exist, we'll add it to the database
update_item = True update_item = True
itemid = API.getKey() itemid = API.getRatingKey()
# Cannot parse XML, abort # Cannot parse XML, abort
if not itemid: if not itemid:
self.logMsg("Cannot parse XML data for movie", -1) self.logMsg("Cannot parse XML data for movie", -1)
@ -346,22 +347,22 @@ class Movies(Items):
# Find one trailer # Find one trailer
trailer = None trailer = None
extras = API.getExtras() extras = API.getExtras()
for item in extras: for extra in extras:
# Only get 1st trailer element # Only get 1st trailer element
if item['extraType'] == '1': if extra['extraType'] == '1':
trailer = item['key'] trailer = extra['key']
trailer = "plugin://plugin.video.plexkodiconnect/trailer/?id=%s&mode=play" % trailer trailer = "plugin://plugin.video.plexkodiconnect/trailer/?id=%s&mode=play" % trailer
self.logMsg("Trailer for %s: %s" % (itemid, trailer), 2) self.logMsg("Trailer for %s: %s" % (itemid, trailer), 2)
break break
##### GET THE FILE AND PATH ##### ##### GET THE FILE AND PATH #####
playurl = API.getFilePath() playurl = API.getKey()
filename = playurl
if "\\" in playurl: # if "\\" in playurl:
# Local path # # Local path
filename = playurl.rsplit("\\", 1)[1] # filename = playurl.rsplit("\\", 1)[1]
else: # Network share # else: # Network share
filename = playurl.rsplit("/", 1)[1] # filename = playurl.rsplit("/", 1)[1]
if self.directpath: if self.directpath:
# Direct paths is set the Kodi way # Direct paths is set the Kodi way
@ -386,13 +387,13 @@ class Movies(Items):
path = "plugin://plugin.video.plexkodiconnect.movies/" path = "plugin://plugin.video.plexkodiconnect.movies/"
params = { params = {
'filename': filename.encode('utf-8'), #'filename': filename.encode('utf-8'),
'filename': filename,
'id': itemid, 'id': itemid,
'dbid': movieid, 'dbid': movieid,
'mode': "play" 'mode': "play"
} }
filename = "%s?%s" % (path, urllib.urlencode(params)) filename = "%s?%s" % (path, urllib.urlencode(params))
##### UPDATE THE MOVIE ##### ##### UPDATE THE MOVIE #####
if update_item: if update_item:
self.logMsg("UPDATE movie itemid: %s - Title: %s" % (itemid, title), 1) self.logMsg("UPDATE movie itemid: %s - Title: %s" % (itemid, title), 1)
@ -462,24 +463,33 @@ class Movies(Items):
# Process cast # Process cast
people = API.getPeopleList() people = API.getPeopleList()
kodi_db.addPeople(movieid, people, "movie") kodi_db.addPeople(movieid, people, "movie")
self.logMsg('People added', 2)
# Process genres # Process genres
kodi_db.addGenres(movieid, genres, "movie") kodi_db.addGenres(movieid, genres, "movie")
self.logMsg('Genres added', 2)
# Process artwork # Process artwork
allartworks = API.getAllArtwork() allartworks = API.getAllArtwork()
self.logMsg('Artwork processed', 2)
artwork.addArtwork(allartworks, movieid, "movie", kodicursor) artwork.addArtwork(allartworks, movieid, "movie", kodicursor)
self.logMsg('Artwork added', 2)
# Process stream details # Process stream details
streams = API.getMediaStreams() streams = API.getMediaStreams()
self.logMsg('Streames processed', 2)
kodi_db.addStreams(fileid, streams, runtime) kodi_db.addStreams(fileid, streams, runtime)
self.logMsg('Streames added', 2)
# Process studios # Process studios
kodi_db.addStudios(movieid, studios, "movie") kodi_db.addStudios(movieid, studios, "movie")
self.logMsg('Studios added', 2)
# Process tags: view, emby tags # Process tags: view, emby tags
tags = [viewtag] tags = [viewtag]
# tags.extend(item['Tags']) # tags.extend(item['Tags'])
# if userdata['Favorite']: # if userdata['Favorite']:
# tags.append("Favorite movies") # tags.append("Favorite movies")
kodi_db.addTags(movieid, tags, "movie") kodi_db.addTags(movieid, tags, "movie")
self.logMsg('Tags added', 2)
# Process playstates # Process playstates
kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed) kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
self.logMsg('Done processing %s' % itemid, 2)
def remove(self, itemid): def remove(self, itemid):
# Remove movieid, fileid, emby reference # Remove movieid, fileid, emby reference
@ -598,7 +608,7 @@ class MusicVideos(Items):
##### GET THE FILE AND PATH ##### ##### GET THE FILE AND PATH #####
playurl = API.getFilePath() playurl = API.getKey()
if "\\" in playurl: if "\\" in playurl:
# Local path # Local path
@ -883,7 +893,7 @@ class TVShows(Items):
API = PlexAPI.API(itemList) API = PlexAPI.API(itemList)
for itemNumber in range(len(itemList)): for itemNumber in range(len(itemList)):
API.setChildNumber(itemNumber) API.setChildNumber(itemNumber)
itemid = API.getKey() itemid = API.getRatingKey()
# Get key and db entry on the Kodi db side # Get key and db entry on the Kodi db side
fileid = self.emby_db.getItem_byId(itemid)[1] fileid = self.emby_db.getItem_byId(itemid)[1]
# Grab the user's viewcount, resume points etc. from PMS' answer # Grab the user's viewcount, resume points etc. from PMS' answer
@ -909,7 +919,7 @@ class TVShows(Items):
# If the item already exist in the local Kodi DB we'll perform a full item update # If the item already exist in the local Kodi DB we'll perform a full item update
# If the item doesn't exist, we'll add it to the database # If the item doesn't exist, we'll add it to the database
update_item = True update_item = True
itemid = API.getKey() itemid = API.getRatingKey()
if not itemid: if not itemid:
self.logMsg("Cannot parse XML data for TV show", -1) self.logMsg("Cannot parse XML data for TV show", -1)
return return
@ -943,7 +953,7 @@ class TVShows(Items):
studio = None studio = None
##### GET THE FILE AND PATH ##### ##### GET THE FILE AND PATH #####
playurl = API.getFilePath() playurl = API.getKey()
if self.directpath: if self.directpath:
# Direct paths is set the Kodi way # Direct paths is set the Kodi way
@ -1070,7 +1080,7 @@ class TVShows(Items):
def add_updateSeason(self, item, viewid=None, viewtag=None): def add_updateSeason(self, item, viewid=None, viewtag=None):
API = PlexAPI.API(item) API = PlexAPI.API(item)
showid = viewid showid = viewid
itemid = API.getKey() itemid = API.getRatingKey()
kodicursor = self.kodicursor kodicursor = self.kodicursor
emby_db = self.emby_db emby_db = self.emby_db
kodi_db = self.kodi_db kodi_db = self.kodi_db
@ -1116,7 +1126,7 @@ class TVShows(Items):
# If the item already exist in the local Kodi DB we'll perform a full item update # If the item already exist in the local Kodi DB we'll perform a full item update
# If the item doesn't exist, we'll add it to the database # If the item doesn't exist, we'll add it to the database
update_item = True update_item = True
itemid = API.getKey() itemid = API.getRatingKey()
emby_dbitem = emby_db.getItem_byId(itemid) emby_dbitem = emby_db.getItem_byId(itemid)
self.logMsg("Processing episode with Plex Id: %s" % itemid, 2) self.logMsg("Processing episode with Plex Id: %s" % itemid, 2)
try: try:
@ -1150,8 +1160,12 @@ class TVShows(Items):
resume, runtime = API.getRuntime() resume, runtime = API.getRuntime()
premieredate = API.getPremiereDate() premieredate = API.getPremiereDate()
self.logMsg("Retrieved metadata for %s" % itemid, 2)
# episode details # episode details
seriesId, seriesName, season, episode = API.getEpisodeDetails() seriesId, seriesName, season, episode = API.getEpisodeDetails()
self.logMsg("Got episode details: %s %s: s%se%s"
% (seriesId, seriesName, season, episode), 2)
if season is None: if season is None:
if item.get('AbsoluteEpisodeNumber'): if item.get('AbsoluteEpisodeNumber'):
@ -1180,27 +1194,30 @@ class TVShows(Items):
try: try:
showid = show[0] showid = show[0]
except TypeError: except TypeError:
# Show is missing from database # self.logMsg("Show is missing from database, trying to add", 2)
show = self.emby.getItem(seriesId) # show = self.emby.getItem(seriesId)
self.add_update(show) # self.logMsg("Show now: %s. Trying to add new show" % show, 2)
show = emby_db.getItem_byId(seriesId) # self.add_update(show)
try: # show = emby_db.getItem_byId(seriesId)
showid = show[0] # try:
except TypeError: # showid = show[0]
self.logMsg("Skipping: %s. Unable to add series: %s." % (itemid, seriesId), -1) # except TypeError:
return False # self.logMsg("Skipping: %s. Unable to add series: %s." % (itemid, seriesId), -1)
self.logMsg("Parent tvshow now found, skip item", 2)
return False
self.logMsg("showid: %s" % showid, 2)
seasonid = kodi_db.addSeason(showid, season) seasonid = kodi_db.addSeason(showid, season)
self.logMsg("seasonid: %s" % seasonid, 2)
##### GET THE FILE AND PATH ##### ##### GET THE FILE AND PATH #####
playurl = API.getFilePath() playurl = API.getKey()
filename = playurl
if "\\" in playurl: # if "\\" in playurl:
# Local path # # Local path
filename = playurl.rsplit("\\", 1)[1] # filename = playurl.rsplit("\\", 1)[1]
else: # Network share # else: # Network share
filename = playurl.rsplit("/", 1)[1] # filename = playurl.rsplit("/", 1)[1]
if self.directpath: if self.directpath:
# Direct paths is set the Kodi way # Direct paths is set the Kodi way
@ -1225,7 +1242,8 @@ class TVShows(Items):
path = "plugin://plugin.video.plexkodiconnect.tvshows/%s/" % seriesId path = "plugin://plugin.video.plexkodiconnect.tvshows/%s/" % seriesId
params = { params = {
'filename': filename.encode('utf-8'), #'filename': filename.encode('utf-8'),
'filename': filename,
'id': itemid, 'id': itemid,
'dbid': episodeid, 'dbid': episodeid,
'mode': "play" 'mode': "play"
@ -1234,7 +1252,7 @@ class TVShows(Items):
##### UPDATE THE EPISODE ##### ##### UPDATE THE EPISODE #####
if update_item: if update_item:
self.logMsg("UPDATE episode itemid: %s - Title: %s" % (itemid, title), 1) self.logMsg("UPDATE episode itemid: %s" % (itemid), 1)
# Update the movie entry # Update the movie entry
if kodiversion in (16, 17): if kodiversion in (16, 17):
@ -1268,7 +1286,7 @@ class TVShows(Items):
##### OR ADD THE EPISODE ##### ##### OR ADD THE EPISODE #####
else: else:
self.logMsg("ADD episode itemid: %s - Title: %s" % (itemid, title), 1) self.logMsg("ADD episode itemid: %s" % (itemid), 1)
# Add path # Add path
pathid = kodi_db.addPath(path) pathid = kodi_db.addPath(path)
@ -1850,7 +1868,7 @@ class Music(Items):
path = "%s/emby/Audio/%s/" % (self.server, itemid) path = "%s/emby/Audio/%s/" % (self.server, itemid)
filename = "stream.mp3" filename = "stream.mp3"
else: else:
playurl = API.getFilePath() playurl = API.getKey()
if "\\" in playurl: if "\\" in playurl:
# Local path # Local path

View file

@ -27,7 +27,7 @@ import PlexFunctions
################################################################################################## ##################################################################################################
@utils.ThreadMethodsStopsync @utils.ThreadMethodsAdditionalStop('emby_shouldStop')
@utils.ThreadMethods @utils.ThreadMethods
class ThreadedGetMetadata(threading.Thread): class ThreadedGetMetadata(threading.Thread):
""" """
@ -41,10 +41,11 @@ class ThreadedGetMetadata(threading.Thread):
the downloaded metadata XMLs as etree objects the downloaded metadata XMLs as etree objects
lock threading.Lock(), used for counting where we are lock threading.Lock(), used for counting where we are
""" """
def __init__(self, queue, out_queue, lock): def __init__(self, queue, out_queue, lock, errorQueue):
self.queue = queue self.queue = queue
self.out_queue = out_queue self.out_queue = out_queue
self.lock = lock self.lock = lock
self.errorQueue = errorQueue
threading.Thread.__init__(self) threading.Thread.__init__(self)
def run(self): def run(self):
@ -54,35 +55,41 @@ class ThreadedGetMetadata(threading.Thread):
lock = self.lock lock = self.lock
threadStopped = self.threadStopped threadStopped = self.threadStopped
global getMetadataCount global getMetadataCount
while threadStopped() is False: try:
# grabs Plex item from queue while threadStopped() is False:
try: # grabs Plex item from queue
updateItem = queue.get(block=False) try:
# Empty queue updateItem = queue.get(block=False)
except Queue.Empty: # Empty queue
continue except Queue.Empty:
# Download Metadata continue
try: # Download Metadata
plexXML = PlexFunctions.GetPlexMetadata(updateItem['itemId']) plexXML = PlexFunctions.GetPlexMetadata(updateItem['itemId'])
except: try:
raise plexXML.tag
# check whether valid XML except:
if plexXML: # Did not receive a valid XML - skip that one for now
queue.task_done()
continue
# Get rid of first XML level:
updateItem['XML'] = plexXML updateItem['XML'] = plexXML
# place item into out queue # place item into out queue
out_queue.put(updateItem) out_queue.put(updateItem)
del plexXML del plexXML
del updateItem del updateItem
# If we don't have a valid XML, don't put that into the queue # If we don't have a valid XML, don't put that into the queue
# but skip this item for now # but skip this item for now
# Keep track of where we are at # Keep track of where we are at
with lock: with lock:
getMetadataCount += 1 getMetadataCount += 1
# signals to queue job is done # signals to queue job is done
queue.task_done() queue.task_done()
except:
self.errorQueue.put(sys.exc_info())
@utils.ThreadMethodsStopsync @utils.ThreadMethodsAdditionalStop('emby_shouldStop')
@utils.ThreadMethods @utils.ThreadMethods
class ThreadedProcessMetadata(threading.Thread): class ThreadedProcessMetadata(threading.Thread):
""" """
@ -96,10 +103,11 @@ class ThreadedProcessMetadata(threading.Thread):
e.g. 'Movies' => itemtypes.Movies() e.g. 'Movies' => itemtypes.Movies()
lock: threading.Lock(), used for counting where we are lock: threading.Lock(), used for counting where we are
""" """
def __init__(self, queue, itemType, lock): def __init__(self, queue, itemType, lock, errorQueue):
self.queue = queue self.queue = queue
self.lock = lock self.lock = lock
self.itemType = itemType self.itemType = itemType
self.errorQueue = errorQueue
threading.Thread.__init__(self) threading.Thread.__init__(self)
def run(self): def run(self):
@ -111,35 +119,40 @@ class ThreadedProcessMetadata(threading.Thread):
threadStopped = self.threadStopped threadStopped = self.threadStopped
global processMetadataCount global processMetadataCount
global processingViewName global processingViewName
with itemFkt() as item: try:
while threadStopped() is False: with itemFkt() as item:
# grabs item from queue while threadStopped() is False:
try: # grabs item from queue
updateItem = queue.get(block=False) try:
# Empty queue updateItem = queue.get(block=False)
except Queue.Empty: # Empty queue
continue except Queue.Empty:
# Do the work; lock to be sure we've only got 1 Thread continue
plexitem = updateItem['XML'] # Do the work; lock to be sure we've only got 1 Thread
method = updateItem['method'] plexitem = updateItem['XML']
viewName = updateItem['viewName'] method = updateItem['method']
viewId = updateItem['viewId'] viewName = updateItem['viewName']
title = updateItem['title'] viewId = updateItem['viewId']
itemSubFkt = getattr(item, method) title = updateItem['title']
with lock: itemSubFkt = getattr(item, method)
itemSubFkt(plexitem, with lock:
viewtag=viewName, itemSubFkt(plexitem,
viewid=viewId) viewtag=viewName,
# Keep track of where we are at viewid=viewId)
processMetadataCount += 1 # Keep track of where we are at
processingViewName = title processMetadataCount += 1
del plexitem processingViewName = title
del updateItem del plexitem
# signals to queue job is done del updateItem
self.queue.task_done() # signals to queue job is done
self.queue.task_done()
except:
xbmc.log('An error occured')
xbmc.log(sys.exc_info())
self.errorQueue.put(sys.exc_info())
@utils.ThreadMethodsStopsync @utils.ThreadMethodsAdditionalStop('emby_shouldStop')
@utils.ThreadMethods @utils.ThreadMethods
class ThreadedShowSyncInfo(threading.Thread): class ThreadedShowSyncInfo(threading.Thread):
""" """
@ -184,10 +197,15 @@ class ThreadedShowSyncInfo(threading.Thread):
percentage = int(float(totalProgress) / float(total)*100.0) percentage = int(float(totalProgress) / float(total)*100.0)
except ZeroDivisionError: except ZeroDivisionError:
percentage = 0 percentage = 0
dialog.update(percentage, try:
message="Downloaded: %s, Processed: %s: %s" dialog.update(
% (getMetadataProgress, percentage,
processMetadataProgress, viewName)) message="Downloaded: %s, Processed: %s: %s"
% (getMetadataProgress, processMetadataProgress,
viewName))
except:
# Unicode formating of the string?!?
pass
# Sleep for x milliseconds # Sleep for x milliseconds
xbmc.sleep(500) xbmc.sleep(500)
dialog.close() dialog.close()
@ -195,7 +213,7 @@ class ThreadedShowSyncInfo(threading.Thread):
@utils.logging @utils.logging
@utils.ThreadMethodsAdditionalSuspend('suspend_LibraryThread') @utils.ThreadMethodsAdditionalSuspend('suspend_LibraryThread')
@utils.ThreadMethodsStopsync @utils.ThreadMethodsAdditionalStop('emby_shouldStop')
@utils.ThreadMethods @utils.ThreadMethods
class LibrarySync(threading.Thread): class LibrarySync(threading.Thread):
@ -213,6 +231,9 @@ class LibrarySync(threading.Thread):
self.__dict__ = self._shared_state self.__dict__ = self._shared_state
# How long should we look into the past for fast syncing items (in s)
self.syncPast = 60
self.clientInfo = clientinfo.ClientInfo() self.clientInfo = clientinfo.ClientInfo()
self.doUtils = downloadutils.DownloadUtils() self.doUtils = downloadutils.DownloadUtils()
self.user = userclient.UserClient() self.user = userclient.UserClient()
@ -257,13 +278,14 @@ class LibrarySync(threading.Thread):
""" """
self.compare = True self.compare = True
# Get last sync time # Get last sync time
lastSync = utils.window('LastIncrementalSync') lastSync = self.lastSync - self.syncPast
if not lastSync: if not lastSync:
# Original Emby format: # Original Emby format:
# lastSync = "2016-01-01T00:00:00Z" # lastSync = "2016-01-01T00:00:00Z"
# January 1, 2015 at midnight: # January 1, 2015 at midnight:
lastSync = '1420070400' lastSync = 1420070400
self.logMsg("Last sync run: %s" % lastSync, 1) # Set new timestamp NOW because sync might take a while
self.saveLastSync()
# Get all PMS items already saved in Kodi # Get all PMS items already saved in Kodi
embyconn = utils.kodiSQL('emby') embyconn = utils.kodiSQL('emby')
@ -287,7 +309,8 @@ class LibrarySync(threading.Thread):
if self.threadStopped(): if self.threadStopped():
return True return True
# Get items per view # Get items per view
items = PlexFunctions.GetPlexUpdatedItems(view['id'], lastSync) items = PlexFunctions.GetAllPlexLeaves(
view['id'], updatedAt=lastSync)
if not items: if not items:
continue continue
# Get one itemtype, because they're the same in the PMS section # Get one itemtype, because they're the same in the PMS section
@ -311,18 +334,16 @@ class LibrarySync(threading.Thread):
for view in self.views: for view in self.views:
self.PlexUpdateWatched( self.PlexUpdateWatched(
view['id'], view['id'],
PlexFunctions.GetItemClassFromType(view['itemtype'])) PlexFunctions.GetItemClassFromType(view['itemtype']),
lastViewedAt=lastSync)
# Reset and return # Reset and return
self.saveLastSync()
self.allKodiElementsId = {} self.allKodiElementsId = {}
self.allPlexElementsId = {} self.allPlexElementsId = {}
return True return True
def saveLastSync(self): def saveLastSync(self):
# Save last sync time # Save last sync time
lastSync = str(utils.getUnixTimestamp()) self.lastSync = utils.getUnixTimestamp()
self.logMsg("New sync time: %s" % lastSync, 1)
utils.window('LastIncrementalSync', value=lastSync)
def initializeDBs(self): def initializeDBs(self):
""" """
@ -348,7 +369,7 @@ class LibrarySync(threading.Thread):
def fullSync(self, manualrun=False, repair=False): def fullSync(self, manualrun=False, repair=False):
# Only run once when first setting up. Can be run manually. # Only run once when first setting up. Can be run manually.
self.compare = manualrun self.compare = manualrun or repair
music_enabled = utils.settings('enableMusic') == "true" music_enabled = utils.settings('enableMusic') == "true"
# Add sources # Add sources
@ -361,7 +382,8 @@ class LibrarySync(threading.Thread):
else: else:
message = "Initial sync" message = "Initial sync"
utils.window('emby_initialScan', value="true") utils.window('emby_initialScan', value="true")
# Set new timestamp NOW because sync might take a while
self.saveLastSync()
starttotal = datetime.now() starttotal = datetime.now()
# Ensure that DBs exist if called for very first time # Ensure that DBs exist if called for very first time
@ -418,7 +440,6 @@ class LibrarySync(threading.Thread):
# musiccursor.close() # musiccursor.close()
xbmc.executebuiltin('UpdateLibrary(video)') xbmc.executebuiltin('UpdateLibrary(video)')
self.saveLastSync()
elapsedtotal = datetime.now() - starttotal elapsedtotal = datetime.now() - starttotal
utils.window('emby_initialScan', clear=True) utils.window('emby_initialScan', clear=True)
@ -572,7 +593,7 @@ class LibrarySync(threading.Thread):
Output: self.updatelist, self.allPlexElementsId Output: self.updatelist, self.allPlexElementsId
self.updatelist APPENDED(!!) list itemids (Plex Keys as self.updatelist APPENDED(!!) list itemids (Plex Keys as
as received from API.getKey()) as received from API.getRatingKey())
One item in this list is of the form: One item in this list is of the form:
'itemId': xxx, 'itemId': xxx,
'itemType': 'Movies','TVShows', ... 'itemType': 'Movies','TVShows', ...
@ -594,7 +615,7 @@ class LibrarySync(threading.Thread):
return False return False
API = PlexAPI.API(item) API = PlexAPI.API(item)
plex_checksum = API.getChecksum() plex_checksum = API.getChecksum()
itemId = API.getKey() itemId = API.getRatingKey()
title, sorttitle = API.getTitle() title, sorttitle = API.getTitle()
self.allPlexElementsId[itemId] = plex_checksum self.allPlexElementsId[itemId] = plex_checksum
kodi_checksum = self.allKodiElementsId.get(itemId) kodi_checksum = self.allKodiElementsId.get(itemId)
@ -616,7 +637,7 @@ class LibrarySync(threading.Thread):
if self.threadStopped(): if self.threadStopped():
return False return False
API = PlexAPI.API(item) API = PlexAPI.API(item)
itemId = API.getKey() itemId = API.getRatingKey()
title, sorttitle = API.getTitle() title, sorttitle = API.getTitle()
plex_checksum = API.getChecksum() plex_checksum = API.getChecksum()
self.allPlexElementsId[itemId] = plex_checksum self.allPlexElementsId[itemId] = plex_checksum
@ -648,6 +669,7 @@ class LibrarySync(threading.Thread):
self.logMsg("Starting sync threads", 1) self.logMsg("Starting sync threads", 1)
getMetadataQueue = Queue.Queue() getMetadataQueue = Queue.Queue()
processMetadataQueue = Queue.Queue(maxsize=100) processMetadataQueue = Queue.Queue(maxsize=100)
errorQueue = Queue.Queue()
getMetadataLock = threading.Lock() getMetadataLock = threading.Lock()
processMetadataLock = threading.Lock() processMetadataLock = threading.Lock()
# To keep track # To keep track
@ -665,20 +687,12 @@ class LibrarySync(threading.Thread):
for i in range(min(self.syncThreadNumber, itemNumber)): for i in range(min(self.syncThreadNumber, itemNumber)):
thread = ThreadedGetMetadata(getMetadataQueue, thread = ThreadedGetMetadata(getMetadataQueue,
processMetadataQueue, processMetadataQueue,
getMetadataLock) getMetadataLock,
errorQueue)
thread.setDaemon(True) thread.setDaemon(True)
thread.start() thread.start()
threads.append(thread) threads.append(thread)
self.logMsg("Download threads spawned", 1) self.logMsg("Download threads spawned", 1)
# Spawn one more thread to process Metadata, once downloaded
thread = ThreadedProcessMetadata(processMetadataQueue,
itemType,
processMetadataLock)
thread.setDaemon(True)
thread.start()
threads.append(thread)
self.logMsg("Processing thread spawned", 1)
# Start one thread to show sync progress # Start one thread to show sync progress
dialog = xbmcgui.DialogProgressBG() dialog = xbmcgui.DialogProgressBG()
thread = ThreadedShowSyncInfo(dialog, thread = ThreadedShowSyncInfo(dialog,
@ -689,9 +703,32 @@ class LibrarySync(threading.Thread):
thread.start() thread.start()
threads.append(thread) threads.append(thread)
self.logMsg("Kodi Infobox thread spawned", 1) self.logMsg("Kodi Infobox thread spawned", 1)
# Spawn one more thread to process Metadata, once downloaded
thread = ThreadedProcessMetadata(processMetadataQueue,
itemType,
processMetadataLock,
errorQueue)
thread.setDaemon(True)
thread.start()
threads.append(thread)
self.logMsg("Processing thread spawned", 1)
# Wait until finished # Wait until finished
getMetadataQueue.join() while True:
processMetadataQueue.join() try:
exc = errorQueue.get(block=False)
except Queue.Empty:
pass
else:
exc_type, exc_obj, exc_trace = exc
# deal with the exception
self.logMsg("Error occured in thread", -1)
self.logMsg(str(exc_type) + str(exc_obj), -1)
self.logMsg(exc_trace, -1)
if getMetadataQueue.empty() and processMetadataQueue.empty():
break
xbmc.sleep(500)
# Kill threads # Kill threads
self.logMsg("Waiting to kill threads", 1) self.logMsg("Waiting to kill threads", 1)
for thread in threads: for thread in threads:
@ -770,24 +807,24 @@ class LibrarySync(threading.Thread):
self.logMsg("%s sync is finished." % itemType, 1) self.logMsg("%s sync is finished." % itemType, 1)
return True return True
def PlexUpdateWatched(self, viewId, itemType): def PlexUpdateWatched(self, viewId, itemType,
lastViewedAt=None, updatedAt=None):
""" """
Updates ALL plex elements' view status ('watched' or 'unwatched') and Updates plex elements' view status ('watched' or 'unwatched') and
also updates resume times. also updates resume times.
This is done by downloading one XML for ALL elements with viewId This is done by downloading one XML for ALL elements with viewId
""" """
starttotal = datetime.now()
# Download XML, not JSON, because PMS JSON seems to be damaged # Download XML, not JSON, because PMS JSON seems to be damaged
headerOptions = {'Accept': 'application/xml'} headerOptions = {'Accept': 'application/xml'}
plexItems = PlexFunctions.GetAllPlexLeaves( plexItems = PlexFunctions.GetAllPlexLeaves(
viewId, headerOptions=headerOptions) viewId,
itemMth = getattr(itemtypes, itemType) lastViewedAt=lastViewedAt,
with itemMth() as method: updatedAt=updatedAt,
method.updateUserdata(plexItems) headerOptions=headerOptions)
if plexItems:
elapsedtotal = datetime.now() - starttotal itemMth = getattr(itemtypes, itemType)
self.logMsg("Syncing userdata for itemtype %s and viewid %s took " with itemMth() as method:
"%s seconds" % (itemType, viewId, elapsedtotal), 1) method.updateUserdata(plexItems)
def musicvideos(self, embycursor, kodicursor, pdialog): def musicvideos(self, embycursor, kodicursor, pdialog):
# Get musicvideos from emby # Get musicvideos from emby

View file

@ -15,6 +15,7 @@ import playutils as putils
import playlist import playlist
import read_embyserver as embyserver import read_embyserver as embyserver
import utils import utils
import embydb_functions
import PlexAPI import PlexAPI
@ -23,12 +24,10 @@ import PlexAPI
@utils.logging @utils.logging
class PlaybackUtils(): class PlaybackUtils():
def __init__(self, item): def __init__(self, item):
self.item = item self.item = item
self.API = PlexAPI.API(self.item)
self.doUtils = downloadutils.DownloadUtils() self.doUtils = downloadutils.DownloadUtils()
@ -40,21 +39,132 @@ class PlaybackUtils():
self.emby = embyserver.Read_EmbyServer() self.emby = embyserver.Read_EmbyServer()
self.pl = playlist.Playlist() self.pl = playlist.Playlist()
def play(self, itemid, dbid=None, seektime=None): def StartPlay(self, resume=None, resumeItem=None):
self.logMsg("StartPlay called with resume=%s, resumeItem=%s"
% (resume, resumeItem), 1)
# Setup Kodi playlist (e.g. make new one or append or even update)
# Why should we have different behaviour if user is on home screen?!?
# self.homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
self.playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
# Clear playlist since we're always using PMS playQueues
self.playlist.clear()
self.logMsg("Play called with itemid: %s, dbid: %s, seektime: %s." self.startPos = max(self.playlist.getposition(), 0) # Can return -1
% (itemid, dbid, seektime), 1) self.sizePlaylist = self.playlist.size()
self.currentPosition = self.startPos
self.logMsg("Playlist start position: %s" % self.startPos, 1)
self.logMsg("Playlist position we're starting with: %s"
% self.currentPosition, 1)
self.logMsg("Playlist size: %s" % self.sizePlaylist, 1)
doUtils = self.doUtils self.plexResumeItemId = resumeItem
item = self.item # Where should we ultimately start playback?
API = self.API self.resumePost = self.startPos
if resume:
if resume == '0':
resume = None
else:
resume = int(resume)
# Run through the passed PMS playlist and construct playlist
for mediaItem in self.item:
self.AddMediaItemToPlaylist(mediaItem)
# Kick off playback
Player = xbmc.Player()
Player.play(self.playlist, startpos=self.resumePost)
if resume:
try:
Player.seekTime(resume)
except:
self.logMsg("Could not use resume: %s. Start from beginning."
% resume, 0)
def AddMediaItemToPlaylist(self, item):
"""
Feed with ONE media item from PMS json response
(on level with e.g. key=/library/metadata/220493 present)
An item may consist of several parts (e.g. movie in 2 pieces/files)
"""
API = PlexAPI.API(item)
playutils = putils.PlayUtils(item)
# e.g. itemid='219155'
itemid = API.getRatingKey()
# Get DB id from Kodi by using plex id, if that works
embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby = embydb_functions.Embydb_Functions(embycursor)
try:
dbid = emby.getItem_byId(itemid)[0]
except TypeError:
# Trailers and the like that are not in the kodi DB
dbid = None
embyconn.close()
# Get playurls per part and process them
for playurl in playutils.getPlayUrl():
# One new listitem per part
listitem = xbmcgui.ListItem()
# For items that are not (yet) synced to Kodi lib, e.g. trailers
if not dbid:
self.logMsg("Add item to playlist without Kodi DB id", 1)
# Add Plex credentials to url because Kodi will have no headers
playurl = API.addPlexCredentialsToUrl(playurl)
listitem.setPath(playurl)
self.setProperties(playurl, listitem)
# Set artwork already done in setProperties
self.playlist.add(
playurl, listitem, index=self.currentPosition)
self.currentPosition += 1
else:
self.logMsg("Add item to playlist with existing Kodi DB id", 1)
self.pl.addtoPlaylist(dbid, API.getType())
self.currentPosition += 1
# For transcoding only, ask for audio/subs pref
if utils.window('emby_%s.playmethod' % playurl) == "Transcode":
playurl = playutils.audioSubsPref(playurl, listitem)
utils.window('emby_%s.playmethod' % playurl, value="Transcode")
playQueueItemID = API.GetPlayQueueItemID()
# Is this the position where we should start playback?
if playQueueItemID == self.plexResumeItemId:
self.logMsg(
"Figure we should start playback at position %s "
"with playQueueItemID %s"
% (self.currentPosition, playQueueItemID), 2)
self.resumePost = self.currentPosition
# We need to keep track of playQueueItemIDs for Plex Companion
utils.window(
'plex_%s.playQueueItemID' % playurl, API.GetPlayQueueItemID())
utils.window(
'plex_%s.playlistPosition' % playurl, self.currentPosition)
# Log the playlist that we end up with
self.pl.verifyPlaylist()
def play(self, item):
API = PlexAPI.API(item)
listitem = xbmcgui.ListItem() listitem = xbmcgui.ListItem()
playutils = putils.PlayUtils(item) playutils = putils.PlayUtils(item)
# Set child number to the very last one, because that's what we want # e.g. itemid='219155'
# to play ultimately itemid = API.getRatingKey()
API.setChildNumber(-1) # Get DB id from Kodi by using plex id, if that works
playurl = playutils.getPlayUrl(child=-1) embyconn = utils.kodiSQL('emby')
embycursor = embyconn.cursor()
emby = embydb_functions.Embydb_Functions(embycursor)
try:
dbid = emby.getItem_byId(itemid)[0]
except TypeError:
# Trailers and the like that are not in the kodi DB
dbid = None
embyconn.close()
playurl = playutils.getPlayUrl()
if not playurl: if not playurl:
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
@ -82,10 +192,9 @@ class PlaybackUtils():
self.logMsg("Playlist size: %s" % sizePlaylist, 1) self.logMsg("Playlist size: %s" % sizePlaylist, 1)
############### RESUME POINT ################ ############### RESUME POINT ################
if seektime is None: userdata = API.getUserData()
userdata = API.getUserData() seektime = userdata['Resume']
seektime = userdata['Resume']
# We need to ensure we add the intro and additional parts only once. # We need to ensure we add the intro and additional parts only once.
# Otherwise we get a loop. # Otherwise we get a loop.
@ -132,7 +241,7 @@ class PlaybackUtils():
self.pl.insertintoPlaylist(currentPosition, url=introPlayurl) self.pl.insertintoPlaylist(currentPosition, url=introPlayurl)
introsPlaylist = True introsPlaylist = True
currentPosition += 1 currentPosition += 1
self.logMsg("Key: %s" % API.getKey(), 1) self.logMsg("Key: %s" % API.getRatingKey(), 1)
self.logMsg("Successfally added trailer number %s" % i, 1) self.logMsg("Successfally added trailer number %s" % i, 1)
# Set "working point" to the movie (last one in playlist) # Set "working point" to the movie (last one in playlist)
API.setChildNumber(-1) API.setChildNumber(-1)
@ -220,7 +329,7 @@ class PlaybackUtils():
# Set all properties necessary for plugin path playback # Set all properties necessary for plugin path playback
item = self.item item = self.item
# itemid = item['Id'] # itemid = item['Id']
itemid = self.API.getKey() itemid = self.API.getRatingKey()
# itemtype = item['Type'] # itemtype = item['Type']
itemtype = self.API.getType() itemtype = self.API.getType()
resume, runtime = self.API.getRuntime() resume, runtime = self.API.getRuntime()
@ -230,8 +339,9 @@ class PlaybackUtils():
utils.window('%s.type' % embyitem, value=itemtype) utils.window('%s.type' % embyitem, value=itemtype)
utils.window('%s.itemid' % embyitem, value=itemid) utils.window('%s.itemid' % embyitem, value=itemid)
if itemtype == "Episode": if itemtype == "episode":
utils.window('%s.refreshid' % embyitem, value=item.get('SeriesId')) utils.window('%s.refreshid' % embyitem,
value=item.get('parentRatingKey'))
else: else:
utils.window('%s.refreshid' % embyitem, value=itemid) utils.window('%s.refreshid' % embyitem, value=itemid)
@ -282,10 +392,6 @@ class PlaybackUtils():
return externalsubs return externalsubs
def setArtwork(self, listItem): def setArtwork(self, listItem):
# Set up item and item info
item = self.item
artwork = self.artwork
# allartwork = artwork.getAllArtwork(item, parentInfo=True) # allartwork = artwork.getAllArtwork(item, parentInfo=True)
allartwork = self.API.getAllArtwork(parentInfo=True) allartwork = self.API.getAllArtwork(parentInfo=True)
# Set artwork for listitem # Set artwork for listitem

View file

@ -21,6 +21,7 @@ class PlayUtils():
def __init__(self, item): def __init__(self, item):
self.item = item self.item = item
self.API = PlexAPI.API(item)
self.clientInfo = clientinfo.ClientInfo() self.clientInfo = clientinfo.ClientInfo()
@ -28,54 +29,44 @@ class PlayUtils():
self.server = utils.window('emby_server%s' % self.userid) self.server = utils.window('emby_server%s' % self.userid)
self.machineIdentifier = utils.window('plex_machineIdentifier') self.machineIdentifier = utils.window('plex_machineIdentifier')
self.API = PlexAPI.API(item) def getPlayUrl(self):
"""
Returns a list of playurls, one per part in item
"""
playurls = []
# TODO: multiple media parts for e.g. trailers: replace [0] here
partCount = len(self.item['_children'][0]['_children'])
for partNumber in range(partCount):
playurl = None
self.API.setPartNumber(partNumber)
def getPlayUrl(self, child=0, partIndex=None): if self.isDirectPlay():
item = self.item self.logMsg("File is direct playing.", 1)
# NO, I am not very fond of this construct! playurl = self.API.getTranscodeVideoPath('DirectPlay')
self.API.setChildNumber(child) playurl = playurl.encode('utf-8')
if partIndex is not None: # Set playmethod property
self.API.setPartNumber(partIndex) utils.window('emby_%s.playmethod' % playurl, "DirectPlay")
playurl = None
if item.get('Type') in ["Recording","TvChannel"] and item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http":
#Is this the right way to play a Live TV or recordings ?
self.logMsg("File protocol is http (livetv).", 1)
playurl = "%s/emby/Videos/%s/live.m3u8?static=true" % (self.server, item['Id'])
utils.window('emby_%s.playmethod' % playurl, value="DirectPlay")
# if item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http": elif self.isDirectStream():
# # Only play as http self.logMsg("File is direct streaming.", 1)
# self.logMsg("File protocol is http.", 1) playurl = self.API.getTranscodeVideoPath('DirectStream')
# playurl = self.httpPlay() # Set playmethod property
# utils.window('emby_%s.playmethod' % playurl, value="DirectStream") utils.window('emby_%s.playmethod' % playurl, "DirectStream")
if self.isDirectPlay(): elif self.isTranscoding():
self.logMsg("File is direct playing.", 1) self.logMsg("File is transcoding.", 1)
playurl = self.API.getTranscodeVideoPath('DirectPlay') quality = {
playurl = playurl.encode('utf-8') 'maxVideoBitrate': self.getBitrate()
# Set playmethod property }
utils.window('emby_%s.playmethod' % playurl, value="DirectPlay") playurl = self.API.getTranscodeVideoPath('Transcode',
quality=quality)
# Set playmethod property
utils.window('emby_%s.playmethod' % playurl, value="Transcode")
elif self.isDirectStream(): playurls.append(playurl)
self.logMsg("File is direct streaming.", 1)
playurl = self.API.getTranscodeVideoPath('DirectStream')
# Set playmethod property
utils.window('emby_%s.playmethod' % playurl, value="DirectStream")
elif self.isTranscoding(): self.logMsg("The playurls are: %s" % playurls, 1)
self.logMsg("File is transcoding.", 1) return playurls
quality = {
'maxVideoBitrate': self.getBitrate()
}
playurl = self.API.getTranscodeVideoPath(
'Transcode',
quality=quality
)
# Set playmethod property
utils.window('emby_%s.playmethod' % playurl, value="Transcode")
self.logMsg("The playurl is: %s" % playurl, 1)
return playurl
def httpPlay(self): def httpPlay(self):
# Audio, Video, Photo # Audio, Video, Photo
@ -155,25 +146,24 @@ class PlayUtils():
videoCodec = self.API.getVideoCodec() videoCodec = self.API.getVideoCodec()
codec = videoCodec['videocodec'] codec = videoCodec['videocodec']
resolution = videoCodec['resolution'] resolution = videoCodec['resolution']
if ((utils.settings('transcodeH265') == "true") and # 720p
("hevc" in codec) and if ((utils.settings('transcode720H265') == "true") and
(resolution == "1080")): ("h265" in codec) and
# Avoid HEVC(H265) 1080p (resolution in "720 1080")):
self.logMsg("Option to transcode 1080P/HEVC enabled.", 0) self.logMsg("Option to transcode 720P/h265 enabled.", 0)
return False return False
else: # 1080p
return True if ((utils.settings('transcodeH265') == "true") and
("h265" in codec) and
(resolution == "1080")):
self.logMsg("Option to transcode 1080P/h265 enabled.", 0)
return False
return True
def isDirectStream(self): def isDirectStream(self):
if not self.h265enabled(): if not self.h265enabled():
return False return False
elif (utils.settings('transcode720H265') == "true" and
item['MediaSources'][0]['Name'].startswith(("720P/HEVC","720P/H265"))):
# Avoid H265 720p
self.logMsg("Option to transcode 720P/H265 enabled.", 1)
return False
# Requirement: BitRate, supported encoding # Requirement: BitRate, supported encoding
# canDirectStream = item['MediaSources'][0]['SupportsDirectStream'] # canDirectStream = item['MediaSources'][0]['SupportsDirectStream']
# Plex: always able?!? # Plex: always able?!?
@ -192,7 +182,7 @@ class PlayUtils():
server = self.server server = self.server
itemid = self.API.getKey() itemid = self.API.getRatingKey()
type = self.API.getType() type = self.API.getType()
# if 'Path' in item and item['Path'].endswith('.strm'): # if 'Path' in item and item['Path'].endswith('.strm'):
@ -211,7 +201,7 @@ class PlayUtils():
settings = self.getBitrate() settings = self.getBitrate()
sourceBitrate = self.API.getBitrate() sourceBitrate = int(self.API.getDataFromPartOrMedia())
self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s" % (settings, sourceBitrate), 1) self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s" % (settings, sourceBitrate), 1)
if settings < sourceBitrate: if settings < sourceBitrate:
return False return False

View file

@ -71,11 +71,9 @@ def jsonrpc(action, arguments = {}):
"id" : 1 , "id" : 1 ,
"method" : "JSONRPC.Ping" }) "method" : "JSONRPC.Ping" })
elif action.lower() == "playmedia": elif action.lower() == "playmedia":
fullurl=arguments[0]
resume=arguments[1]
xbmc.Player().play("plugin://plugin.video.plexkodiconnect/" xbmc.Player().play("plugin://plugin.video.plexkodiconnect/"
"?mode=companion&resume=%s&id=%s" "?mode=companion&arguments=%s"
% (resume, fullurl)) % arguments)
return True return True
elif arguments: elif arguments:
request=json.dumps({ "id" : 1, request=json.dumps({ "id" : 1,

View file

@ -111,16 +111,33 @@ class MyHandler(BaseHTTPRequestHandler):
printDebug("adjusting the volume to %s%%" % volume) printDebug("adjusting the volume to %s%%" % volume)
jsonrpc("Application.SetVolume", {"volume": volume}) jsonrpc("Application.SetVolume", {"volume": volume})
elif "/playMedia" in request_path: elif "/playMedia" in request_path:
playQueueVersion = int(params.get('playQueueVersion', 1))
if playQueueVersion < subMgr.playQueueVersion:
# playQueue was updated; ignore this command for now
return
if playQueueVersion > subMgr.playQueueVersion:
# TODO: we should probably update something else now :-)
subMgr.playQueueVersion = playQueueVersion
s.response(getOKMsg(), getPlexHeaders()) s.response(getOKMsg(), getPlexHeaders())
resume = params.get('viewOffset', params.get('offset', "0")) offset = params.get('viewOffset', params.get('offset', "0"))
protocol = params.get('protocol', "http") protocol = params.get('protocol', "http")
address = params.get('address', s.client_address[0]) address = params.get('address', s.client_address[0])
server = getServerByHost(address) server = getServerByHost(address)
port = params.get('port', server.get('port', '32400')) port = params.get('port', server.get('port', '32400'))
fullurl = protocol+"://"+address+":"+port+params['key'] try:
printDebug("playMedia command -> fullurl: %s" % fullurl) containerKey = urlparse(params.get('containerKey')).path
jsonrpc("playmedia", [fullurl, resume]) except:
containerKey = ''
regex = re.compile(r'''/playQueues/(\d+)$''')
try:
playQueueID = regex.findall(containerKey)[0]
except IndexError:
playQueueID = ''
jsonrpc("playmedia", params)
subMgr.lastkey = params['key'] subMgr.lastkey = params['key']
subMgr.containerKey = containerKey
subMgr.playQueueID = playQueueID
subMgr.server = server.get('server', 'localhost') subMgr.server = server.get('server', 'localhost')
subMgr.port = port subMgr.port = port
subMgr.protocol = protocol subMgr.protocol = protocol

View file

@ -62,7 +62,7 @@ class plexgdm:
print "PlexGDM: %s" % message print "PlexGDM: %s" % message
def clientDetails(self, c_id, c_name, c_post, c_product, c_version): 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_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: timeline,playback,navigation,mirror,playqueues\r\nDevice-Class: HTPC" % ( c_id, c_name, c_post, c_product, c_version )
self.client_id = c_id self.client_id = c_id
def getClientDetails(self): def getClientDetails(self):

View file

@ -12,6 +12,9 @@ class SubscriptionManager:
self.subscribers = {} self.subscribers = {}
self.info = {} self.info = {}
self.lastkey = "" self.lastkey = ""
self.containerKey = ""
self.playQueueID = ''
self.playQueueVersion = 1
self.lastratingkey = "" self.lastratingkey = ""
self.volume = 0 self.volume = 0
self.guid = "" self.guid = ""
@ -75,12 +78,15 @@ class SubscriptionManager:
if keyid: if keyid:
self.lastkey = "/library/metadata/%s"%keyid self.lastkey = "/library/metadata/%s"%keyid
self.lastratingkey = keyid self.lastratingkey = keyid
ret += ' containerKey="%s"' % (self.lastkey) ret += ' containerKey="%s"' % (self.containerKey)
ret += ' key="%s"' % (self.lastkey) ret += ' key="%s"' % (self.lastkey)
ret += ' ratingKey="%s"' % (self.lastratingkey) ret += ' ratingKey="%s"' % (self.lastratingkey)
if pbmc_server: if pbmc_server:
(self.server, self.port) = pbmc_server.split(':') (self.server, self.port) = pbmc_server.split(':')
serv = getServerByHost(self.server) serv = getServerByHost(self.server)
if self.playQueueID:
ret += ' playQueueID="%s"' % self.playQueueID
ret += ' playQueueVersion="%s"' % self.playQueueVersion
ret += ' duration="%s"' % info['duration'] ret += ' duration="%s"' % info['duration']
ret += ' seekRange="0-%s"' % info['duration'] ret += ' seekRange="0-%s"' % info['duration']
ret += ' controllable="%s"' % self.controllable() ret += ' controllable="%s"' % self.controllable()
@ -119,12 +125,15 @@ class SubscriptionManager:
for p in players.values(): for p in players.values():
info = self.playerprops[p.get('playerid')] info = self.playerprops[p.get('playerid')]
params = {} params = {}
params['containerKey'] = (self.lastkey or "/library/metadata/900000") params['containerKey'] = (self.containerKey or "/library/metadata/900000")
if self.playQueueID:
params['playQueueID'] = self.playQueueID
params['key'] = (self.lastkey or "/library/metadata/900000") params['key'] = (self.lastkey or "/library/metadata/900000")
params['ratingKey'] = (self.lastratingkey or "900000") params['ratingKey'] = (self.lastratingkey or "900000")
params['state'] = info['state'] params['state'] = info['state']
params['time'] = info['time'] params['time'] = info['time']
params['duration'] = info['duration'] params['duration'] = info['duration']
params['playQueueVersion'] = self.playQueueVersion
serv = getServerByHost(self.server) serv = getServerByHost(self.server)
url = serv.get('protocol', 'http') + '://' \ url = serv.get('protocol', 'http') + '://' \
+ serv.get('server', 'localhost') + ':' \ + serv.get('server', 'localhost') + ':' \
@ -134,11 +143,9 @@ class SubscriptionManager:
printDebug("params: %s" % params) printDebug("params: %s" % params)
printDebug("players: %s" % players) printDebug("players: %s" % players)
printDebug("sent server notification with state = %s" % params['state']) printDebug("sent server notification with state = %s" % params['state'])
WINDOW = xbmcgui.Window(10000)
WINDOW.setProperty('plexbmc.nowplaying.sent', '1')
def controllable(self): def controllable(self):
return "playPause,play,stop,skipPrevious,skipNext,volume,stepBack,stepForward,seekTo" return "volume,shuffle,repeat,audioStream,videoStream,subtitleStream,skipPrevious,skipNext,seekTo,stepBack,stepForward,stop,playPause"
def addSubscriber(self, protocol, host, port, uuid, commandID): def addSubscriber(self, protocol, host, port, uuid, commandID):
sub = Subscriber(protocol, host, port, uuid, commandID) sub = Subscriber(protocol, host, port, uuid, commandID)
@ -211,7 +218,13 @@ class Subscriber:
+ "/:/timeline" + "/:/timeline"
# Override some headers # Override some headers
headerOptions = { headerOptions = {
'Accept': '*/*' 'Content-Range': 'bytes 0-/-1',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17',
'Accept': '*/*',
'X-Plex-Username': 'croneter',
'Connection': 'keep-alive',
'X-Plex-Client-Capabilities': 'protocols=shoutcast,http-video;videoDecoders=h264{profile:high&resolution:1080&level:51};audioDecoders=mp3,aac,dts{bitrate:800000&channels:8},ac3{bitrate:800000&channels:8}',
'X-Plex-Client-Profile-Extra': 'add-transcode-target-audio-codec(type=videoProfile&context=streaming&protocol=*&audioCodec=dca,ac3)'
} }
response = self.download.downloadUrl( response = self.download.downloadUrl(
url, url,
@ -220,6 +233,6 @@ class Subscriber:
headerOptions=headerOptions) headerOptions=headerOptions)
# if not requests.post(self.host, self.port, "/:/timeline", msg, getPlexHeaders(), self.protocol): # if not requests.post(self.host, self.port, "/:/timeline", msg, getPlexHeaders(), self.protocol):
# subMgr.removeSubscriber(self.uuid) # subMgr.removeSubscriber(self.uuid)
if response in [False, 401]: if response in [False, None, 401]:
subMgr.removeSubscriber(self.uuid) subMgr.removeSubscriber(self.uuid)
subMgr = SubscriptionManager() subMgr = SubscriptionManager()

View file

@ -13,7 +13,6 @@ import utils
import downloadutils import downloadutils
import PlexAPI import PlexAPI
import librarysync
################################################################################################## ##################################################################################################

View file

@ -9,7 +9,7 @@ import sqlite3
import time import time
import unicodedata import unicodedata
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
from functools import wraps, update_wrapper from functools import wraps
from datetime import datetime, timedelta from datetime import datetime, timedelta
from calendar import timegm from calendar import timegm
@ -24,19 +24,35 @@ import xbmcvfs
addonName = xbmcaddon.Addon().getAddonInfo('name') addonName = xbmcaddon.Addon().getAddonInfo('name')
def ThreadMethodsStopsync(cls): def LogTime(func):
""" """
Decorator to replace stopThread method to include the Kodi window property Decorator for functions and methods to log the time it took to run the code
'emby_shouldStop' """
@wraps(func)
def wrapper(*args, **kwargs):
starttotal = datetime.now()
result = func(*args, **kwargs)
elapsedtotal = datetime.now() - starttotal
logMsg('%s %s' % (addonName, func.__name__),
'It took %s to run the function.' % (elapsedtotal), 1)
return result
return wrapper
Use with any library sync threads. @ThreadMethods still required FIRST
def ThreadMethodsAdditionalStop(windowAttribute):
""" """
def threadStopped(self): Decorator to replace stopThread method to include the Kodi windowAttribute
return (self._threadStopped or
self._abortMonitor.abortRequested() or Use with any sync threads. @ThreadMethods still required FIRST
window('emby_shouldStop') == "true") """
cls.threadStopped = threadStopped def wrapper(cls):
return cls def threadStopped(self):
return (self._threadStopped or
self._abortMonitor.abortRequested() or
window(windowAttribute) == "true")
cls.threadStopped = threadStopped
return cls
return wrapper
def ThreadMethodsAdditionalSuspend(windowAttribute): def ThreadMethodsAdditionalSuspend(windowAttribute):
@ -48,8 +64,8 @@ def ThreadMethodsAdditionalSuspend(windowAttribute):
""" """
def wrapper(cls): def wrapper(cls):
def threadSuspended(self): def threadSuspended(self):
return (self._threadSuspended or True if return (self._threadSuspended or
window(windowAttribute) == 'true' else False) window(windowAttribute) == 'true')
cls.threadSuspended = threadSuspended cls.threadSuspended = threadSuspended
return cls return cls
return wrapper return wrapper
@ -140,7 +156,6 @@ def getUnixTimestamp(secondsIntoTheFuture=None):
def logMsg(title, msg, level=1): def logMsg(title, msg, level=1):
# Get the logLevel set in UserClient # Get the logLevel set in UserClient
try: try:
logLevel = int(window('emby_logLevel')) logLevel = int(window('emby_logLevel'))
@ -155,13 +170,19 @@ def logMsg(title, msg, level=1):
xbmc.log("%s -> %s : %s" % ( xbmc.log("%s -> %s : %s" % (
title, func.co_name, msg)) title, func.co_name, msg))
except UnicodeEncodeError: except UnicodeEncodeError:
xbmc.log("%s -> %s : %s" % ( try:
title, func.co_name, msg.encode('utf-8'))) xbmc.log("%s -> %s : %s" % (
title, func.co_name, msg.encode('utf-8')))
except:
xbmc.log("%s -> %s : %s" % (title, func.co_name, 'COULDNT LOG'))
else: else:
try: try:
xbmc.log("%s -> %s" % (title, msg)) xbmc.log("%s -> %s" % (title, msg))
except UnicodeEncodeError: except UnicodeEncodeError:
xbmc.log("%s -> %s" % (title, msg.encode('utf-8'))) try:
xbmc.log("%s -> %s" % (title, msg.encode('utf-8')))
except:
xbmc.log("%s -> %s " % (title, 'COULDNT LOG'))
def window(property, value=None, clear=False, windowid=10000): def window(property, value=None, clear=False, windowid=10000):

View file

@ -28,6 +28,7 @@
<setting id="myplexlogin" label="Log into plex.tv?" type="bool" default="true" /> <setting id="myplexlogin" label="Log into plex.tv?" type="bool" default="true" />
<setting id="plexLogin" label="plex.tv username" type="text" default="" visible="eq(-1,true)" /> <setting id="plexLogin" label="plex.tv username" type="text" default="" visible="eq(-1,true)" />
<setting id="plexhome" label="Plex home in use" type="bool" default="true" visible="false" /> <setting id="plexhome" label="Plex home in use" type="bool" default="true" visible="false" />
<setting id="plexToken" label="plexToken" type="text" default="" visible="false" />
</category> </category>
<category label="Sync Options"> <category label="Sync Options">