From fa0003a5eb81e34bd4a546c8cdde2bf310678eff Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Mon, 1 Feb 2016 10:33:33 +0100 Subject: [PATCH] Overhaul PlexAPI. Only using XMLs now, no JSONs --- resources/lib/PlexAPI.py | 320 +++++++++------------------------ resources/lib/PlexFunctions.py | 92 ++++------ resources/lib/downloadutils.py | 17 +- resources/lib/entrypoint.py | 2 +- resources/lib/itemtypes.py | 11 +- resources/lib/librarysync.py | 63 ++++--- 6 files changed, 175 insertions(+), 330 deletions(-) diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index 021b68ad..81ad80f7 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -769,13 +769,12 @@ class PlexAPI(): XML = self.getXMLFromPMS(PMS['baseURL'],PMS['path'],PMS['options'],PMS['token']) 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 requests. An authentication option is NOT yet added. Inputs: - JSON=True will enforce a JSON answer options: dictionary of options that will override the standard header options otherwise set. Output: @@ -800,8 +799,6 @@ class PlexAPI(): if self.token: xargs['X-Plex-Token'] = self.token - if JSON: - xargs['Accept'] = 'application/json' if options: xargs.update(options) return xargs @@ -1381,8 +1378,6 @@ class API(): self.server = utils.window('emby_server%s' % self.userId) self.token = utils.window('emby_accessToken%s' % self.userId) - self.jumpback = int(utils.settings('resumeJumpBack')) - def setPartNumber(self, number=0): """ Sets the part number to work with (used to deal with Movie with several @@ -1396,10 +1391,10 @@ class API(): """ return self.part - def convert_date(self, stamp): + def DateToKodi(self, stamp): """ - convert_date(stamp) converts a Unix time stamp (seconds passed since - January 1 1970) to a propper, human-readable time stamp + converts a Unix time stamp (seconds passed sinceJanuary 1 1970) to a + propper, human-readable time stamp used by Kodi """ # DATEFORMAT = xbmc.getRegion('dateshort') # TIMEFORMAT = xbmc.getRegion('meridiem') @@ -1415,85 +1410,53 @@ class API(): # else: # localtime = time.strftime('%H:%M', date_time) # return localtime + ' ' + localdate - DATEFORMAT = xbmc.getRegion('dateshort') - TIMEFORMAT = xbmc.getRegion('meridiem') - date_time = time.localtime(float(stamp)) - localdate = time.strftime('%Y-%m-%dT%H:%M:%SZ', date_time) + try: + DATEFORMAT = xbmc.getRegion('dateshort') + TIMEFORMAT = xbmc.getRegion('meridiem') + date_time = time.localtime(float(stamp)) + localdate = time.strftime('%Y-%m-%dT%H:%M:%SZ', date_time) + except: + localdate = None return localdate def getType(self): """ Returns the type of media, e.g. 'movie' or 'clip' for trailers """ - # XML - try: - item = self.item.attrib - # JSON - except AttributeError: - item = self.item - return item.get('type', '') + return self.item.attrib.get('type', None) def getChecksum(self): """ - Can be used on both XML and JSON 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! checksum = "K%s%s" % (self.getRatingKey(), - item.get('updatedAt', '')) + self.item.attrib.get('updatedAt', '')) return checksum def getRatingKey(self): """ - Can be used on both XML and JSON Returns the Plex key such as '246922' as a string """ - # XML - try: - result = self.item.attrib - # JSON - except AttributeError: - item = self.item - return item['ratingKey'] + return self.item.attrib.get('ratingKey', '') 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) + return self.item.attrib.get('key', None) def getIndex(self): """ Returns the 'index' of an PMS XML reply. Depicts e.g. season number. """ - item = self.item[0].attrib - index = item['index'] - return str(index) + return self.item.attrib.get('index', None) def getDateCreated(self): """ Returns the date when this library item was created """ - item = self.item[0].attrib - dateadded = item['addedAt'] - dateadded = self.convert_date(dateadded) - return dateadded + return self.DateToKodi(self.item.attrib.get('addedAt', None)) def getUserData(self): """ @@ -1508,7 +1471,7 @@ class API(): 'Rating': rating } """ - item = self.item + item = self.item.attrib # Default favorite = False playcount = None @@ -1517,13 +1480,6 @@ class API(): resume = 0 rating = 0 - # XML - try: - item = item[0].attrib - # JSON - except (AttributeError, KeyError): - pass - try: playcount = int(item['viewCount']) except KeyError: @@ -1533,8 +1489,7 @@ class API(): played = True try: - lastPlayedDate = int(item['lastViewedAt']) - lastPlayedDate = self.convert_date(lastPlayedDate) + lastPlayedDate = self.DateToKodi(int(item['lastViewedAt'])) except KeyError: lastPlayedDate = None @@ -1559,12 +1514,11 @@ class API(): 'Producer': list } """ - item = self.item director = [] writer = [] cast = [] producer = [] - for child in item[0]: + for child in self.item: if child.tag == 'Director': director.append(child.attrib['tag']) elif child.tag == 'Writer': @@ -1591,7 +1545,6 @@ class API(): ('Role': xxx for cast/actors only, None if not found) } """ - item = self.item people = [] # Key of library: Plex-identifier. Value represents the Kodi/emby side people_of_interest = { @@ -1600,7 +1553,7 @@ class API(): 'Role': 'Actor', 'Producer': 'Producer' } - for child in item[0]: + for child in self.item: if child.tag in people_of_interest.keys(): name = child.attrib['tag'] name_id = child.attrib['id'] @@ -1630,9 +1583,8 @@ class API(): """ Returns a list of genres found. (Not a string) """ - item = self.item genre = [] - for child in item[0]: + for child in self.item: if child.tag == 'Genre': genre.append(child.attrib['tag']) return genre @@ -1643,8 +1595,7 @@ class API(): Return IMDB, e.g. "imdb://tt0903624?lang=en". Returns None if not found """ - item = self.item - item = item[0].attrib + item = self.item.attrib try: item = item['guid'] except KeyError: @@ -1660,99 +1611,50 @@ class API(): def getTitle(self): """ - Returns an item's name/title or "Missing Title Name" for both XML and - JSON PMS replies - + Returns an item's name/title or "Missing Title Name". Output: title, sorttitle sorttitle = title, if no sorttitle is found """ - item = self.item - - # 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 + title = self.item.attrib.get('title', 'Missing Title Name') + sorttitle = self.item.attrib.get('titleSort', title) return title, sorttitle def getPlot(self): """ Returns the plot or None. """ - item = self.item - item = item[0].attrib - try: - plot = item['summary'] - except: - plot = None - return plot + return self.item.attrib.get('summary', None) def getTagline(self): """ Returns a shorter tagline or None """ - item = self.item - item = item[0].attrib - try: - tagline = item['tagline'] - except KeyError: - tagline = None - return tagline + return self.item.attrib.get('tagline', None) def getAudienceRating(self): """ Returns the audience rating or None """ - item = self.item - item = item[0].attrib - try: - rating = item['audienceRating'] - except KeyError: - rating = None - return rating + return self.item.attrib.get('audienceRating', None) def getYear(self): """ Returns the production(?) year ("year") or None """ - item = self.item - item = item[0].attrib - try: - year = item['year'] - except KeyError: - year = None - return year + return self.item.attrib.get('year', None) def getRuntime(self): """ - Resume point of time and runtime/totaltime in seconds, rounded to 6th - decimal. + Resume point of time and runtime/totaltime in rounded to seconds. Time from Plex server is measured in milliseconds. Kodi: seconds Output: - resume, runtime as floats. 0.0 if not found + resume, runtime as ints. 0 if not found """ - time_factor = PlexToKodiTimefactor() - - # XML - try: - item = self.item[0].attrib - # JSON - except (AttributeError, KeyError): - pass + item = self.item.attrib try: runtime = float(item['duration']) @@ -1763,30 +1665,16 @@ class API(): except KeyError: resume = 0.0 - # Adjust the resume point by x seconds as chosen by the user in the - # settings - 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) + runtime = int(runtime * PlexToKodiTimefactor()) + resume = int(resume * PlexToKodiTimefactor()) return resume, runtime def getMpaa(self): """ Get the content rating or None """ + mpaa = self.item.attrib.get('contentRating', None) # Convert more complex cases - item = self.item - item = item[0].attrib - try: - mpaa = item['contentRating'] - except KeyError: - mpaa = None if mpaa in ("NR", "UR"): # Kodi seems to not like NR, but will accept Rated Not Rated mpaa = "Rated Not Rated" @@ -1796,9 +1684,8 @@ class API(): """ Returns a list of all countries found in item. """ - item = self.item country = [] - for child in item[0]: + for child in self.item: if child.tag == 'Country': country.append(child.attrib['tag']) return country @@ -1807,23 +1694,15 @@ class API(): """ Returns the "originallyAvailableAt" or None """ - item = self.item - item = item[0].attrib - try: - premiere = item['originallyAvailableAt'] - except: - premiere = None - return premiere + return self.item.attrib.get('originallyAvailableAt', None) def getStudios(self): """ Returns a list with a single entry for the studio, or an empty list """ - item = self.item studio = [] - item = item[0].attrib try: - studio.append(self.getStudio(item['studio'])) + studio.append(self.getStudio(self.item.attrib['studio'])) except KeyError: pass return studio @@ -1862,7 +1741,7 @@ class API(): Episode number, Plex: 'index' ] """ - item = self.item[0].attrib + item = self.item.attrib key = item['grandparentRatingKey'] title = item['grandparentTitle'] season = item['parentIndex'] @@ -1891,13 +1770,7 @@ class API(): If not found, empty str is returned """ - # XML: - try: - item = self.item.attrib - # JSON - except AttributeError: - item = self.item - return item.get('playQueueItemID', '') + return self.item.atrrib.get('playQueueItemID', '') def getDataFromPartOrMedia(self, key): """ @@ -1906,14 +1779,8 @@ class API(): If all fails, None is returned. """ - # JSON - try: - 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 + media = self.item.attrib + part = self.item[self.part].attrib try: try: @@ -1979,26 +1846,25 @@ class API(): 'originallyAvailableAt': 'year': """ - extras = self.item[0].find('Extras') elements = [] - if not extras: - return elements - for extra in extras: + for extra in self.item.find('Extras'): # Trailer: - key = extra.attrib['key'] - title = extra.attrib['title'] - thumb = extra.attrib['thumb'] - duration = extra.attrib['duration'] - year = extra.attrib['year'] - extraType = extra.attrib['extraType'] - originallyAvailableAt = extra.attrib['originallyAvailableAt'] - elements.append({'key': key, - 'title': title, - 'thumb': thumb, - 'duration': duration, - 'extraType': extraType, - 'originallyAvailableAt': originallyAvailableAt, - 'year': year}) + key = extra.attrib.get('key', None) + title = extra.attrib.get('title', None) + thumb = extra.attrib.get('thumb', None) + duration = float(extra.attrib.get('duration', 0.0)) + year = extra.attrib.get('year', None) + extraType = extra.attrib.get('extraType', None) + originallyAvailableAt = extra.attrib.get( + 'originallyAvailableAt', None) + elements.append( + {'key': key, + 'title': title, + 'thumb': thumb, + 'duration': int(duration * PlexToKodiTimefactor()), + 'extraType': extraType, + 'originallyAvailableAt': originallyAvailableAt, + 'year': year}) return elements def getMediaStreams(self): @@ -2014,24 +1880,20 @@ class API(): 'subtitle': list of subtitle languages (or "Unknown") } """ - item = self.item videotracks = [] audiotracks = [] subtitlelanguages = [] - aspectratio = None - try: - aspectratio = item[0][0].attrib['aspectRatio'] - except KeyError: - pass + # Sometimes, aspectratio is on the "toplevel" + aspectratio = self.item[0].attrib.get('aspectRatio', None) # TODO: what if several Media tags exist?!? # Loop over parts - for child in item[0][0]: - container = child.attrib['container'].lower() + for child in self.item[0]: + container = child.attrib.get('container', None) # Loop over Streams for grandchild in child: mediaStream = grandchild.attrib - type = int(mediaStream['streamType']) - if type == 1: # Video streams + mediaType = int(mediaStream.get('streamType', 999)) + if mediaType == 1: # Video streams videotrack = {} videotrack['codec'] = mediaStream['codec'].lower() if "msmpeg4" in videotrack['codec']: @@ -2043,21 +1905,17 @@ class API(): elif "h264" in videotrack['codec']: if container in ("mp4", "mov", "m4v"): videotrack['codec'] = "avc1" - videotrack['height'] = mediaStream.get('height') - videotrack['width'] = mediaStream.get('width') + videotrack['height'] = mediaStream.get('height', None) + videotrack['width'] = mediaStream.get('width', None) # TODO: 3d Movies?!? # videotrack['Video3DFormat'] = item.get('Video3DFormat') - try: - aspectratio = mediaStream['aspectRatio'] - except KeyError: - if not aspectratio: - aspectratio = round(float(videotrack['width'] / videotrack['height']), 6) + aspectratio = mediaStream.get('aspectRatio', aspectratio) videotrack['aspect'] = aspectratio # TODO: Video 3d format videotrack['video3DFormat'] = None videotracks.append(videotrack) - elif type == 2: # Audio streams + elif mediaType == 2: # Audio streams audiotrack = {} audiotrack['codec'] = mediaStream['codec'].lower() profile = mediaStream['codecID'].lower() @@ -2070,17 +1928,16 @@ class API(): audiotrack['language'] = 'unknown' audiotracks.append(audiotrack) - elif type == 3: # Subtitle streams + elif mediaType == 3: # Subtitle streams try: subtitlelanguages.append(mediaStream['language']) except: subtitlelanguages.append("Unknown") - media = { + return { 'video': videotracks, 'audio': audiotracks, 'subtitle': subtitlelanguages } - return media def getAllArtwork(self, parentInfo=False): """ @@ -2097,15 +1954,7 @@ class API(): 'Backdrop': [] Plex key: "art". Only 1 pix } """ - server = self.server - item = self.item - - # XML - try: - item = item[0].attrib - # JSON - except (AttributeError, KeyError): - pass + item = self.item.attrib maxHeight = 10000 maxWidth = 10000 @@ -2130,7 +1979,7 @@ class API(): # Get background artwork URL try: background = item['art'] - background = "%s%s" % (server, background) + background = "%s%s" % (self.server, background) background = self.addPlexCredentialsToUrl(background) except KeyError: background = "" @@ -2138,7 +1987,7 @@ class API(): # Get primary "thumb" pictures: try: primary = item['thumb'] - primary = "%s%s" % (server, primary) + primary = "%s%s" % (self.server, primary) primary = self.addPlexCredentialsToUrl(primary) except KeyError: primary = "" @@ -2160,7 +2009,7 @@ class API(): artwork = ( "%s/emby/Items/%s/Images/Backdrop/%s?" "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" - % (server, parentId, backdropIndex, + % (self.server, parentId, backdropIndex, maxWidth, maxHeight, parentbackdroptag, customquery)) allartworks['Backdrop'].append(artwork) backdropIndex += 1 @@ -2178,7 +2027,7 @@ class API(): artwork = ( "%s/emby/Items/%s/Images/%s/0?" "MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s" - % (server, parentId, parentart, + % (self.server, parentId, parentart, maxWidth, maxHeight, parentTag, customquery)) allartworks[parentart] = artwork @@ -2192,11 +2041,12 @@ class API(): artwork = ( "%s/emby/Items/%s/Images/Primary/0?" "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 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 @@ -2329,12 +2179,12 @@ class API(): mapping = {} item = self.item - itemid = self.getRatingKey() try: - mediastreams = item[0][0][0] + mediastreams = item[0][self.part] except (TypeError, KeyError, IndexError): return + itemid = self.getRatingKey() kodiindex = 0 for stream in mediastreams: # index = stream['Index'] @@ -2394,9 +2244,3 @@ class API(): if not xml: self.logMsg("Error retrieving metadata for %s" % url, 1) return xml - - def GetParts(self): - """ - Returns the parts of the specified video child in the XML response - """ - return self.item[0][0] diff --git a/resources/lib/PlexFunctions.py b/resources/lib/PlexFunctions.py index 3a83a5bd..358dde4e 100644 --- a/resources/lib/PlexFunctions.py +++ b/resources/lib/PlexFunctions.py @@ -115,16 +115,15 @@ def GetPlayQueue(playQueueID): return xml -def GetPlexMetadata(key, JSON=True): +def GetPlexMetadata(key): """ Returns raw API metadata for key as an etree XML. Can be called with either Plex key '/library/metadata/xxxx'metadata OR with the digits 'xxxx' only. - Returns an empty string '' if something went wrong + Returns None if something went wrong """ - xml = '' key = str(key) if '/library/metadata/' in key: url = "{server}" + key @@ -133,82 +132,72 @@ def GetPlexMetadata(key, JSON=True): arguments = { 'checkFiles': 1, # No idea 'includeExtras': 1, # Trailers and Extras => Extras - 'includeRelated': 1, # Similar movies => Video -> Related - 'includeRelatedCount': 5, - 'includeOnDeck': 1, + # 'includeRelated': 1, # Similar movies => Video -> Related + # 'includeRelatedCount': 5, + # 'includeOnDeck': 1, 'includeChapters': 1, 'includePopularLeaves': 1, 'includeConcerts': 1 } url = url + '?' + urlencode(arguments) - if not JSON: - headerOptions = {'Accept': 'application/xml'} - else: - headerOptions = {} - xml = downloadutils.DownloadUtils().downloadUrl(url, headerOptions=headerOptions) + xml = downloadutils.DownloadUtils().downloadUrl(url) # Did we receive a valid XML? try: - xml.tag + xml.attrib # Nope we did not receive a valid XML except AttributeError: logMsg(title, "Error retrieving metadata for %s" % url, -1) - xml = '' + xml = None return xml 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) Input: key Key to a Plex item, e.g. 12345 """ - result = [] - url = "{server}/library/metadata/%s/children" % key - jsondata = downloadutils.DownloadUtils().downloadUrl(url) + xml = downloadutils.DownloadUtils().downloadUrl( + "{server}/library/metadata/%s/children" % key) try: - result = jsondata['_children'] - except KeyError: + xml.attrib + except AttributeError: logMsg( title, "Error retrieving all children for Plex item %s" % key, -1) - pass - return result + xml = None + return xml 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. + + Returns None if something went wrong """ result = [] url = "{server}/library/sections/%s/all" % viewId - jsondata = downloadutils.DownloadUtils().downloadUrl(url, headerOptions=headerOptions) + result = downloadutils.DownloadUtils().downloadUrl( + url, headerOptions=headerOptions) + try: - result = jsondata['_children'] - except TypeError: - # Maybe we received an XML, check for that with tag attribute - 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: + result.tag + # Nope, not an XML, abort + except AttributeError: logMsg(title, "Error retrieving all items for Plex section %s" % viewId, -1) + result = None + return result def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None, headerOptions={}): """ - Returns a list (raw JSON or XML API dump) of all Plex subitems for the - key. + Returns a list (raw XML API dump) of all Plex subitems for the key. (e.g. /library/sections/2/allLeaves pointing to all TV shows) Input: @@ -226,7 +215,6 @@ def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None, Relevant "master time": PMS server. I guess this COULD lead to problems, e.g. when server and client are in different time zones. """ - result = [] args = [] url = "{server}/library/sections/%s/allLeaves?" % viewId if lastViewedAt: @@ -234,24 +222,18 @@ def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None, if updatedAt: args.append('updatedAt>=%s' % updatedAt) args = '&'.join(args) - jsondata = downloadutils.DownloadUtils().downloadUrl( + xml = downloadutils.DownloadUtils().downloadUrl( url+args, headerOptions=headerOptions) + try: - result = jsondata['_children'] - except TypeError: - # Maybe we received an XML, check for that with tag attribute - try: - jsondata.tag - result = jsondata - # Nope, not an XML, abort - except AttributeError: - 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 + xml.attrib + # Nope, not an XML, abort + except AttributeError: + logMsg(title, + "Error retrieving all leaves for Plex section %s" + % viewId, -1) + xml = None + return xml def GetPlexCollections(mediatype): diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index 92b98943..e830d0c4 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -180,9 +180,9 @@ class DownloadUtils(): plx = PlexAPI.PlexAPI() if authenticate: options['X-Plex-Token'] = self.token - header = plx.getXArgsDeviceInfo(options=options, JSON=True) + header = plx.getXArgsDeviceInfo(options=options) else: - header = plx.getXArgsDeviceInfo(options=options, JSON=True) + header = plx.getXArgsDeviceInfo(options=options) return header 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: try: - # UNICODE - JSON object - r = r.json() + # Allow for xml responses + r = etree.fromstring(r.content) self.logMsg("====== 200 Success ======", 2) - self.logMsg("Response: %s" % r, 2) + self.logMsg("Received an XML response for: %s" % url, 2) + return r except: - # Allow for xml responses try: - r = etree.fromstring(r.content) + # UNICODE - JSON object + r = r.json() self.logMsg("====== 200 Success ======", 2) - self.logMsg("Received an XML response for: %s" % url, 2) + self.logMsg("Response: %s" % r, 2) return r except: try: diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index d651dde6..d416a7a1 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -105,7 +105,7 @@ def PassPlaylist(xml, resume=0): def doPlayback(itemid, dbid): utils.logMsg(title, "doPlayback called with %s %s" % (itemid, dbid), 1) - item = PlexFunctions.GetPlexMetadata(itemid, JSON=True) + item = PlexFunctions.GetPlexMetadata(itemid) playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) # If current playlist is NOT empty, we only need to update the item url if playlist.size() != 0: diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 04b92828..7a9c7a4c 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -317,12 +317,15 @@ class Movies(Items): userdata = API.getUserData() playcount = userdata['PlayCount'] dateplayed = userdata['LastPlayedDate'] + resume = userdata['Resume'] + runtime = userdata['Runtime'] # item details people = API.getPeople() writer = API.joinList(people['Writer']) director = API.joinList(people['Director']) genres = API.getGenres() + genre = API.joinList(genres) title, sorttitle = API.getTitle() plot = API.getPlot() shortplot = None @@ -331,10 +334,8 @@ class Movies(Items): rating = API.getAudienceRating() year = API.getYear() - imdb = API.getProvider('Imdb') - resume, runtime = API.getRuntime() + imdb = API.getProvider() mpaa = API.getMpaa() - genre = API.joinList(genres) countries = API.getCountry() country = API.joinList(countries) studios = API.getStudios() @@ -350,8 +351,8 @@ class Movies(Items): for extra in extras: # Only get 1st trailer element if extra['extraType'] == '1': - trailer = extra['key'] - trailer = "plugin://plugin.video.plexkodiconnect/trailer/?id=%s&mode=play" % trailer + trailer = ("plugin://plugin.video.plexkodiconnect/trailer/?" + "id=%s&mode=play") % extra['key'] self.logMsg("Trailer for %s: %s" % (itemid, trailer), 2) break diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 872a6ad9..86210897 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -63,10 +63,8 @@ class ThreadedGetMetadata(threading.Thread): continue # Download Metadata plexXML = PlexFunctions.GetPlexMetadata(updateItem['itemId']) - try: - plexXML.tag - except: - # Did not receive a valid XML - skip that one for now + if not plexXML: + # Did not receive a valid XML - skip that item for now queue.task_done() continue @@ -129,9 +127,11 @@ class ThreadedProcessMetadata(threading.Thread): title = updateItem['title'] itemSubFkt = getattr(item, method) with lock: - itemSubFkt(plexitem, - viewtag=viewName, - viewid=viewId) + # Get the one child entry in the xml and process + for child in plexitem: + itemSubFkt(child, + viewtag=viewName, + viewid=viewId) # Keep track of where we are at processMetadataCount += 1 processingViewName = title @@ -303,7 +303,7 @@ class LibrarySync(threading.Thread): if not items: continue # Get one itemtype, because they're the same in the PMS section - plexType = items[0]['type'] + plexType = items[0].attrib['type'] # Populate self.updatelist self.GetUpdatelist(items, PlexFunctions.GetItemClassFromType(plexType), @@ -456,7 +456,10 @@ class LibrarySync(threading.Thread): vnodes = self.vnodes # 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 vnodes.clearProperties() @@ -467,7 +470,8 @@ class LibrarySync(threading.Thread): 'movie', 'show' ] - for folder in result: + for folderItem in result: + folder = folderItem.attrib mediatype = folder['type'] if mediatype in mediatypes: folderid = folder['key'] @@ -567,13 +571,12 @@ class LibrarySync(threading.Thread): embyconn.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 Input: - elementList: List of elements, e.g. list of '_children' - movie elements as received from PMS + xml: PMS answer for section items itemType: 'Movies', 'TVShows', ... method: Method name to be called with this itemtype see itemtypes.py @@ -596,12 +599,10 @@ class LibrarySync(threading.Thread): """ if self.compare: # Manual sync - for item in elementList: - # Skipping XML item 'title=All episodes' without a 'ratingKey' - if not item.get('ratingKey', False): + for item in xml: + # Skipping items 'title=All episodes' without a 'ratingKey' + if not item.attrib.get('ratingKey', False): continue - if self.threadStopped(): - return False API = PlexAPI.API(item) plex_checksum = API.getChecksum() itemId = API.getRatingKey() @@ -619,12 +620,10 @@ class LibrarySync(threading.Thread): 'title': title}) else: # Initial or repair sync: get all Plex movies - for item in elementList: + for item in xml: # Only look at valid items = Plex library items - if not item.get('ratingKey', False): + if not item.attrib.get('ratingKey', False): continue - if self.threadStopped(): - return False API = PlexAPI.API(item) itemId = API.getRatingKey() title, sorttitle = API.getTitle() @@ -679,7 +678,7 @@ class LibrarySync(threading.Thread): thread.setDaemon(True) thread.start() 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 thread = ThreadedProcessMetadata(processMetadataQueue, itemType, @@ -758,6 +757,9 @@ class LibrarySync(threading.Thread): viewId = view['id'] viewName = view['name'] 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 self.GetUpdatelist(all_plexmovies, itemType, @@ -768,6 +770,8 @@ class LibrarySync(threading.Thread): self.logMsg("Processed view %s with ID %s" % (viewName, viewId), 1) # Update viewstate for view in views: + if self.threadStopped(): + return False self.PlexUpdateWatched(view['id'], itemType) ##### PROCESS DELETES ##### @@ -892,6 +896,10 @@ class LibrarySync(threading.Thread): viewId = view['id'] viewName = view['name'] 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 self.GetUpdatelist(allPlexTvShows, itemType, @@ -910,6 +918,10 @@ class LibrarySync(threading.Thread): return False # Grab all seasons to tvshow from PMS 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 self.GetUpdatelist(seasons, itemType, @@ -926,6 +938,11 @@ class LibrarySync(threading.Thread): return False # Grab all episodes to tvshow from PMS 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 self.GetUpdatelist(episodes, itemType,