Overhaul PlexAPI. Only using XMLs now, no JSONs
This commit is contained in:
parent
dc44f1a879
commit
fa0003a5eb
6 changed files with 175 additions and 330 deletions
|
@ -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]
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue