Trailers up. BUT: first trailer is repeated indefinitely
This commit is contained in:
parent
e1eab21f7f
commit
7f9dfca2d6
6 changed files with 255 additions and 221 deletions
|
@ -1258,44 +1258,20 @@ class PlexAPI():
|
||||||
self.logMsg("Error retrieving metadata for %s" % url, 1)
|
self.logMsg("Error retrieving metadata for %s" % url, 1)
|
||||||
return xml
|
return xml
|
||||||
|
|
||||||
def GetPlexPlaylist(self, key):
|
|
||||||
"""
|
|
||||||
Returns raw API metadata XML dump.
|
|
||||||
|
|
||||||
Can be called with either Plex key '/library/metadata/xxxx'metadata
|
|
||||||
OR with the digits 'xxxx' only.
|
|
||||||
"""
|
|
||||||
xml = ''
|
|
||||||
key = str(key)
|
|
||||||
url = "{server}/playQueues"
|
|
||||||
if '/library/metadata/' in key:
|
|
||||||
# Cut of the slash!
|
|
||||||
item = key[1:]
|
|
||||||
else:
|
|
||||||
item = "library/metadata/" + key
|
|
||||||
arguments = {
|
|
||||||
'checkFiles': 1, # No idea
|
|
||||||
'includeExtras': 1, # Trailers and Extras => Extras
|
|
||||||
'includeRelated': 1, # Similar movies => Video -> Related
|
|
||||||
'includeRelatedCount': 5,
|
|
||||||
'includeOnDeck': 1,
|
|
||||||
'includeChapters': 1,
|
|
||||||
'includePopularLeaves': 1,
|
|
||||||
'includeConcerts': 1
|
|
||||||
}
|
|
||||||
url = url + '?' + urlencode(arguments)
|
|
||||||
headerOptions = {'Accept': 'application/xml'}
|
|
||||||
xml = self.doUtils.downloadUrl(url, headerOptions=headerOptions)
|
|
||||||
if not xml:
|
|
||||||
self.logMsg("Error retrieving metadata for %s" % url, 1)
|
|
||||||
return xml
|
|
||||||
|
|
||||||
|
|
||||||
class API():
|
class API():
|
||||||
|
"""
|
||||||
|
API(item)
|
||||||
|
|
||||||
|
Processes a Plex media server's XML response
|
||||||
|
|
||||||
|
item: xml.etree.ElementTree element
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, item):
|
def __init__(self, item):
|
||||||
|
|
||||||
self.item = item
|
self.item = item
|
||||||
|
# which child in the XML response shall we look at?
|
||||||
|
self.child = 0
|
||||||
self.clientinfo = clientinfo.ClientInfo()
|
self.clientinfo = clientinfo.ClientInfo()
|
||||||
self.addonName = self.clientinfo.getAddonName()
|
self.addonName = self.clientinfo.getAddonName()
|
||||||
self.clientId = self.clientinfo.getDeviceId()
|
self.clientId = self.clientinfo.getDeviceId()
|
||||||
|
@ -1304,10 +1280,16 @@ class API():
|
||||||
self.token = utils.window('emby_accessToken%s' % self.userId)
|
self.token = utils.window('emby_accessToken%s' % self.userId)
|
||||||
|
|
||||||
def logMsg(self, msg, lvl=1):
|
def logMsg(self, msg, lvl=1):
|
||||||
|
|
||||||
className = self.__class__.__name__
|
className = self.__class__.__name__
|
||||||
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
utils.logMsg("%s %s" % (self.addonName, className), msg, lvl)
|
||||||
|
|
||||||
|
def setChildNumber(self, number=0):
|
||||||
|
"""
|
||||||
|
Which child in the XML response shall we look at and work with?
|
||||||
|
"""
|
||||||
|
self.child = number
|
||||||
|
self.logMsg("Set child number to %s" % number, 1)
|
||||||
|
|
||||||
def convert_date(self, stamp):
|
def convert_date(self, stamp):
|
||||||
"""
|
"""
|
||||||
convert_date(stamp) converts a Unix time stamp (seconds passed since
|
convert_date(stamp) converts a Unix time stamp (seconds passed since
|
||||||
|
@ -1334,29 +1316,27 @@ class API():
|
||||||
return localdate
|
return localdate
|
||||||
|
|
||||||
def getType(self):
|
def getType(self):
|
||||||
|
"""
|
||||||
|
Returns the type of media, e.g. 'movie'
|
||||||
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
# Include a letter to prohibit saving as an int!
|
item = item[self.child].attrib
|
||||||
# xml
|
|
||||||
try:
|
|
||||||
item = item[0].attrib
|
|
||||||
# json
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
itemtype = item['type']
|
itemtype = item['type']
|
||||||
return itemtype
|
return itemtype
|
||||||
|
|
||||||
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
|
item = self.item
|
||||||
# Include a letter to prohibit saving as an int!
|
# XML
|
||||||
# xml
|
|
||||||
try:
|
try:
|
||||||
item = item[0].attrib
|
item = item[self.child].attrib
|
||||||
# json
|
# JSON
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
# Include a letter to prohibit saving as an int!
|
||||||
checksum = "K%s%s%s%s%s" % (
|
checksum = "K%s%s%s%s%s" % (
|
||||||
self.getKey(),
|
self.getKey(),
|
||||||
item['updatedAt'],
|
item['updatedAt'],
|
||||||
|
@ -1368,14 +1348,15 @@ class API():
|
||||||
|
|
||||||
def getKey(self):
|
def getKey(self):
|
||||||
"""
|
"""
|
||||||
|
Can be used on both XML and JSON
|
||||||
Returns the Plex unique movie id as a str, not int
|
Returns the Plex unique movie id as a str, not int
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
key_regex = re.compile(r'/(\d+)$')
|
key_regex = re.compile(r'/(\d+)$')
|
||||||
# xml
|
# XML
|
||||||
try:
|
try:
|
||||||
item = item[0].attrib
|
item = item[self.child].attrib
|
||||||
# json
|
# JSON
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
key = item['key']
|
key = item['key']
|
||||||
|
@ -1383,21 +1364,30 @@ class API():
|
||||||
return str(key)
|
return str(key)
|
||||||
|
|
||||||
def getDateCreated(self):
|
def getDateCreated(self):
|
||||||
|
"""
|
||||||
|
Returns the date when this library item was created
|
||||||
|
|
||||||
|
Input:
|
||||||
|
index child number as int; normally =0
|
||||||
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
# xml
|
item = item[self.child].attrib
|
||||||
try:
|
|
||||||
item = item[0].attrib
|
|
||||||
# json
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
dateadded = item['addedAt']
|
dateadded = item['addedAt']
|
||||||
dateadded = self.convert_date(dateadded)
|
dateadded = self.convert_date(dateadded)
|
||||||
except KeyError:
|
|
||||||
dateadded = None
|
|
||||||
return dateadded
|
return dateadded
|
||||||
|
|
||||||
def getUserData(self):
|
def getUserData(self):
|
||||||
|
"""
|
||||||
|
Returns a dict with None if a value is missing
|
||||||
|
{
|
||||||
|
'Favorite': favorite, # False, because n/a in Plex
|
||||||
|
'PlayCount': playcount,
|
||||||
|
'Played': played, # True/False
|
||||||
|
'LastPlayedDate': lastPlayedDate,
|
||||||
|
'Resume': resume, # Resume time in seconds
|
||||||
|
'Rating': rating
|
||||||
|
}
|
||||||
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
# Default
|
# Default
|
||||||
favorite = False
|
favorite = False
|
||||||
|
@ -1407,12 +1397,7 @@ class API():
|
||||||
resume = 0
|
resume = 0
|
||||||
rating = 0
|
rating = 0
|
||||||
|
|
||||||
# xml
|
item = item[self.child].attrib
|
||||||
try:
|
|
||||||
item = item[0].attrib
|
|
||||||
# json
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
try:
|
||||||
playcount = int(item['viewCount'])
|
playcount = int(item['viewCount'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -1443,9 +1428,7 @@ class API():
|
||||||
|
|
||||||
def getPeople(self):
|
def getPeople(self):
|
||||||
"""
|
"""
|
||||||
Input is Plex' XMl
|
Returns a dict of lists of people found.
|
||||||
Returns a dictionary of lists of people found in item.
|
|
||||||
|
|
||||||
{
|
{
|
||||||
'Director': list,
|
'Director': list,
|
||||||
'Writer': list,
|
'Writer': list,
|
||||||
|
@ -1458,7 +1441,7 @@ class API():
|
||||||
writer = []
|
writer = []
|
||||||
cast = []
|
cast = []
|
||||||
producer = []
|
producer = []
|
||||||
for child in item[0]:
|
for child in item[self.child]:
|
||||||
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':
|
||||||
|
@ -1481,8 +1464,8 @@ class API():
|
||||||
'Name': xxx,
|
'Name': xxx,
|
||||||
'Type': xxx,
|
'Type': xxx,
|
||||||
'Id': xxx
|
'Id': xxx
|
||||||
'imageurl': url to picture
|
'imageurl': url to picture, None otherwise
|
||||||
('Role': xxx for cast/actors only)
|
('Role': xxx for cast/actors only, None if not found)
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
|
@ -1494,7 +1477,7 @@ class API():
|
||||||
'Role': 'Actor',
|
'Role': 'Actor',
|
||||||
'Producer': 'Producer'
|
'Producer': 'Producer'
|
||||||
}
|
}
|
||||||
for child in item[0]:
|
for child in item[self.child]:
|
||||||
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']
|
||||||
|
@ -1522,31 +1505,24 @@ class API():
|
||||||
|
|
||||||
def getGenres(self):
|
def getGenres(self):
|
||||||
"""
|
"""
|
||||||
returns a list of genres found in item. (Not a string!!)
|
Returns a list of genres found. (Not a string)
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
genre = []
|
genre = []
|
||||||
for child in item[0]:
|
for child in item[self.child]:
|
||||||
if child.tag == 'Genre':
|
if child.tag == 'Genre':
|
||||||
genre.append(child.attrib['tag'])
|
genre.append(child.attrib['tag'])
|
||||||
return genre
|
return genre
|
||||||
|
|
||||||
def getProvider(self, providername):
|
def getProvider(self, providername):
|
||||||
"""
|
"""
|
||||||
provider = getProvider(self, item, providername)
|
|
||||||
|
|
||||||
providername: imdb, tvdb, musicBrainzArtist, musicBrainzAlbum,
|
providername: imdb, tvdb, musicBrainzArtist, musicBrainzAlbum,
|
||||||
musicBrainzTrackId
|
musicBrainzTrackId
|
||||||
|
|
||||||
Return IMDB: "tt1234567"
|
Return IMDB: "tt1234567". Returns None if not found
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
# xml
|
item = item[self.child].attrib
|
||||||
try:
|
|
||||||
item = item[0].attrib
|
|
||||||
# json
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
imdb_regex = re.compile(r'''(
|
imdb_regex = re.compile(r'''(
|
||||||
imdb:// # imdb tag, which will be followed be tt1234567
|
imdb:// # imdb tag, which will be followed be tt1234567
|
||||||
(tt\d{7}) # actual IMDB ID, e.g. tt1234567
|
(tt\d{7}) # actual IMDB ID, e.g. tt1234567
|
||||||
|
@ -1570,13 +1546,16 @@ class API():
|
||||||
return provider
|
return provider
|
||||||
|
|
||||||
def getTitle(self):
|
def getTitle(self):
|
||||||
|
"""
|
||||||
|
Returns an item's name/title or "Missing Title Name"
|
||||||
|
|
||||||
|
Output:
|
||||||
|
title, sorttitle
|
||||||
|
|
||||||
|
sorttitle = title, if no sorttitle is found
|
||||||
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
# xml
|
item = item[self.child].attrib
|
||||||
try:
|
|
||||||
item = item[0].attrib
|
|
||||||
# json
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
try:
|
||||||
title = item['title']
|
title = item['title']
|
||||||
except:
|
except:
|
||||||
|
@ -1588,13 +1567,11 @@ class API():
|
||||||
return title, sorttitle
|
return title, sorttitle
|
||||||
|
|
||||||
def getPlot(self):
|
def getPlot(self):
|
||||||
|
"""
|
||||||
|
Returns the plot or None.
|
||||||
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
# xml
|
item = item[self.child].attrib
|
||||||
try:
|
|
||||||
item = item[0].attrib
|
|
||||||
# json
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
try:
|
||||||
plot = item['summary']
|
plot = item['summary']
|
||||||
except:
|
except:
|
||||||
|
@ -1602,13 +1579,11 @@ class API():
|
||||||
return plot
|
return plot
|
||||||
|
|
||||||
def getTagline(self):
|
def getTagline(self):
|
||||||
|
"""
|
||||||
|
Returns a shorter tagline or None
|
||||||
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
# xml
|
item = item[self.child].attrib
|
||||||
try:
|
|
||||||
item = item[0].attrib
|
|
||||||
# json
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
try:
|
||||||
tagline = item['tagline']
|
tagline = item['tagline']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -1616,13 +1591,11 @@ class API():
|
||||||
return tagline
|
return tagline
|
||||||
|
|
||||||
def getAudienceRating(self):
|
def getAudienceRating(self):
|
||||||
|
"""
|
||||||
|
Returns the audience rating or None
|
||||||
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
# xml
|
item = item[self.child].attrib
|
||||||
try:
|
|
||||||
item = item[0].attrib
|
|
||||||
# json
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
try:
|
||||||
rating = item['audienceRating']
|
rating = item['audienceRating']
|
||||||
except:
|
except:
|
||||||
|
@ -1630,13 +1603,11 @@ class API():
|
||||||
return rating
|
return rating
|
||||||
|
|
||||||
def getYear(self):
|
def getYear(self):
|
||||||
|
"""
|
||||||
|
Returns the production(?) year ("year") or None
|
||||||
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
# xml
|
item = item[self.child].attrib
|
||||||
try:
|
|
||||||
item = item[0].attrib
|
|
||||||
# json
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
try:
|
||||||
year = item['year']
|
year = item['year']
|
||||||
except:
|
except:
|
||||||
|
@ -1645,20 +1616,21 @@ class API():
|
||||||
|
|
||||||
def getRuntime(self):
|
def getRuntime(self):
|
||||||
"""
|
"""
|
||||||
Resume point of time and runtime/totaltime. Rounded to 6th decimal.
|
Resume point of time and runtime/totaltime in seconds, rounded to 6th
|
||||||
|
decimal.
|
||||||
Time from Plex server is measured in milliseconds.
|
Time from Plex server is measured in milliseconds.
|
||||||
Kodi: on the Plex side and in seconds on the Kodi side.
|
Kodi: seconds
|
||||||
|
|
||||||
|
Output:
|
||||||
|
resume, runtime as floats. 0.0 if not found
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
time_factor = 1.0 / 1000.0 # millisecond -> seconds
|
time_factor = 1.0 / 1000.0 # millisecond -> seconds
|
||||||
# xml
|
item = item[self.child].attrib
|
||||||
try:
|
try:
|
||||||
item = item[0].attrib
|
|
||||||
# json
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
runtime = float(item['duration'])
|
runtime = float(item['duration'])
|
||||||
|
except KeyError:
|
||||||
|
runtime = 0.0
|
||||||
try:
|
try:
|
||||||
resume = float(item['viewOffset'])
|
resume = float(item['viewOffset'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -1670,14 +1642,12 @@ class API():
|
||||||
return resume, runtime
|
return resume, runtime
|
||||||
|
|
||||||
def getMpaa(self):
|
def getMpaa(self):
|
||||||
|
"""
|
||||||
|
Get the content rating or None
|
||||||
|
"""
|
||||||
# Convert more complex cases
|
# Convert more complex cases
|
||||||
item = self.item
|
item = self.item
|
||||||
# xml
|
item = item[self.child].attrib
|
||||||
try:
|
|
||||||
item = item[0].attrib
|
|
||||||
# json
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
try:
|
||||||
mpaa = item['contentRating']
|
mpaa = item['contentRating']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -1693,18 +1663,17 @@ class API():
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
country = []
|
country = []
|
||||||
for child in item[0]:
|
for child in item[self.child]:
|
||||||
if child.tag == 'Country':
|
if child.tag == 'Country':
|
||||||
country.append(child.attrib['tag'])
|
country.append(child.attrib['tag'])
|
||||||
return country
|
return country
|
||||||
|
|
||||||
def getPremiereDate(self):
|
def getPremiereDate(self):
|
||||||
|
"""
|
||||||
|
Returns the "originallyAvailableAt" or None
|
||||||
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
try:
|
item = item[self.child].attrib
|
||||||
item = item[0].attrib
|
|
||||||
# json
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
try:
|
||||||
premiere = item['originallyAvailableAt']
|
premiere = item['originallyAvailableAt']
|
||||||
except:
|
except:
|
||||||
|
@ -1712,14 +1681,12 @@ class API():
|
||||||
return premiere
|
return premiere
|
||||||
|
|
||||||
def getStudios(self):
|
def getStudios(self):
|
||||||
|
"""
|
||||||
|
Returns a list with a single entry for the studio, or an empty list
|
||||||
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
studio = []
|
studio = []
|
||||||
# xml
|
item = item[self.child].attrib
|
||||||
try:
|
|
||||||
item = item[0].attrib
|
|
||||||
# json
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
try:
|
||||||
studio.append(self.getStudio(item['studio']))
|
studio.append(self.getStudio(item['studio']))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -1727,7 +1694,9 @@ class API():
|
||||||
return studio
|
return studio
|
||||||
|
|
||||||
def getStudio(self, studioName):
|
def getStudio(self, studioName):
|
||||||
# Convert studio for Kodi to properly detect them
|
"""
|
||||||
|
Convert studio for Kodi to properly detect them
|
||||||
|
"""
|
||||||
studios = {
|
studios = {
|
||||||
'abc (us)': "ABC",
|
'abc (us)': "ABC",
|
||||||
'fox (us)': "FOX",
|
'fox (us)': "FOX",
|
||||||
|
@ -1739,24 +1708,24 @@ class API():
|
||||||
|
|
||||||
def joinList(self, listobject):
|
def joinList(self, listobject):
|
||||||
"""
|
"""
|
||||||
Smart-joins the list into a single string using a " / " separator.
|
Smart-joins the listobject into a single string using a " / "
|
||||||
|
separator.
|
||||||
If the list is empty, smart_join returns an empty string.
|
If the list is empty, smart_join returns an empty string.
|
||||||
"""
|
"""
|
||||||
string = " / ".join(listobject)
|
string = " / ".join(listobject)
|
||||||
return string
|
return string
|
||||||
|
|
||||||
def getFilePath(self):
|
def getFilePath(self):
|
||||||
|
"""
|
||||||
|
returns the path to the Plex object, e.g. "/library/metadata/221803"
|
||||||
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
try:
|
item = item[self.child].attrib
|
||||||
item = item[0].attrib
|
|
||||||
# json
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
try:
|
try:
|
||||||
filepath = item['key']
|
filepath = item['key']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
filepath = ""
|
filepath = ""
|
||||||
|
# Plex: do we need this?
|
||||||
else:
|
else:
|
||||||
if "\\\\" in filepath:
|
if "\\\\" in filepath:
|
||||||
# append smb protocol
|
# append smb protocol
|
||||||
|
@ -1778,26 +1747,53 @@ class API():
|
||||||
return filepath
|
return filepath
|
||||||
|
|
||||||
def addPlexCredentialsToUrl(self, url, arguments={}):
|
def addPlexCredentialsToUrl(self, url, arguments={}):
|
||||||
|
"""
|
||||||
|
Takes an URL and optional arguments (also to be URL-encoded); returns
|
||||||
|
an extended URL with e.g. the Plex token included.
|
||||||
|
"""
|
||||||
token = {'X-Plex-Token': self.token}
|
token = {'X-Plex-Token': self.token}
|
||||||
xargs = PlexAPI().getXArgsDeviceInfo(options=token)
|
xargs = PlexAPI().getXArgsDeviceInfo(options=token)
|
||||||
xargs.update(arguments)
|
xargs.update(arguments)
|
||||||
url = "%s?%s" % (url, urlencode(xargs))
|
url = "%s?%s" % (url, urlencode(xargs))
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
def getBitrate(self):
|
||||||
|
"""
|
||||||
|
Returns the bitrate as an int or None
|
||||||
|
"""
|
||||||
|
item = self.item
|
||||||
|
try:
|
||||||
|
bitrate = item[self.child][0].attrib['bitrate']
|
||||||
|
bitrate = int(bitrate)
|
||||||
|
except KeyError:
|
||||||
|
bitrate = None
|
||||||
|
return bitrate
|
||||||
|
|
||||||
def getMediaStreams(self):
|
def getMediaStreams(self):
|
||||||
|
"""
|
||||||
|
Returns the media streams
|
||||||
|
|
||||||
|
Output: each track contains a dictionaries
|
||||||
|
{
|
||||||
|
'video': videotrack-list, 'videocodec', 'height', 'width',
|
||||||
|
'aspectratio', video3DFormat'
|
||||||
|
'audio': audiotrack-list, 'audiocodec', 'channels',
|
||||||
|
'audiolanguage'
|
||||||
|
'subtitle': list of subtitle languages (or "Unknown")
|
||||||
|
}
|
||||||
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
videotracks = []
|
videotracks = []
|
||||||
audiotracks = []
|
audiotracks = []
|
||||||
subtitlelanguages = []
|
subtitlelanguages = []
|
||||||
aspectratio = None
|
aspectratio = None
|
||||||
try:
|
try:
|
||||||
aspectratio = item[0][0].attrib['aspectRatio']
|
aspectratio = item[self.child][0].attrib['aspectRatio']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
# Loop over parts:
|
|
||||||
# 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 item[self.child][0]:
|
||||||
container = child.attrib['container'].lower()
|
container = child.attrib['container'].lower()
|
||||||
# Loop over Streams
|
# Loop over Streams
|
||||||
for grandchild in child:
|
for grandchild in child:
|
||||||
|
@ -1855,7 +1851,20 @@ class API():
|
||||||
return media
|
return media
|
||||||
|
|
||||||
def getAllArtwork(self, parentInfo=False):
|
def getAllArtwork(self, parentInfo=False):
|
||||||
|
"""
|
||||||
|
Gets the URLs to the Plex artwork, or empty string if not found.
|
||||||
|
|
||||||
|
Output:
|
||||||
|
{
|
||||||
|
'Primary': Plex key: "thumb". Only 1 pix
|
||||||
|
'Art':,
|
||||||
|
'Banner':,
|
||||||
|
'Logo':,
|
||||||
|
'Thumb':,
|
||||||
|
'Disc':,
|
||||||
|
'Backdrop': [] Plex key: "art". Only 1 pix
|
||||||
|
}
|
||||||
|
"""
|
||||||
server = self.server
|
server = self.server
|
||||||
item = self.item
|
item = self.item
|
||||||
|
|
||||||
|
@ -1870,7 +1879,6 @@ class API():
|
||||||
customquery += "&EnableImageEnhancers=false"
|
customquery += "&EnableImageEnhancers=false"
|
||||||
|
|
||||||
allartworks = {
|
allartworks = {
|
||||||
|
|
||||||
'Primary': "",
|
'Primary': "",
|
||||||
'Art': "",
|
'Art': "",
|
||||||
'Banner': "",
|
'Banner': "",
|
||||||
|
@ -1879,10 +1887,9 @@ class API():
|
||||||
'Disc': "",
|
'Disc': "",
|
||||||
'Backdrop': []
|
'Backdrop': []
|
||||||
}
|
}
|
||||||
|
|
||||||
# Process backdrops
|
# Process backdrops
|
||||||
# Get background artwork URL
|
# Get background artwork URL
|
||||||
item = item[0].attrib
|
item = item[self.child].attrib
|
||||||
try:
|
try:
|
||||||
background = item['art']
|
background = item['art']
|
||||||
background = "%s%s" % (server, background)
|
background = "%s%s" % (server, background)
|
||||||
|
@ -1953,13 +1960,15 @@ class API():
|
||||||
|
|
||||||
def getTranscodeVideoPath(self, action, quality={}, subtitle={}, audioboost=None, partIndex=None, options={}):
|
def getTranscodeVideoPath(self, action, quality={}, subtitle={}, audioboost=None, partIndex=None, options={}):
|
||||||
"""
|
"""
|
||||||
Transcode Video support
|
Transcode Video support; returns the URL to get a media started
|
||||||
|
|
||||||
Input:
|
Input:
|
||||||
action 'Transcode' OR any other string
|
action 'Transcode' OR any other string
|
||||||
quality: {'videoResolution': 'resolution',
|
quality: {
|
||||||
|
'videoResolution': 'resolution',
|
||||||
'videoQuality': 'quality',
|
'videoQuality': 'quality',
|
||||||
'maxVideoBitrate': 'bitrate'}
|
'maxVideoBitrate': 'bitrate'
|
||||||
|
}
|
||||||
subtitle {'selected', 'dontBurnIn', 'size'}
|
subtitle {'selected', 'dontBurnIn', 'size'}
|
||||||
audioboost Guess this takes an int as str
|
audioboost Guess this takes an int as str
|
||||||
partIndex No idea
|
partIndex No idea
|
||||||
|
@ -2029,7 +2038,6 @@ class API():
|
||||||
return transcodePath + urlencode(args) + '&' + urlencode(xargs)
|
return transcodePath + urlencode(args) + '&' + urlencode(xargs)
|
||||||
|
|
||||||
def adjustResume(self, resume_seconds):
|
def adjustResume(self, resume_seconds):
|
||||||
|
|
||||||
resume = 0
|
resume = 0
|
||||||
if resume_seconds:
|
if resume_seconds:
|
||||||
resume = round(float(resume_seconds), 6)
|
resume = round(float(resume_seconds), 6)
|
||||||
|
@ -2037,35 +2045,21 @@ class API():
|
||||||
if resume > jumpback:
|
if resume > jumpback:
|
||||||
# To avoid negative bookmark
|
# To avoid negative bookmark
|
||||||
resume = resume - jumpback
|
resume = resume - jumpback
|
||||||
|
|
||||||
return resume
|
return resume
|
||||||
|
|
||||||
def returnParts(self):
|
|
||||||
"""
|
|
||||||
TODO
|
|
||||||
"""
|
|
||||||
item = self.item
|
|
||||||
PartCount = 0
|
|
||||||
# Run through parts
|
|
||||||
for child in item[0][0]:
|
|
||||||
if child.tag == 'Part':
|
|
||||||
PartCount += PartCount
|
|
||||||
|
|
||||||
def externalSubs(self, playurl):
|
def externalSubs(self, playurl):
|
||||||
|
|
||||||
externalsubs = []
|
externalsubs = []
|
||||||
mapping = {}
|
mapping = {}
|
||||||
|
|
||||||
item = self.item
|
item = self.item
|
||||||
itemid = self.getKey()
|
itemid = self.getKey()
|
||||||
try:
|
try:
|
||||||
mediastreams = item[0][0][0]
|
mediastreams = item[self.child][0][0]
|
||||||
except (TypeError, KeyError, IndexError):
|
except (TypeError, KeyError, IndexError):
|
||||||
return
|
return
|
||||||
|
|
||||||
kodiindex = 0
|
kodiindex = 0
|
||||||
for stream in mediastreams:
|
for stream in mediastreams:
|
||||||
|
|
||||||
# index = stream['Index']
|
# index = stream['Index']
|
||||||
index = stream.attrib['id']
|
index = stream.attrib['id']
|
||||||
# Since Emby returns all possible tracks together, have to pull only external subtitles.
|
# Since Emby returns all possible tracks together, have to pull only external subtitles.
|
||||||
|
@ -2087,3 +2081,34 @@ class API():
|
||||||
utils.window('emby_%s.indexMapping' % playurl, value=mapping)
|
utils.window('emby_%s.indexMapping' % playurl, value=mapping)
|
||||||
|
|
||||||
return externalsubs
|
return externalsubs
|
||||||
|
|
||||||
|
def GetPlexPlaylist(self):
|
||||||
|
"""
|
||||||
|
Returns raw API metadata XML dump for a playlist with e.g. trailers.
|
||||||
|
"""
|
||||||
|
item = self.item
|
||||||
|
key = self.getKey()
|
||||||
|
uuid = item.attrib['librarySectionUUID']
|
||||||
|
mediatype = item[self.child].tag.lower()
|
||||||
|
trailerNumber = utils.settings('trailerNumber')
|
||||||
|
if not trailerNumber:
|
||||||
|
trailerNumber = '3'
|
||||||
|
url = "{server}/playQueues"
|
||||||
|
args = {
|
||||||
|
'type': mediatype,
|
||||||
|
'uri': 'library://' + uuid +
|
||||||
|
'/item/%2Flibrary%2Fmetadata%2F' + key,
|
||||||
|
'includeChapters': '1',
|
||||||
|
'extrasPrefixCount': trailerNumber,
|
||||||
|
'shuffle': '0',
|
||||||
|
'repeat': '0'
|
||||||
|
}
|
||||||
|
url = url + '?' + urlencode(args)
|
||||||
|
xml = downloadutils.DownloadUtils().downloadUrl(
|
||||||
|
url,
|
||||||
|
type="POST",
|
||||||
|
headerOptions={'Accept': 'application/xml'}
|
||||||
|
)
|
||||||
|
if not xml:
|
||||||
|
self.logMsg("Error retrieving metadata for %s" % url, 1)
|
||||||
|
return xml
|
||||||
|
|
|
@ -190,7 +190,6 @@ class DownloadUtils():
|
||||||
header = plx.getXArgsDeviceInfo(options=options, JSON=True)
|
header = plx.getXArgsDeviceInfo(options=options, JSON=True)
|
||||||
else:
|
else:
|
||||||
header = plx.getXArgsDeviceInfo(options=options, JSON=True)
|
header = plx.getXArgsDeviceInfo(options=options, JSON=True)
|
||||||
self.logMsg("Header: %s" % header, 2)
|
|
||||||
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={}):
|
||||||
|
|
|
@ -278,6 +278,10 @@ class Movies(Items):
|
||||||
# If the item doesn't exist, we'll add it to the database
|
# If the item doesn't exist, we'll add it to the database
|
||||||
update_item = True
|
update_item = True
|
||||||
itemid = API.getKey()
|
itemid = API.getKey()
|
||||||
|
# Cannot parse XML, abort
|
||||||
|
if not itemid:
|
||||||
|
self.logMsg("Cannot parse XML data for movie", -1)
|
||||||
|
return
|
||||||
emby_dbitem = emby_db.getItem_byId(itemid)
|
emby_dbitem = emby_db.getItem_byId(itemid)
|
||||||
try:
|
try:
|
||||||
movieid = emby_dbitem[0]
|
movieid = emby_dbitem[0]
|
||||||
|
|
|
@ -479,7 +479,7 @@ class LibrarySync(threading.Thread):
|
||||||
all_kodimoviesId = dict(emby_db.getChecksum('Movie'))
|
all_kodimoviesId = dict(emby_db.getChecksum('Movie'))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
all_kodimoviesId = {}
|
all_kodimoviesId = {}
|
||||||
all_plexmoviesIds = []
|
all_plexmoviesIds = {}
|
||||||
|
|
||||||
##### PROCESS MOVIES #####
|
##### PROCESS MOVIES #####
|
||||||
for view in views:
|
for view in views:
|
||||||
|
@ -510,7 +510,7 @@ class LibrarySync(threading.Thread):
|
||||||
plex_checksum = API.getChecksum()
|
plex_checksum = API.getChecksum()
|
||||||
itemid = API.getKey()
|
itemid = API.getKey()
|
||||||
kodi_checksum = all_kodimoviesId.get(itemid)
|
kodi_checksum = all_kodimoviesId.get(itemid)
|
||||||
all_plexmoviesIds.append(plex_checksum)
|
all_plexmoviesIds[itemid] = plex_checksum
|
||||||
if kodi_checksum != plex_checksum:
|
if kodi_checksum != plex_checksum:
|
||||||
# Only update if movie is not in Kodi or checksum is different
|
# Only update if movie is not in Kodi or checksum is different
|
||||||
updatelist.append(itemid)
|
updatelist.append(itemid)
|
||||||
|
@ -520,7 +520,7 @@ class LibrarySync(threading.Thread):
|
||||||
API = PlexAPI.API(plexmovie)
|
API = PlexAPI.API(plexmovie)
|
||||||
itemid = API.getKey()
|
itemid = API.getKey()
|
||||||
plex_checksum = API.getChecksum()
|
plex_checksum = API.getChecksum()
|
||||||
all_plexmoviesIds.append(plex_checksum)
|
all_plexmoviesIds[itemid] = plex_checksum
|
||||||
updatelist.append(itemid)
|
updatelist.append(itemid)
|
||||||
|
|
||||||
total = len(updatelist)
|
total = len(updatelist)
|
||||||
|
@ -532,23 +532,28 @@ class LibrarySync(threading.Thread):
|
||||||
# Process individual movies
|
# Process individual movies
|
||||||
if self.shouldStop():
|
if self.shouldStop():
|
||||||
return False
|
return False
|
||||||
|
# Download Metadata
|
||||||
plexmovie = plx.GetPlexMetadata(itemid)
|
plexmovie = plx.GetPlexMetadata(itemid)
|
||||||
|
# Check whether metadata is valid
|
||||||
title = plexmovie[0].attrib['title']
|
title = plexmovie[0].attrib['title']
|
||||||
if pdialog:
|
if pdialog:
|
||||||
percentage = int((float(count) / float(total))*100)
|
percentage = int((float(count) / float(total))*100)
|
||||||
pdialog.update(percentage, message=title)
|
pdialog.update(percentage, message=title)
|
||||||
count += 1
|
count += 1
|
||||||
# Download individual metadata
|
# Download individual metadata
|
||||||
|
self.logMsg("Start parsing metadata for movie: %s" % title, 0)
|
||||||
movies.add_update(plexmovie, viewName, viewId)
|
movies.add_update(plexmovie, viewName, viewId)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.logMsg("Movies finished.", 2)
|
self.logMsg("Movies finished.", 2)
|
||||||
|
|
||||||
##### PROCESS DELETES #####
|
##### PROCESS DELETES #####
|
||||||
|
self.logMsg("all_plexmoviesIds: %s" % all_plexmoviesIds, 0)
|
||||||
|
self.logMsg("all_kodimoviesId: %s" % all_kodimoviesId, 0)
|
||||||
if compare:
|
if compare:
|
||||||
# Manual sync, process deletes
|
# Manual sync, process deletes
|
||||||
for kodimovie, checksum in all_kodimoviesId.items():
|
for kodimovie in all_kodimoviesId:
|
||||||
if checksum not in all_plexmoviesIds:
|
if kodimovie not in all_plexmoviesIds:
|
||||||
movies.remove(kodimovie)
|
movies.remove(kodimovie)
|
||||||
else:
|
else:
|
||||||
self.logMsg("Movies compare finished.", 1)
|
self.logMsg("Movies compare finished.", 1)
|
||||||
|
|
|
@ -110,37 +110,41 @@ class PlaybackUtils():
|
||||||
|
|
||||||
############### -- CHECK FOR INTROS ################
|
############### -- CHECK FOR INTROS ################
|
||||||
# PLEX: todo. Seems like Plex returns a playlist WITH trailers
|
# PLEX: todo. Seems like Plex returns a playlist WITH trailers
|
||||||
# if utils.settings('enableCinema') == "true" and not seektime:
|
if utils.settings('enableCinema') == "true" and not seektime:
|
||||||
if False:
|
|
||||||
# if we have any play them when the movie/show is not being resumed
|
# if we have any play them when the movie/show is not being resumed
|
||||||
url = "{server}/emby/Users/{UserId}/Items/%s/Intros?format=json" % itemid
|
# Download XML playlist associated with the picked movie
|
||||||
intros = doUtils.downloadUrl(url)
|
self.item = API.GetPlexPlaylist()
|
||||||
|
item = self.item
|
||||||
if intros['TotalRecordCount'] != 0:
|
# And overwrite instances with new item
|
||||||
|
self.API = PlexAPI.API(item)
|
||||||
|
API = self.API
|
||||||
|
playutils = putils.PlayUtils(item)
|
||||||
|
playListSize = int(self.item.attrib['size'])
|
||||||
|
if playListSize > 1:
|
||||||
getTrailers = True
|
getTrailers = True
|
||||||
|
|
||||||
if utils.settings('askCinema') == "true":
|
if utils.settings('askCinema') == "true":
|
||||||
resp = xbmcgui.Dialog().yesno("Emby Cinema Mode", "Play trailers?")
|
resp = xbmcgui.Dialog().yesno("Emby Cinema Mode", "Play trailers?")
|
||||||
if not resp:
|
if not resp:
|
||||||
# User selected to not play trailers
|
# User selected to not play trailers
|
||||||
getTrailers = False
|
getTrailers = False
|
||||||
self.logMsg("Skip trailers.", 1)
|
self.logMsg("Skip trailers.", 1)
|
||||||
|
|
||||||
if getTrailers:
|
if getTrailers:
|
||||||
for intro in intros['Items']:
|
for i in range(0, playListSize):
|
||||||
# The server randomly returns intros, process them.
|
# The server randomly returns intros, process them
|
||||||
|
# Set the child in XML Plex response to a trailer
|
||||||
|
API.setChildNumber(i)
|
||||||
introListItem = xbmcgui.ListItem()
|
introListItem = xbmcgui.ListItem()
|
||||||
introPlayurl = putils.PlayUtils(intro).getPlayUrl()
|
introPlayurl = playutils.getPlayUrl()
|
||||||
self.logMsg("Adding Intro: %s" % introPlayurl, 1)
|
self.logMsg("Adding Trailer: %s" % introPlayurl, 1)
|
||||||
|
|
||||||
# Set listitem and properties for intros
|
# Set listitem and properties for intros
|
||||||
pbutils = PlaybackUtils(intro)
|
self.setProperties(introPlayurl, introListItem)
|
||||||
pbutils.setProperties(introPlayurl, introListItem)
|
|
||||||
|
|
||||||
self.pl.insertintoPlaylist(currentPosition, url=introPlayurl)
|
self.pl.insertintoPlaylist(currentPosition, url=introPlayurl)
|
||||||
introsPlaylist = True
|
introsPlaylist = True
|
||||||
currentPosition += 1
|
currentPosition += 1
|
||||||
|
self.logMsg("Successfally added trailer number %s" % i, 1)
|
||||||
|
# Set "working point" to the movie (last one in playlist)
|
||||||
|
API.setChildNumber(playListSize - 1)
|
||||||
|
|
||||||
############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ###############
|
############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ###############
|
||||||
|
|
||||||
|
@ -224,7 +228,7 @@ class PlaybackUtils():
|
||||||
# itemid = item['Id']
|
# itemid = item['Id']
|
||||||
itemid = self.API.getKey()
|
itemid = self.API.getKey()
|
||||||
# itemtype = item['Type']
|
# itemtype = item['Type']
|
||||||
itemtype = item[0].attrib['type']
|
itemtype = self.API.getType()
|
||||||
resume, runtime = self.API.getRuntime()
|
resume, runtime = self.API.getRuntime()
|
||||||
|
|
||||||
embyitem = "emby_%s" % playurl
|
embyitem = "emby_%s" % playurl
|
||||||
|
|
|
@ -28,7 +28,7 @@ class PlayUtils():
|
||||||
self.server = utils.window('emby_server%s' % self.userid)
|
self.server = utils.window('emby_server%s' % self.userid)
|
||||||
self.machineIdentifier = utils.window('plex_machineIdentifier')
|
self.machineIdentifier = utils.window('plex_machineIdentifier')
|
||||||
|
|
||||||
self.plx = PlexAPI.API(item)
|
self.API = PlexAPI.API(item)
|
||||||
|
|
||||||
def logMsg(self, msg, lvl=1):
|
def logMsg(self, msg, lvl=1):
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ class PlayUtils():
|
||||||
if self.isDirectStream():
|
if self.isDirectStream():
|
||||||
|
|
||||||
self.logMsg("File is direct streaming.", 1)
|
self.logMsg("File is direct streaming.", 1)
|
||||||
playurl = self.plx.getTranscodeVideoPath('direct')
|
playurl = self.API.getTranscodeVideoPath('direct')
|
||||||
# Set playmethod property
|
# Set playmethod property
|
||||||
utils.window('emby_%s.playmethod' % playurl, value="DirectStream")
|
utils.window('emby_%s.playmethod' % playurl, value="DirectStream")
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ class PlayUtils():
|
||||||
quality = {
|
quality = {
|
||||||
'bitrate': self.getBitrate()
|
'bitrate': self.getBitrate()
|
||||||
}
|
}
|
||||||
playurl = self.plx.getTranscodeVideoPath(
|
playurl = self.API.getTranscodeVideoPath(
|
||||||
'Transcode', quality=quality
|
'Transcode', quality=quality
|
||||||
)
|
)
|
||||||
# Set playmethod property
|
# Set playmethod property
|
||||||
|
@ -225,11 +225,10 @@ class PlayUtils():
|
||||||
|
|
||||||
def directStream(self):
|
def directStream(self):
|
||||||
|
|
||||||
item = self.item
|
|
||||||
server = self.server
|
server = self.server
|
||||||
|
|
||||||
itemid = self.plx.getKey()
|
itemid = self.API.getKey()
|
||||||
type = item[0].tag
|
type = self.API.getType()
|
||||||
|
|
||||||
# if 'Path' in item and item['Path'].endswith('.strm'):
|
# if 'Path' in item and item['Path'].endswith('.strm'):
|
||||||
# # Allow strm loading when direct streaming
|
# # Allow strm loading when direct streaming
|
||||||
|
@ -239,24 +238,22 @@ class PlayUtils():
|
||||||
else:
|
else:
|
||||||
playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
|
playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
|
||||||
playurl = "{server}/player/playback/playMedia?key=%2Flibrary%2Fmetadata%2F%s&offset=0&X-Plex-Client-Identifier={clientId}&machineIdentifier={SERVER ID}&address={SERVER IP}&port={SERVER PORT}&protocol=http&path=http%3A%2F%2F{SERVER IP}%3A{SERVER PORT}%2Flibrary%2Fmetadata%2F{MEDIA ID}" % (itemid)
|
playurl = "{server}/player/playback/playMedia?key=%2Flibrary%2Fmetadata%2F%s&offset=0&X-Plex-Client-Identifier={clientId}&machineIdentifier={SERVER ID}&address={SERVER IP}&port={SERVER PORT}&protocol=http&path=http%3A%2F%2F{SERVER IP}%3A{SERVER PORT}%2Flibrary%2Fmetadata%2F{MEDIA ID}" % (itemid)
|
||||||
playurl = self.plx.replaceURLtags()
|
playurl = self.API.replaceURLtags()
|
||||||
|
|
||||||
return playurl
|
return playurl
|
||||||
|
|
||||||
def isNetworkSufficient(self):
|
def isNetworkSufficient(self):
|
||||||
|
|
||||||
settings = self.getBitrate()*1000
|
settings = self.getBitrate()
|
||||||
|
|
||||||
try:
|
sourceBitrate = self.API.getBitrate()
|
||||||
sourceBitrate = int(self.item[0][0].attrib['bitrate'])
|
if not sourceBitrate:
|
||||||
except (KeyError, TypeError):
|
self.logMsg("Bitrate value is missing.", 0)
|
||||||
self.logMsg("Bitrate value is missing.", 1)
|
return True
|
||||||
else:
|
|
||||||
self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s"
|
self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s"
|
||||||
% (settings, sourceBitrate), 1)
|
% (settings, sourceBitrate), 1)
|
||||||
if settings < sourceBitrate:
|
if settings < sourceBitrate:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def isTranscoding(self):
|
def isTranscoding(self):
|
||||||
|
|
Loading…
Reference in a new issue