Overhaul PlexAPI. Only using XMLs now, no JSONs

This commit is contained in:
tomkat83 2016-02-01 10:33:33 +01:00
parent dc44f1a879
commit fa0003a5eb
6 changed files with 175 additions and 330 deletions

View file

@ -769,13 +769,12 @@ class PlexAPI():
XML = self.getXMLFromPMS(PMS['baseURL'],PMS['path'],PMS['options'],PMS['token']) XML = self.getXMLFromPMS(PMS['baseURL'],PMS['path'],PMS['options'],PMS['token'])
queue.put( (PMS['data'], XML) ) queue.put( (PMS['data'], XML) )
def getXArgsDeviceInfo(self, options={}, JSON=False): def getXArgsDeviceInfo(self, options={}):
""" """
Returns a dictionary that can be used as headers for GET and POST Returns a dictionary that can be used as headers for GET and POST
requests. An authentication option is NOT yet added. requests. An authentication option is NOT yet added.
Inputs: Inputs:
JSON=True will enforce a JSON answer
options: dictionary of options that will override the options: dictionary of options that will override the
standard header options otherwise set. standard header options otherwise set.
Output: Output:
@ -800,8 +799,6 @@ class PlexAPI():
if self.token: if self.token:
xargs['X-Plex-Token'] = self.token xargs['X-Plex-Token'] = self.token
if JSON:
xargs['Accept'] = 'application/json'
if options: if options:
xargs.update(options) xargs.update(options)
return xargs return xargs
@ -1381,8 +1378,6 @@ class API():
self.server = utils.window('emby_server%s' % self.userId) self.server = utils.window('emby_server%s' % self.userId)
self.token = utils.window('emby_accessToken%s' % self.userId) self.token = utils.window('emby_accessToken%s' % self.userId)
self.jumpback = int(utils.settings('resumeJumpBack'))
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
@ -1396,10 +1391,10 @@ class API():
""" """
return self.part return self.part
def convert_date(self, stamp): def DateToKodi(self, stamp):
""" """
convert_date(stamp) converts a Unix time stamp (seconds passed since converts a Unix time stamp (seconds passed sinceJanuary 1 1970) to a
January 1 1970) to a propper, human-readable time stamp propper, human-readable time stamp used by Kodi
""" """
# DATEFORMAT = xbmc.getRegion('dateshort') # DATEFORMAT = xbmc.getRegion('dateshort')
# TIMEFORMAT = xbmc.getRegion('meridiem') # TIMEFORMAT = xbmc.getRegion('meridiem')
@ -1415,85 +1410,53 @@ class API():
# else: # else:
# localtime = time.strftime('%H:%M', date_time) # localtime = time.strftime('%H:%M', date_time)
# return localtime + ' ' + localdate # return localtime + ' ' + localdate
DATEFORMAT = xbmc.getRegion('dateshort') try:
TIMEFORMAT = xbmc.getRegion('meridiem') DATEFORMAT = xbmc.getRegion('dateshort')
date_time = time.localtime(float(stamp)) TIMEFORMAT = xbmc.getRegion('meridiem')
localdate = time.strftime('%Y-%m-%dT%H:%M:%SZ', date_time) date_time = time.localtime(float(stamp))
localdate = time.strftime('%Y-%m-%dT%H:%M:%SZ', date_time)
except:
localdate = None
return localdate return localdate
def getType(self): def getType(self):
""" """
Returns the type of media, e.g. 'movie' or 'clip' for trailers Returns the type of media, e.g. 'movie' or 'clip' for trailers
""" """
# XML return self.item.attrib.get('type', None)
try:
item = self.item.attrib
# JSON
except AttributeError:
item = self.item
return item.get('type', '')
def getChecksum(self): def getChecksum(self):
""" """
Can be used on both XML and JSON
Returns a string, not int Returns a string, not int
""" """
item = self.item
# XML
try:
item = item[0].attrib
# JSON
except (AttributeError, KeyError):
pass
# Include a letter to prohibit saving as an int! # Include a letter to prohibit saving as an int!
checksum = "K%s%s" % (self.getRatingKey(), checksum = "K%s%s" % (self.getRatingKey(),
item.get('updatedAt', '')) self.item.attrib.get('updatedAt', ''))
return checksum return checksum
def getRatingKey(self): def getRatingKey(self):
""" """
Can be used on both XML and JSON
Returns the Plex key such as '246922' as a string Returns the Plex key such as '246922' as a string
""" """
# XML return self.item.attrib.get('ratingKey', '')
try:
result = self.item.attrib
# JSON
except AttributeError:
item = self.item
return item['ratingKey']
def getKey(self): def getKey(self):
""" """
Can be used on both XML and JSON
Returns the Plex key such as '/library/metadata/246922' Returns the Plex key such as '/library/metadata/246922'
""" """
item = self.item return self.item.attrib.get('key', None)
# 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[0].attrib return self.item.attrib.get('index', None)
index = item['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
""" """
item = self.item[0].attrib return self.DateToKodi(self.item.attrib.get('addedAt', None))
dateadded = item['addedAt']
dateadded = self.convert_date(dateadded)
return dateadded
def getUserData(self): def getUserData(self):
""" """
@ -1508,7 +1471,7 @@ class API():
'Rating': rating 'Rating': rating
} }
""" """
item = self.item item = self.item.attrib
# Default # Default
favorite = False favorite = False
playcount = None playcount = None
@ -1517,13 +1480,6 @@ class API():
resume = 0 resume = 0
rating = 0 rating = 0
# 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:
@ -1533,8 +1489,7 @@ class API():
played = True played = True
try: try:
lastPlayedDate = int(item['lastViewedAt']) lastPlayedDate = self.DateToKodi(int(item['lastViewedAt']))
lastPlayedDate = self.convert_date(lastPlayedDate)
except KeyError: except KeyError:
lastPlayedDate = None lastPlayedDate = None
@ -1559,12 +1514,11 @@ class API():
'Producer': list 'Producer': list
} }
""" """
item = self.item
director = [] director = []
writer = [] writer = []
cast = [] cast = []
producer = [] producer = []
for child in item[0]: for child in self.item:
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':
@ -1591,7 +1545,6 @@ class API():
('Role': xxx for cast/actors only, None if not found) ('Role': xxx for cast/actors only, None if not found)
} }
""" """
item = self.item
people = [] people = []
# Key of library: Plex-identifier. Value represents the Kodi/emby side # Key of library: Plex-identifier. Value represents the Kodi/emby side
people_of_interest = { people_of_interest = {
@ -1600,7 +1553,7 @@ class API():
'Role': 'Actor', 'Role': 'Actor',
'Producer': 'Producer' 'Producer': 'Producer'
} }
for child in item[0]: for child in self.item:
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']
@ -1630,9 +1583,8 @@ class API():
""" """
Returns a list of genres found. (Not a string) Returns a list of genres found. (Not a string)
""" """
item = self.item
genre = [] genre = []
for child in item[0]: for child in self.item:
if child.tag == 'Genre': if child.tag == 'Genre':
genre.append(child.attrib['tag']) genre.append(child.attrib['tag'])
return genre return genre
@ -1643,8 +1595,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.attrib
item = item[0].attrib
try: try:
item = item['guid'] item = item['guid']
except KeyError: except KeyError:
@ -1660,99 +1611,50 @@ class API():
def getTitle(self): def getTitle(self):
""" """
Returns an item's name/title or "Missing Title Name" for both XML and Returns an item's name/title or "Missing Title Name".
JSON PMS replies
Output: Output:
title, sorttitle title, sorttitle
sorttitle = title, if no sorttitle is found sorttitle = title, if no sorttitle is found
""" """
item = self.item title = self.item.attrib.get('title', 'Missing Title Name')
sorttitle = self.item.attrib.get('titleSort', title)
# XML
try:
item = item[0].attrib
# JSON
except (AttributeError, KeyError):
pass
try:
title = item['title']
except:
title = 'Missing Title Name'
try:
sorttitle = item['titleSort']
except KeyError:
sorttitle = title
return title, sorttitle return title, sorttitle
def getPlot(self): def getPlot(self):
""" """
Returns the plot or None. Returns the plot or None.
""" """
item = self.item return self.item.attrib.get('summary', None)
item = item[0].attrib
try:
plot = item['summary']
except:
plot = None
return plot
def getTagline(self): def getTagline(self):
""" """
Returns a shorter tagline or None Returns a shorter tagline or None
""" """
item = self.item return self.item.attrib.get('tagline', None)
item = item[0].attrib
try:
tagline = item['tagline']
except KeyError:
tagline = None
return tagline
def getAudienceRating(self): def getAudienceRating(self):
""" """
Returns the audience rating or None Returns the audience rating or None
""" """
item = self.item return self.item.attrib.get('audienceRating', None)
item = item[0].attrib
try:
rating = item['audienceRating']
except KeyError:
rating = None
return rating
def getYear(self): def getYear(self):
""" """
Returns the production(?) year ("year") or None Returns the production(?) year ("year") or None
""" """
item = self.item return self.item.attrib.get('year', None)
item = item[0].attrib
try:
year = item['year']
except KeyError:
year = None
return year
def getRuntime(self): def getRuntime(self):
""" """
Resume point of time and runtime/totaltime in seconds, rounded to 6th Resume point of time and runtime/totaltime in rounded to seconds.
decimal.
Time from Plex server is measured in milliseconds. Time from Plex server is measured in milliseconds.
Kodi: seconds Kodi: seconds
Output: Output:
resume, runtime as floats. 0.0 if not found resume, runtime as ints. 0 if not found
""" """
time_factor = PlexToKodiTimefactor() item = self.item.attrib
# XML
try:
item = self.item[0].attrib
# JSON
except (AttributeError, KeyError):
pass
try: try:
runtime = float(item['duration']) runtime = float(item['duration'])
@ -1763,30 +1665,16 @@ class API():
except KeyError: except KeyError:
resume = 0.0 resume = 0.0
# Adjust the resume point by x seconds as chosen by the user in the runtime = int(runtime * PlexToKodiTimefactor())
# settings resume = int(resume * PlexToKodiTimefactor())
if resume:
# To avoid negative bookmark
if resume > self.jumpback:
resume = resume - self.jumpback
runtime = runtime * time_factor
resume = resume * time_factor
resume = round(resume, 6)
runtime = round(runtime, 6)
return resume, runtime return resume, runtime
def getMpaa(self): def getMpaa(self):
""" """
Get the content rating or None Get the content rating or None
""" """
mpaa = self.item.attrib.get('contentRating', None)
# Convert more complex cases # Convert more complex cases
item = self.item
item = item[0].attrib
try:
mpaa = item['contentRating']
except KeyError:
mpaa = None
if mpaa in ("NR", "UR"): if mpaa in ("NR", "UR"):
# Kodi seems to not like NR, but will accept Rated Not Rated # Kodi seems to not like NR, but will accept Rated Not Rated
mpaa = "Rated Not Rated" mpaa = "Rated Not Rated"
@ -1796,9 +1684,8 @@ class API():
""" """
Returns a list of all countries found in item. Returns a list of all countries found in item.
""" """
item = self.item
country = [] country = []
for child in item[0]: for child in self.item:
if child.tag == 'Country': if child.tag == 'Country':
country.append(child.attrib['tag']) country.append(child.attrib['tag'])
return country return country
@ -1807,23 +1694,15 @@ class API():
""" """
Returns the "originallyAvailableAt" or None Returns the "originallyAvailableAt" or None
""" """
item = self.item return self.item.attrib.get('originallyAvailableAt', None)
item = item[0].attrib
try:
premiere = item['originallyAvailableAt']
except:
premiere = None
return premiere
def getStudios(self): def getStudios(self):
""" """
Returns a list with a single entry for the studio, or an empty list Returns a list with a single entry for the studio, or an empty list
""" """
item = self.item
studio = [] studio = []
item = item[0].attrib
try: try:
studio.append(self.getStudio(item['studio'])) studio.append(self.getStudio(self.item.attrib['studio']))
except KeyError: except KeyError:
pass pass
return studio return studio
@ -1862,7 +1741,7 @@ class API():
Episode number, Plex: 'index' Episode number, Plex: 'index'
] ]
""" """
item = self.item[0].attrib item = self.item.attrib
key = item['grandparentRatingKey'] key = item['grandparentRatingKey']
title = item['grandparentTitle'] title = item['grandparentTitle']
season = item['parentIndex'] season = item['parentIndex']
@ -1891,13 +1770,7 @@ class API():
If not found, empty str is returned If not found, empty str is returned
""" """
# XML: return self.item.atrrib.get('playQueueItemID', '')
try:
item = self.item.attrib
# JSON
except AttributeError:
item = self.item
return item.get('playQueueItemID', '')
def getDataFromPartOrMedia(self, key): def getDataFromPartOrMedia(self, key):
""" """
@ -1906,14 +1779,8 @@ class API():
If all fails, None is returned. If all fails, None is returned.
""" """
# JSON media = self.item.attrib
try: part = self.item[self.part].attrib
media = self.item['_children'][0]
part = media['_children'][self.part]
# XML
except TypeError:
media = self.item[0].attrib
part = self.item[0][self.part].attrib
try: try:
try: try:
@ -1979,26 +1846,25 @@ class API():
'originallyAvailableAt': 'originallyAvailableAt':
'year': 'year':
""" """
extras = self.item[0].find('Extras')
elements = [] elements = []
if not extras: for extra in self.item.find('Extras'):
return elements
for extra in extras:
# Trailer: # Trailer:
key = extra.attrib['key'] key = extra.attrib.get('key', None)
title = extra.attrib['title'] title = extra.attrib.get('title', None)
thumb = extra.attrib['thumb'] thumb = extra.attrib.get('thumb', None)
duration = extra.attrib['duration'] duration = float(extra.attrib.get('duration', 0.0))
year = extra.attrib['year'] year = extra.attrib.get('year', None)
extraType = extra.attrib['extraType'] extraType = extra.attrib.get('extraType', None)
originallyAvailableAt = extra.attrib['originallyAvailableAt'] originallyAvailableAt = extra.attrib.get(
elements.append({'key': key, 'originallyAvailableAt', None)
'title': title, elements.append(
'thumb': thumb, {'key': key,
'duration': duration, 'title': title,
'extraType': extraType, 'thumb': thumb,
'originallyAvailableAt': originallyAvailableAt, 'duration': int(duration * PlexToKodiTimefactor()),
'year': year}) 'extraType': extraType,
'originallyAvailableAt': originallyAvailableAt,
'year': year})
return elements return elements
def getMediaStreams(self): def getMediaStreams(self):
@ -2014,24 +1880,20 @@ class API():
'subtitle': list of subtitle languages (or "Unknown") 'subtitle': list of subtitle languages (or "Unknown")
} }
""" """
item = self.item
videotracks = [] videotracks = []
audiotracks = [] audiotracks = []
subtitlelanguages = [] subtitlelanguages = []
aspectratio = None # Sometimes, aspectratio is on the "toplevel"
try: aspectratio = self.item[0].attrib.get('aspectRatio', None)
aspectratio = item[0][0].attrib['aspectRatio']
except KeyError:
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[0][0]: for child in self.item[0]:
container = child.attrib['container'].lower() container = child.attrib.get('container', None)
# Loop over Streams # Loop over Streams
for grandchild in child: for grandchild in child:
mediaStream = grandchild.attrib mediaStream = grandchild.attrib
type = int(mediaStream['streamType']) mediaType = int(mediaStream.get('streamType', 999))
if type == 1: # Video streams if mediaType == 1: # Video streams
videotrack = {} videotrack = {}
videotrack['codec'] = mediaStream['codec'].lower() videotrack['codec'] = mediaStream['codec'].lower()
if "msmpeg4" in videotrack['codec']: if "msmpeg4" in videotrack['codec']:
@ -2043,21 +1905,17 @@ class API():
elif "h264" in videotrack['codec']: elif "h264" in videotrack['codec']:
if container in ("mp4", "mov", "m4v"): if container in ("mp4", "mov", "m4v"):
videotrack['codec'] = "avc1" videotrack['codec'] = "avc1"
videotrack['height'] = mediaStream.get('height') videotrack['height'] = mediaStream.get('height', None)
videotrack['width'] = mediaStream.get('width') videotrack['width'] = mediaStream.get('width', None)
# TODO: 3d Movies?!? # TODO: 3d Movies?!?
# videotrack['Video3DFormat'] = item.get('Video3DFormat') # videotrack['Video3DFormat'] = item.get('Video3DFormat')
try: aspectratio = mediaStream.get('aspectRatio', aspectratio)
aspectratio = mediaStream['aspectRatio']
except KeyError:
if not aspectratio:
aspectratio = round(float(videotrack['width'] / videotrack['height']), 6)
videotrack['aspect'] = aspectratio videotrack['aspect'] = aspectratio
# TODO: Video 3d format # TODO: Video 3d format
videotrack['video3DFormat'] = None videotrack['video3DFormat'] = None
videotracks.append(videotrack) videotracks.append(videotrack)
elif type == 2: # Audio streams elif mediaType == 2: # Audio streams
audiotrack = {} audiotrack = {}
audiotrack['codec'] = mediaStream['codec'].lower() audiotrack['codec'] = mediaStream['codec'].lower()
profile = mediaStream['codecID'].lower() profile = mediaStream['codecID'].lower()
@ -2070,17 +1928,16 @@ class API():
audiotrack['language'] = 'unknown' audiotrack['language'] = 'unknown'
audiotracks.append(audiotrack) audiotracks.append(audiotrack)
elif type == 3: # Subtitle streams elif mediaType == 3: # Subtitle streams
try: try:
subtitlelanguages.append(mediaStream['language']) subtitlelanguages.append(mediaStream['language'])
except: except:
subtitlelanguages.append("Unknown") subtitlelanguages.append("Unknown")
media = { return {
'video': videotracks, 'video': videotracks,
'audio': audiotracks, 'audio': audiotracks,
'subtitle': subtitlelanguages 'subtitle': subtitlelanguages
} }
return media
def getAllArtwork(self, parentInfo=False): def getAllArtwork(self, parentInfo=False):
""" """
@ -2097,15 +1954,7 @@ class API():
'Backdrop': [] Plex key: "art". Only 1 pix 'Backdrop': [] Plex key: "art". Only 1 pix
} }
""" """
server = self.server item = self.item.attrib
item = self.item
# XML
try:
item = item[0].attrib
# JSON
except (AttributeError, KeyError):
pass
maxHeight = 10000 maxHeight = 10000
maxWidth = 10000 maxWidth = 10000
@ -2130,7 +1979,7 @@ class API():
# Get background artwork URL # Get background artwork URL
try: try:
background = item['art'] background = item['art']
background = "%s%s" % (server, background) background = "%s%s" % (self.server, background)
background = self.addPlexCredentialsToUrl(background) background = self.addPlexCredentialsToUrl(background)
except KeyError: except KeyError:
background = "" background = ""
@ -2138,7 +1987,7 @@ class API():
# Get primary "thumb" pictures: # Get primary "thumb" pictures:
try: try:
primary = item['thumb'] primary = item['thumb']
primary = "%s%s" % (server, primary) primary = "%s%s" % (self.server, primary)
primary = self.addPlexCredentialsToUrl(primary) primary = self.addPlexCredentialsToUrl(primary)
except KeyError: except KeyError:
primary = "" primary = ""
@ -2160,7 +2009,7 @@ class API():
artwork = ( artwork = (
"%s/emby/Items/%s/Images/Backdrop/%s?" "%s/emby/Items/%s/Images/Backdrop/%s?"
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
% (server, parentId, backdropIndex, % (self.server, parentId, backdropIndex,
maxWidth, maxHeight, parentbackdroptag, customquery)) maxWidth, maxHeight, parentbackdroptag, customquery))
allartworks['Backdrop'].append(artwork) allartworks['Backdrop'].append(artwork)
backdropIndex += 1 backdropIndex += 1
@ -2178,7 +2027,7 @@ class API():
artwork = ( artwork = (
"%s/emby/Items/%s/Images/%s/0?" "%s/emby/Items/%s/Images/%s/0?"
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
% (server, parentId, parentart, % (self.server, parentId, parentart,
maxWidth, maxHeight, parentTag, customquery)) maxWidth, maxHeight, parentTag, customquery))
allartworks[parentart] = artwork allartworks[parentart] = artwork
@ -2192,11 +2041,12 @@ class API():
artwork = ( artwork = (
"%s/emby/Items/%s/Images/Primary/0?" "%s/emby/Items/%s/Images/Primary/0?"
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
% (server, parentId, maxWidth, maxHeight, parentTag, customquery)) % (self.server, parentId, maxWidth, maxHeight, parentTag, customquery))
allartworks['Primary'] = artwork allartworks['Primary'] = artwork
return allartworks return allartworks
def getTranscodeVideoPath(self, action, quality={}, subtitle={}, audioboost=None, options={}): def getTranscodeVideoPath(self, action, quality={}, subtitle={},
audioboost=None, options={}):
""" """
Transcode Video support; returns the URL to get a media started Transcode Video support; returns the URL to get a media started
@ -2329,12 +2179,12 @@ class API():
mapping = {} mapping = {}
item = self.item item = self.item
itemid = self.getRatingKey()
try: try:
mediastreams = item[0][0][0] mediastreams = item[0][self.part]
except (TypeError, KeyError, IndexError): except (TypeError, KeyError, IndexError):
return return
itemid = self.getRatingKey()
kodiindex = 0 kodiindex = 0
for stream in mediastreams: for stream in mediastreams:
# index = stream['Index'] # index = stream['Index']
@ -2394,9 +2244,3 @@ class API():
if not xml: if not xml:
self.logMsg("Error retrieving metadata for %s" % url, 1) self.logMsg("Error retrieving metadata for %s" % url, 1)
return xml return xml
def GetParts(self):
"""
Returns the parts of the specified video child in the XML response
"""
return self.item[0][0]

View file

@ -115,16 +115,15 @@ def GetPlayQueue(playQueueID):
return xml return xml
def GetPlexMetadata(key, JSON=True): def GetPlexMetadata(key):
""" """
Returns raw API metadata for key as an etree XML. Returns raw API metadata for key as an etree XML.
Can be called with either Plex key '/library/metadata/xxxx'metadata Can be called with either Plex key '/library/metadata/xxxx'metadata
OR with the digits 'xxxx' only. OR with the digits 'xxxx' only.
Returns an empty string '' if something went wrong Returns None if something went wrong
""" """
xml = ''
key = str(key) key = str(key)
if '/library/metadata/' in key: if '/library/metadata/' in key:
url = "{server}" + key url = "{server}" + key
@ -133,82 +132,72 @@ def GetPlexMetadata(key, JSON=True):
arguments = { arguments = {
'checkFiles': 1, # No idea 'checkFiles': 1, # No idea
'includeExtras': 1, # Trailers and Extras => Extras 'includeExtras': 1, # Trailers and Extras => Extras
'includeRelated': 1, # Similar movies => Video -> Related # 'includeRelated': 1, # Similar movies => Video -> Related
'includeRelatedCount': 5, # 'includeRelatedCount': 5,
'includeOnDeck': 1, # 'includeOnDeck': 1,
'includeChapters': 1, 'includeChapters': 1,
'includePopularLeaves': 1, 'includePopularLeaves': 1,
'includeConcerts': 1 'includeConcerts': 1
} }
url = url + '?' + urlencode(arguments) url = url + '?' + urlencode(arguments)
if not JSON: xml = downloadutils.DownloadUtils().downloadUrl(url)
headerOptions = {'Accept': 'application/xml'}
else:
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.attrib
# Nope we did not receive a valid XML # Nope we did not receive a valid XML
except AttributeError: except AttributeError:
logMsg(title, "Error retrieving metadata for %s" % url, -1) logMsg(title, "Error retrieving metadata for %s" % url, -1)
xml = '' xml = None
return xml return xml
def GetAllPlexChildren(key): def GetAllPlexChildren(key):
""" """
Returns a list (raw JSON API dump) of all Plex children for the key. Returns a list (raw xml API dump) of all Plex children for the key.
(e.g. /library/metadata/194853/children pointing to a season) (e.g. /library/metadata/194853/children pointing to a season)
Input: Input:
key Key to a Plex item, e.g. 12345 key Key to a Plex item, e.g. 12345
""" """
result = [] xml = downloadutils.DownloadUtils().downloadUrl(
url = "{server}/library/metadata/%s/children" % key "{server}/library/metadata/%s/children" % key)
jsondata = downloadutils.DownloadUtils().downloadUrl(url)
try: try:
result = jsondata['_children'] xml.attrib
except KeyError: except AttributeError:
logMsg( logMsg(
title, "Error retrieving all children for Plex item %s" % key, -1) title, "Error retrieving all children for Plex item %s" % key, -1)
pass xml = None
return result return xml
def GetPlexSectionResults(viewId, headerOptions={}): def GetPlexSectionResults(viewId, headerOptions={}):
""" """
Returns a list (raw JSON or XML API dump) of all Plex items in the Plex Returns a list (XML API dump) of all Plex items in the Plex
section with key = viewId. section with key = viewId.
Returns None if something went wrong
""" """
result = [] result = []
url = "{server}/library/sections/%s/all" % viewId url = "{server}/library/sections/%s/all" % viewId
jsondata = downloadutils.DownloadUtils().downloadUrl(url, headerOptions=headerOptions) result = downloadutils.DownloadUtils().downloadUrl(
url, headerOptions=headerOptions)
try: try:
result = jsondata['_children'] result.tag
except TypeError: # Nope, not an XML, abort
# Maybe we received an XML, check for that with tag attribute except AttributeError:
try:
jsondata.tag
result = jsondata
# Nope, not an XML, abort
except AttributeError:
logMsg(title,
"Error retrieving all items for Plex section %s"
% viewId, -1)
return result
except KeyError:
logMsg(title, logMsg(title,
"Error retrieving all items for Plex section %s" "Error retrieving all items for Plex section %s"
% viewId, -1) % viewId, -1)
result = None
return result return result
def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None, def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None,
headerOptions={}): headerOptions={}):
""" """
Returns a list (raw JSON or XML API dump) of all Plex subitems for the Returns a list (raw 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:
@ -226,7 +215,6 @@ def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None,
Relevant "master time": PMS server. I guess this COULD lead to problems, Relevant "master time": PMS server. I guess this COULD lead to problems,
e.g. when server and client are in different time zones. e.g. when server and client are in different time zones.
""" """
result = []
args = [] args = []
url = "{server}/library/sections/%s/allLeaves?" % viewId url = "{server}/library/sections/%s/allLeaves?" % viewId
if lastViewedAt: if lastViewedAt:
@ -234,24 +222,18 @@ def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None,
if updatedAt: if updatedAt:
args.append('updatedAt>=%s' % updatedAt) args.append('updatedAt>=%s' % updatedAt)
args = '&'.join(args) args = '&'.join(args)
jsondata = downloadutils.DownloadUtils().downloadUrl( xml = downloadutils.DownloadUtils().downloadUrl(
url+args, headerOptions=headerOptions) url+args, headerOptions=headerOptions)
try: try:
result = jsondata['_children'] xml.attrib
except TypeError: # Nope, not an XML, abort
# Maybe we received an XML, check for that with tag attribute except AttributeError:
try: logMsg(title,
jsondata.tag "Error retrieving all leaves for Plex section %s"
result = jsondata % viewId, -1)
# Nope, not an XML, abort xml = None
except AttributeError: return xml
logMsg(title,
"Error retrieving all leaves for Plex section %s"
% viewId, -1)
return result
except KeyError:
logMsg("Error retrieving all leaves for Plex viewId %s" % viewId, -1)
return result
def GetPlexCollections(mediatype): def GetPlexCollections(mediatype):

View file

@ -180,9 +180,9 @@ class DownloadUtils():
plx = PlexAPI.PlexAPI() plx = PlexAPI.PlexAPI()
if authenticate: if authenticate:
options['X-Plex-Token'] = self.token options['X-Plex-Token'] = self.token
header = plx.getXArgsDeviceInfo(options=options, JSON=True) header = plx.getXArgsDeviceInfo(options=options)
else: else:
header = plx.getXArgsDeviceInfo(options=options, JSON=True) header = plx.getXArgsDeviceInfo(options=options)
return header return header
def downloadUrl(self, url, postBody=None, type="GET", parameters=None, authenticate=True, headerOptions={}): def downloadUrl(self, url, postBody=None, type="GET", parameters=None, authenticate=True, headerOptions={}):
@ -309,18 +309,19 @@ class DownloadUtils():
elif r.status_code == requests.codes.ok: elif r.status_code == requests.codes.ok:
try: try:
# UNICODE - JSON object # Allow for xml responses
r = r.json() r = etree.fromstring(r.content)
self.logMsg("====== 200 Success ======", 2) self.logMsg("====== 200 Success ======", 2)
self.logMsg("Response: %s" % r, 2) self.logMsg("Received an XML response for: %s" % url, 2)
return r return r
except: except:
# Allow for xml responses
try: try:
r = etree.fromstring(r.content) # UNICODE - JSON object
r = r.json()
self.logMsg("====== 200 Success ======", 2) self.logMsg("====== 200 Success ======", 2)
self.logMsg("Received an XML response for: %s" % url, 2) self.logMsg("Response: %s" % r, 2)
return r return r
except: except:
try: try:

View file

@ -105,7 +105,7 @@ def PassPlaylist(xml, resume=0):
def doPlayback(itemid, dbid): def doPlayback(itemid, dbid):
utils.logMsg(title, "doPlayback called with %s %s" utils.logMsg(title, "doPlayback called with %s %s"
% (itemid, dbid), 1) % (itemid, dbid), 1)
item = PlexFunctions.GetPlexMetadata(itemid, JSON=True) item = PlexFunctions.GetPlexMetadata(itemid)
playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
# If current playlist is NOT empty, we only need to update the item url # If current playlist is NOT empty, we only need to update the item url
if playlist.size() != 0: if playlist.size() != 0:

View file

@ -317,12 +317,15 @@ class Movies(Items):
userdata = API.getUserData() userdata = API.getUserData()
playcount = userdata['PlayCount'] playcount = userdata['PlayCount']
dateplayed = userdata['LastPlayedDate'] dateplayed = userdata['LastPlayedDate']
resume = userdata['Resume']
runtime = userdata['Runtime']
# item details # item details
people = API.getPeople() people = API.getPeople()
writer = API.joinList(people['Writer']) writer = API.joinList(people['Writer'])
director = API.joinList(people['Director']) director = API.joinList(people['Director'])
genres = API.getGenres() genres = API.getGenres()
genre = API.joinList(genres)
title, sorttitle = API.getTitle() title, sorttitle = API.getTitle()
plot = API.getPlot() plot = API.getPlot()
shortplot = None shortplot = None
@ -331,10 +334,8 @@ class Movies(Items):
rating = API.getAudienceRating() rating = API.getAudienceRating()
year = API.getYear() year = API.getYear()
imdb = API.getProvider('Imdb') imdb = API.getProvider()
resume, runtime = API.getRuntime()
mpaa = API.getMpaa() mpaa = API.getMpaa()
genre = API.joinList(genres)
countries = API.getCountry() countries = API.getCountry()
country = API.joinList(countries) country = API.joinList(countries)
studios = API.getStudios() studios = API.getStudios()
@ -350,8 +351,8 @@ class Movies(Items):
for extra in extras: for extra in extras:
# Only get 1st trailer element # Only get 1st trailer element
if extra['extraType'] == '1': if extra['extraType'] == '1':
trailer = extra['key'] trailer = ("plugin://plugin.video.plexkodiconnect/trailer/?"
trailer = "plugin://plugin.video.plexkodiconnect/trailer/?id=%s&mode=play" % trailer "id=%s&mode=play") % extra['key']
self.logMsg("Trailer for %s: %s" % (itemid, trailer), 2) self.logMsg("Trailer for %s: %s" % (itemid, trailer), 2)
break break

View file

@ -63,10 +63,8 @@ class ThreadedGetMetadata(threading.Thread):
continue continue
# Download Metadata # Download Metadata
plexXML = PlexFunctions.GetPlexMetadata(updateItem['itemId']) plexXML = PlexFunctions.GetPlexMetadata(updateItem['itemId'])
try: if not plexXML:
plexXML.tag # Did not receive a valid XML - skip that item for now
except:
# Did not receive a valid XML - skip that one for now
queue.task_done() queue.task_done()
continue continue
@ -129,9 +127,11 @@ class ThreadedProcessMetadata(threading.Thread):
title = updateItem['title'] title = updateItem['title']
itemSubFkt = getattr(item, method) itemSubFkt = getattr(item, method)
with lock: with lock:
itemSubFkt(plexitem, # Get the one child entry in the xml and process
viewtag=viewName, for child in plexitem:
viewid=viewId) itemSubFkt(child,
viewtag=viewName,
viewid=viewId)
# Keep track of where we are at # Keep track of where we are at
processMetadataCount += 1 processMetadataCount += 1
processingViewName = title processingViewName = title
@ -303,7 +303,7 @@ class LibrarySync(threading.Thread):
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
plexType = items[0]['type'] plexType = items[0].attrib['type']
# Populate self.updatelist # Populate self.updatelist
self.GetUpdatelist(items, self.GetUpdatelist(items,
PlexFunctions.GetItemClassFromType(plexType), PlexFunctions.GetItemClassFromType(plexType),
@ -456,7 +456,10 @@ class LibrarySync(threading.Thread):
vnodes = self.vnodes vnodes = self.vnodes
# Get views # Get views
result = doUtils.downloadUrl("{server}/library/sections")['_children'] result = doUtils.downloadUrl("{server}/library/sections")
if not result:
self.logMsg("Error download PMS views, abort maintainViews", -1)
return False
# total nodes for window properties # total nodes for window properties
vnodes.clearProperties() vnodes.clearProperties()
@ -467,7 +470,8 @@ class LibrarySync(threading.Thread):
'movie', 'movie',
'show' 'show'
] ]
for folder in result: for folderItem in result:
folder = folderItem.attrib
mediatype = folder['type'] mediatype = folder['type']
if mediatype in mediatypes: if mediatype in mediatypes:
folderid = folder['key'] folderid = folder['key']
@ -567,13 +571,12 @@ class LibrarySync(threading.Thread):
embyconn.close() embyconn.close()
kodiconn.close() kodiconn.close()
def GetUpdatelist(self, elementList, itemType, method, viewName, viewId): def GetUpdatelist(self, xml, itemType, method, viewName, viewId):
""" """
Adds items to self.updatelist as well as self.allPlexElementsId dict Adds items to self.updatelist as well as self.allPlexElementsId dict
Input: Input:
elementList: List of elements, e.g. list of '_children' xml: PMS answer for section items
movie elements as received from PMS
itemType: 'Movies', 'TVShows', ... itemType: 'Movies', 'TVShows', ...
method: Method name to be called with this itemtype method: Method name to be called with this itemtype
see itemtypes.py see itemtypes.py
@ -596,12 +599,10 @@ class LibrarySync(threading.Thread):
""" """
if self.compare: if self.compare:
# Manual sync # Manual sync
for item in elementList: for item in xml:
# Skipping XML item 'title=All episodes' without a 'ratingKey' # Skipping items 'title=All episodes' without a 'ratingKey'
if not item.get('ratingKey', False): if not item.attrib.get('ratingKey', False):
continue continue
if self.threadStopped():
return False
API = PlexAPI.API(item) API = PlexAPI.API(item)
plex_checksum = API.getChecksum() plex_checksum = API.getChecksum()
itemId = API.getRatingKey() itemId = API.getRatingKey()
@ -619,12 +620,10 @@ class LibrarySync(threading.Thread):
'title': title}) 'title': title})
else: else:
# Initial or repair sync: get all Plex movies # Initial or repair sync: get all Plex movies
for item in elementList: for item in xml:
# Only look at valid items = Plex library items # Only look at valid items = Plex library items
if not item.get('ratingKey', False): if not item.attrib.get('ratingKey', False):
continue continue
if self.threadStopped():
return False
API = PlexAPI.API(item) API = PlexAPI.API(item)
itemId = API.getRatingKey() itemId = API.getRatingKey()
title, sorttitle = API.getTitle() title, sorttitle = API.getTitle()
@ -679,7 +678,7 @@ class LibrarySync(threading.Thread):
thread.setDaemon(True) thread.setDaemon(True)
thread.start() thread.start()
threads.append(thread) threads.append(thread)
self.logMsg("Download threads spawned", 1) self.logMsg("%s download threads spawned" % len(threads), 1)
# Spawn one more thread to process Metadata, once downloaded # Spawn one more thread to process Metadata, once downloaded
thread = ThreadedProcessMetadata(processMetadataQueue, thread = ThreadedProcessMetadata(processMetadataQueue,
itemType, itemType,
@ -758,6 +757,9 @@ class LibrarySync(threading.Thread):
viewId = view['id'] viewId = view['id']
viewName = view['name'] viewName = view['name']
all_plexmovies = PlexFunctions.GetPlexSectionResults(viewId) all_plexmovies = PlexFunctions.GetPlexSectionResults(viewId)
if not all_plexmovies:
self.logMsg("Couldnt get section items, aborting for view.", 1)
continue
# Populate self.updatelist and self.allPlexElementsId # Populate self.updatelist and self.allPlexElementsId
self.GetUpdatelist(all_plexmovies, self.GetUpdatelist(all_plexmovies,
itemType, itemType,
@ -768,6 +770,8 @@ class LibrarySync(threading.Thread):
self.logMsg("Processed view %s with ID %s" % (viewName, viewId), 1) self.logMsg("Processed view %s with ID %s" % (viewName, viewId), 1)
# Update viewstate # Update viewstate
for view in views: for view in views:
if self.threadStopped():
return False
self.PlexUpdateWatched(view['id'], itemType) self.PlexUpdateWatched(view['id'], itemType)
##### PROCESS DELETES ##### ##### PROCESS DELETES #####
@ -892,6 +896,10 @@ class LibrarySync(threading.Thread):
viewId = view['id'] viewId = view['id']
viewName = view['name'] viewName = view['name']
allPlexTvShows = PlexFunctions.GetPlexSectionResults(viewId) allPlexTvShows = PlexFunctions.GetPlexSectionResults(viewId)
if not allPlexTvShows:
self.logMsg(
"Error downloading show view xml for view %s" % viewId, -1)
continue
# Populate self.updatelist and self.allPlexElementsId # Populate self.updatelist and self.allPlexElementsId
self.GetUpdatelist(allPlexTvShows, self.GetUpdatelist(allPlexTvShows,
itemType, itemType,
@ -910,6 +918,10 @@ class LibrarySync(threading.Thread):
return False return False
# Grab all seasons to tvshow from PMS # Grab all seasons to tvshow from PMS
seasons = PlexFunctions.GetAllPlexChildren(tvShowId) seasons = PlexFunctions.GetAllPlexChildren(tvShowId)
if not seasons:
self.logMsg(
"Error downloading season xml for show %s" % tvShowId, -1)
continue
# Populate self.updatelist and self.allPlexElementsId # Populate self.updatelist and self.allPlexElementsId
self.GetUpdatelist(seasons, self.GetUpdatelist(seasons,
itemType, itemType,
@ -926,6 +938,11 @@ class LibrarySync(threading.Thread):
return False return False
# Grab all episodes to tvshow from PMS # Grab all episodes to tvshow from PMS
episodes = PlexFunctions.GetAllPlexLeaves(view['id']) episodes = PlexFunctions.GetAllPlexLeaves(view['id'])
if not episodes:
self.logMsg(
"Error downloading episod xml for view %s"
% view.get('name'), -1)
continue
# Populate self.updatelist and self.allPlexElementsId # Populate self.updatelist and self.allPlexElementsId
self.GetUpdatelist(episodes, self.GetUpdatelist(episodes,
itemType, itemType,