Overhaul Part 1
This commit is contained in:
parent
99895ec49f
commit
8912a0b601
16 changed files with 719 additions and 465 deletions
|
@ -91,8 +91,7 @@ class Main:
|
||||||
folderid = params['folderid'][0]
|
folderid = params['folderid'][0]
|
||||||
modes[mode](itemid, folderid)
|
modes[mode](itemid, folderid)
|
||||||
elif mode == "companion":
|
elif mode == "companion":
|
||||||
resume = params.get('resume', '')[0]
|
modes[mode](itemid, params=sys.argv[2])
|
||||||
modes[mode](itemid, resume=resume)
|
|
||||||
else:
|
else:
|
||||||
modes[mode]()
|
modes[mode]()
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -55,6 +55,8 @@ import re
|
||||||
import json
|
import json
|
||||||
from urllib import urlencode, quote_plus
|
from urllib import urlencode, quote_plus
|
||||||
|
|
||||||
|
from PlexFunctions import PlexToKodiTimefactor
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import xml.etree.cElementTree as etree
|
import xml.etree.cElementTree as etree
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -198,9 +200,9 @@ class PlexAPI():
|
||||||
'avatar': avatar,
|
'avatar': avatar,
|
||||||
'token': token
|
'token': token
|
||||||
}
|
}
|
||||||
utils.settings('plexLogin', value=username)
|
utils.settings('plexLogin', username)
|
||||||
utils.settings('plexToken', value=token)
|
utils.settings('plexToken', token)
|
||||||
utils.settings('plexhome', value=home)
|
utils.settings('plexhome', home)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def CheckPlexTvSignin(self, identifier):
|
def CheckPlexTvSignin(self, identifier):
|
||||||
|
@ -339,8 +341,8 @@ class PlexAPI():
|
||||||
verify=sslverify,
|
verify=sslverify,
|
||||||
timeout=timeout)
|
timeout=timeout)
|
||||||
except requests.exceptions.ConnectionError as e:
|
except requests.exceptions.ConnectionError as e:
|
||||||
self.logMsg("Server is offline or cannot be reached. Url: %s."
|
self.logMsg("Server is offline or cannot be reached. Url: %s "
|
||||||
"Header: %s. Error message: %s"
|
"Header: %s Error message: %s"
|
||||||
% (url, header, e), -1)
|
% (url, header, e), -1)
|
||||||
return False
|
return False
|
||||||
except requests.exceptions.ReadTimeout:
|
except requests.exceptions.ReadTimeout:
|
||||||
|
@ -781,8 +783,8 @@ class PlexAPI():
|
||||||
"""
|
"""
|
||||||
# Get addon infos
|
# Get addon infos
|
||||||
xargs = {
|
xargs = {
|
||||||
"Content-type": "application/x-www-form-urlencoded",
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
"Access-Control-Allow-Origin": "*",
|
# "Access-Control-Allow-Origin": "*",
|
||||||
'X-Plex-Language': 'en',
|
'X-Plex-Language': 'en',
|
||||||
'X-Plex-Device': self.addonName,
|
'X-Plex-Device': self.addonName,
|
||||||
'X-Plex-Client-Platform': self.platform,
|
'X-Plex-Client-Platform': self.platform,
|
||||||
|
@ -1371,8 +1373,6 @@ class API():
|
||||||
|
|
||||||
def __init__(self, item):
|
def __init__(self, item):
|
||||||
self.item = item
|
self.item = item
|
||||||
# which child in the XML response shall we look at?
|
|
||||||
self.child = 0
|
|
||||||
# which media part in the XML response shall we look at?
|
# which media part in the XML response shall we look at?
|
||||||
self.part = 0
|
self.part = 0
|
||||||
self.clientinfo = clientinfo.ClientInfo()
|
self.clientinfo = clientinfo.ClientInfo()
|
||||||
|
@ -1383,18 +1383,6 @@ class API():
|
||||||
|
|
||||||
self.jumpback = int(utils.settings('resumeJumpBack'))
|
self.jumpback = int(utils.settings('resumeJumpBack'))
|
||||||
|
|
||||||
def setChildNumber(self, number=0):
|
|
||||||
"""
|
|
||||||
Which child in the XML response shall we look at and work with?
|
|
||||||
"""
|
|
||||||
self.child = int(number)
|
|
||||||
|
|
||||||
def getChildNumber(self):
|
|
||||||
"""
|
|
||||||
Returns the child in the XML response that we're currently looking at
|
|
||||||
"""
|
|
||||||
return self.child
|
|
||||||
|
|
||||||
def setPartNumber(self, number=0):
|
def setPartNumber(self, number=0):
|
||||||
"""
|
"""
|
||||||
Sets the part number to work with (used to deal with Movie with several
|
Sets the part number to work with (used to deal with Movie with several
|
||||||
|
@ -1435,12 +1423,9 @@ class API():
|
||||||
|
|
||||||
def getType(self):
|
def getType(self):
|
||||||
"""
|
"""
|
||||||
Returns the type of media, e.g. 'movie'
|
Returns the type of media, e.g. 'movie' or 'clip' for trailers
|
||||||
"""
|
"""
|
||||||
item = self.item
|
return self.item['type']
|
||||||
item = item[self.child].attrib
|
|
||||||
itemtype = item['type']
|
|
||||||
return itemtype
|
|
||||||
|
|
||||||
def getChecksum(self):
|
def getChecksum(self):
|
||||||
"""
|
"""
|
||||||
|
@ -1450,47 +1435,58 @@ class API():
|
||||||
item = self.item
|
item = self.item
|
||||||
# XML
|
# XML
|
||||||
try:
|
try:
|
||||||
item = item[self.child].attrib
|
item = item[0].attrib
|
||||||
# JSON
|
# JSON
|
||||||
except KeyError:
|
except (AttributeError, KeyError):
|
||||||
pass
|
pass
|
||||||
# Include a letter to prohibit saving as an int!
|
# Include a letter to prohibit saving as an int!
|
||||||
checksum = "K%s%s" % (self.getKey(),
|
checksum = "K%s%s" % (self.getRatingKey(),
|
||||||
item.get('updatedAt', ''))
|
item.get('updatedAt', ''))
|
||||||
return checksum
|
return checksum
|
||||||
|
|
||||||
def getKey(self):
|
def getRatingKey(self):
|
||||||
"""
|
"""
|
||||||
Can be used on both XML and JSON
|
Can be used on both XML and JSON
|
||||||
Returns the Plex unique movie id as a str, not int
|
Returns the Plex key such as '246922' as a string
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
# XML
|
# XML
|
||||||
try:
|
try:
|
||||||
item = item[self.child].attrib
|
item = item[0].attrib
|
||||||
# JSON
|
# JSON
|
||||||
except KeyError:
|
except (AttributeError, KeyError):
|
||||||
pass
|
pass
|
||||||
key = item['ratingKey']
|
key = item['ratingKey']
|
||||||
return str(key)
|
return str(key)
|
||||||
|
|
||||||
|
def getKey(self):
|
||||||
|
"""
|
||||||
|
Can be used on both XML and JSON
|
||||||
|
Returns the Plex key such as '/library/metadata/246922'
|
||||||
|
"""
|
||||||
|
item = self.item
|
||||||
|
# XML
|
||||||
|
try:
|
||||||
|
item = item[0].attrib
|
||||||
|
# JSON
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
pass
|
||||||
|
key = item['key']
|
||||||
|
return str(key)
|
||||||
|
|
||||||
def getIndex(self):
|
def getIndex(self):
|
||||||
"""
|
"""
|
||||||
Returns the 'index' of an PMS XML reply. Depicts e.g. season number.
|
Returns the 'index' of an PMS XML reply. Depicts e.g. season number.
|
||||||
"""
|
"""
|
||||||
item = self.item[self.child].attrib
|
item = self.item[0].attrib
|
||||||
index = item['index']
|
index = item['index']
|
||||||
return str(index)
|
return str(index)
|
||||||
|
|
||||||
def getDateCreated(self):
|
def getDateCreated(self):
|
||||||
"""
|
"""
|
||||||
Returns the date when this library item was created
|
Returns the date when this library item was created
|
||||||
|
|
||||||
Input:
|
|
||||||
index child number as int; normally =0
|
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item[0].attrib
|
||||||
item = item[self.child].attrib
|
|
||||||
dateadded = item['addedAt']
|
dateadded = item['addedAt']
|
||||||
dateadded = self.convert_date(dateadded)
|
dateadded = self.convert_date(dateadded)
|
||||||
return dateadded
|
return dateadded
|
||||||
|
@ -1517,7 +1513,13 @@ class API():
|
||||||
resume = 0
|
resume = 0
|
||||||
rating = 0
|
rating = 0
|
||||||
|
|
||||||
item = item[self.child].attrib
|
# XML
|
||||||
|
try:
|
||||||
|
item = item[0].attrib
|
||||||
|
# JSON
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
playcount = int(item['viewCount'])
|
playcount = int(item['viewCount'])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -1558,7 +1560,7 @@ class API():
|
||||||
writer = []
|
writer = []
|
||||||
cast = []
|
cast = []
|
||||||
producer = []
|
producer = []
|
||||||
for child in item[self.child]:
|
for child in item[0]:
|
||||||
if child.tag == 'Director':
|
if child.tag == 'Director':
|
||||||
director.append(child.attrib['tag'])
|
director.append(child.attrib['tag'])
|
||||||
elif child.tag == 'Writer':
|
elif child.tag == 'Writer':
|
||||||
|
@ -1594,7 +1596,7 @@ class API():
|
||||||
'Role': 'Actor',
|
'Role': 'Actor',
|
||||||
'Producer': 'Producer'
|
'Producer': 'Producer'
|
||||||
}
|
}
|
||||||
for child in item[self.child]:
|
for child in item[0]:
|
||||||
if child.tag in people_of_interest.keys():
|
if child.tag in people_of_interest.keys():
|
||||||
name = child.attrib['tag']
|
name = child.attrib['tag']
|
||||||
name_id = child.attrib['id']
|
name_id = child.attrib['id']
|
||||||
|
@ -1626,7 +1628,7 @@ class API():
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
genre = []
|
genre = []
|
||||||
for child in item[self.child]:
|
for child in item[0]:
|
||||||
if child.tag == 'Genre':
|
if child.tag == 'Genre':
|
||||||
genre.append(child.attrib['tag'])
|
genre.append(child.attrib['tag'])
|
||||||
return genre
|
return genre
|
||||||
|
@ -1638,7 +1640,7 @@ class API():
|
||||||
Return IMDB, e.g. "imdb://tt0903624?lang=en". Returns None if not found
|
Return IMDB, e.g. "imdb://tt0903624?lang=en". Returns None if not found
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
item = item[self.child].attrib
|
item = item[0].attrib
|
||||||
try:
|
try:
|
||||||
item = item['guid']
|
item = item['guid']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -1663,12 +1665,14 @@ class API():
|
||||||
sorttitle = title, if no sorttitle is found
|
sorttitle = title, if no sorttitle is found
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
|
|
||||||
# XML
|
# XML
|
||||||
try:
|
try:
|
||||||
item = item[self.child].attrib
|
item = item[0].attrib
|
||||||
# JSON
|
# JSON
|
||||||
except KeyError:
|
except (AttributeError, KeyError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
title = item['title']
|
title = item['title']
|
||||||
except:
|
except:
|
||||||
|
@ -1684,7 +1688,7 @@ class API():
|
||||||
Returns the plot or None.
|
Returns the plot or None.
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
item = item[self.child].attrib
|
item = item[0].attrib
|
||||||
try:
|
try:
|
||||||
plot = item['summary']
|
plot = item['summary']
|
||||||
except:
|
except:
|
||||||
|
@ -1696,7 +1700,7 @@ class API():
|
||||||
Returns a shorter tagline or None
|
Returns a shorter tagline or None
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
item = item[self.child].attrib
|
item = item[0].attrib
|
||||||
try:
|
try:
|
||||||
tagline = item['tagline']
|
tagline = item['tagline']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -1708,7 +1712,7 @@ class API():
|
||||||
Returns the audience rating or None
|
Returns the audience rating or None
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
item = item[self.child].attrib
|
item = item[0].attrib
|
||||||
try:
|
try:
|
||||||
rating = item['audienceRating']
|
rating = item['audienceRating']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -1720,7 +1724,7 @@ class API():
|
||||||
Returns the production(?) year ("year") or None
|
Returns the production(?) year ("year") or None
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
item = item[self.child].attrib
|
item = item[0].attrib
|
||||||
try:
|
try:
|
||||||
year = item['year']
|
year = item['year']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -1737,9 +1741,14 @@ class API():
|
||||||
Output:
|
Output:
|
||||||
resume, runtime as floats. 0.0 if not found
|
resume, runtime as floats. 0.0 if not found
|
||||||
"""
|
"""
|
||||||
time_factor = 1.0 / 1000.0 # millisecond -> seconds
|
time_factor = PlexToKodiTimefactor()
|
||||||
item = self.item
|
|
||||||
item = item[self.child].attrib
|
# XML
|
||||||
|
try:
|
||||||
|
item = self.item[0].attrib
|
||||||
|
# JSON
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
runtime = float(item['duration'])
|
runtime = float(item['duration'])
|
||||||
|
@ -1769,7 +1778,7 @@ class API():
|
||||||
"""
|
"""
|
||||||
# Convert more complex cases
|
# Convert more complex cases
|
||||||
item = self.item
|
item = self.item
|
||||||
item = item[self.child].attrib
|
item = item[0].attrib
|
||||||
try:
|
try:
|
||||||
mpaa = item['contentRating']
|
mpaa = item['contentRating']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -1785,7 +1794,7 @@ class API():
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
country = []
|
country = []
|
||||||
for child in item[self.child]:
|
for child in item[0]:
|
||||||
if child.tag == 'Country':
|
if child.tag == 'Country':
|
||||||
country.append(child.attrib['tag'])
|
country.append(child.attrib['tag'])
|
||||||
return country
|
return country
|
||||||
|
@ -1795,7 +1804,7 @@ class API():
|
||||||
Returns the "originallyAvailableAt" or None
|
Returns the "originallyAvailableAt" or None
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
item = item[self.child].attrib
|
item = item[0].attrib
|
||||||
try:
|
try:
|
||||||
premiere = item['originallyAvailableAt']
|
premiere = item['originallyAvailableAt']
|
||||||
except:
|
except:
|
||||||
|
@ -1808,7 +1817,7 @@ class API():
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
studio = []
|
studio = []
|
||||||
item = item[self.child].attrib
|
item = item[0].attrib
|
||||||
try:
|
try:
|
||||||
studio.append(self.getStudio(item['studio']))
|
studio.append(self.getStudio(item['studio']))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -1849,67 +1858,36 @@ class API():
|
||||||
Episode number, Plex: 'index'
|
Episode number, Plex: 'index'
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
item = self.item[self.child].attrib
|
item = self.item[0].attrib
|
||||||
key = item['grandparentRatingKey']
|
key = item['grandparentRatingKey']
|
||||||
title = item['grandparentTitle']
|
title = item['grandparentTitle']
|
||||||
season = item['parentIndex']
|
season = item['parentIndex']
|
||||||
episode = item['index']
|
episode = item['index']
|
||||||
return key, title, season, episode
|
return str(key), title, str(season), str(episode)
|
||||||
|
|
||||||
def getFilePath(self):
|
|
||||||
"""
|
|
||||||
returns the path to the Plex object, e.g. "/library/metadata/221803"
|
|
||||||
"""
|
|
||||||
item = self.item
|
|
||||||
item = item[self.child].attrib
|
|
||||||
try:
|
|
||||||
filepath = item['key']
|
|
||||||
except KeyError:
|
|
||||||
filepath = ""
|
|
||||||
# Plex: do we need this?
|
|
||||||
else:
|
|
||||||
if "\\\\" in filepath:
|
|
||||||
# append smb protocol
|
|
||||||
filepath = filepath.replace("\\\\", "smb://")
|
|
||||||
filepath = filepath.replace("\\", "/")
|
|
||||||
|
|
||||||
if item.get('VideoType'):
|
|
||||||
videotype = item['VideoType']
|
|
||||||
# Specific format modification
|
|
||||||
if 'Dvd'in videotype:
|
|
||||||
filepath = "%s/VIDEO_TS/VIDEO_TS.IFO" % filepath
|
|
||||||
elif 'Bluray' in videotype:
|
|
||||||
filepath = "%s/BDMV/index.bdmv" % filepath
|
|
||||||
|
|
||||||
if "\\" in filepath:
|
|
||||||
# Local path scenario, with special videotype
|
|
||||||
filepath = filepath.replace("/", "\\")
|
|
||||||
|
|
||||||
return filepath
|
|
||||||
|
|
||||||
def addPlexCredentialsToUrl(self, url, arguments={}):
|
def addPlexCredentialsToUrl(self, url, arguments={}):
|
||||||
"""
|
"""
|
||||||
Takes an URL and optional arguments (also to be URL-encoded); returns
|
Takes an URL and optional arguments (also to be URL-encoded); returns
|
||||||
an extended URL with e.g. the Plex token included.
|
an extended URL with e.g. the Plex token included.
|
||||||
|
|
||||||
|
arguments overrule everything
|
||||||
"""
|
"""
|
||||||
token = {'X-Plex-Token': self.token}
|
token = {'X-Plex-Token': self.token}
|
||||||
xargs = PlexAPI().getXArgsDeviceInfo(options=token)
|
xargs = PlexAPI().getXArgsDeviceInfo(options=token)
|
||||||
xargs.update(arguments)
|
xargs.update(arguments)
|
||||||
url = "%s?%s" % (url, urlencode(xargs))
|
if '?' not in url:
|
||||||
|
url = "%s?%s" % (url, urlencode(xargs))
|
||||||
|
else:
|
||||||
|
url = "%s&%s" % (url, urlencode(xargs))
|
||||||
return url
|
return url
|
||||||
|
|
||||||
def getBitrate(self):
|
def GetPlayQueueItemID(self):
|
||||||
"""
|
"""
|
||||||
Returns the bitrate as an int. The Part bitrate is returned; if not
|
Returns current playQueueItemID for the item.
|
||||||
available in the Plex XML, the Media bitrate is returned
|
|
||||||
|
If not found, empty str is returned
|
||||||
"""
|
"""
|
||||||
item = self.item
|
return self.item.get('playQueueItemID')
|
||||||
try:
|
|
||||||
bitrate = item[self.child][0][self.part].attrib['bitrate']
|
|
||||||
except KeyError:
|
|
||||||
bitrate = item[self.child][0].attrib['bitrate']
|
|
||||||
bitrate = int(bitrate)
|
|
||||||
return bitrate
|
|
||||||
|
|
||||||
def getDataFromPartOrMedia(self, key):
|
def getDataFromPartOrMedia(self, key):
|
||||||
"""
|
"""
|
||||||
|
@ -1918,8 +1896,8 @@ class API():
|
||||||
|
|
||||||
If all fails, None is returned.
|
If all fails, None is returned.
|
||||||
"""
|
"""
|
||||||
media = self.item[self.child][0].attrib
|
media = self.item['_children'][0]
|
||||||
part = self.item[self.child][0][self.part].attrib
|
part = media['_children'][self.part]
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
value = part[key]
|
value = part[key]
|
||||||
|
@ -2025,12 +2003,12 @@ class API():
|
||||||
subtitlelanguages = []
|
subtitlelanguages = []
|
||||||
aspectratio = None
|
aspectratio = None
|
||||||
try:
|
try:
|
||||||
aspectratio = item[self.child][0].attrib['aspectRatio']
|
aspectratio = item[0][0].attrib['aspectRatio']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
# TODO: what if several Media tags exist?!?
|
# TODO: what if several Media tags exist?!?
|
||||||
# Loop over parts
|
# Loop over parts
|
||||||
for child in item[self.child][0]:
|
for child in item[0][0]:
|
||||||
container = child.attrib['container'].lower()
|
container = child.attrib['container'].lower()
|
||||||
# Loop over Streams
|
# Loop over Streams
|
||||||
for grandchild in child:
|
for grandchild in child:
|
||||||
|
@ -2105,6 +2083,13 @@ class API():
|
||||||
server = self.server
|
server = self.server
|
||||||
item = self.item
|
item = self.item
|
||||||
|
|
||||||
|
# XML
|
||||||
|
try:
|
||||||
|
item = item[0].attrib
|
||||||
|
# JSON
|
||||||
|
except (AttributeError, KeyError):
|
||||||
|
pass
|
||||||
|
|
||||||
maxHeight = 10000
|
maxHeight = 10000
|
||||||
maxWidth = 10000
|
maxWidth = 10000
|
||||||
customquery = ""
|
customquery = ""
|
||||||
|
@ -2126,7 +2111,6 @@ class API():
|
||||||
}
|
}
|
||||||
# Process backdrops
|
# Process backdrops
|
||||||
# Get background artwork URL
|
# Get background artwork URL
|
||||||
item = item[self.child].attrib
|
|
||||||
try:
|
try:
|
||||||
background = item['art']
|
background = item['art']
|
||||||
background = "%s%s" % (server, background)
|
background = "%s%s" % (server, background)
|
||||||
|
@ -2259,7 +2243,7 @@ class API():
|
||||||
xargs = PlexAPI().getXArgsDeviceInfo(options=options)
|
xargs = PlexAPI().getXArgsDeviceInfo(options=options)
|
||||||
# For Direct Playing
|
# For Direct Playing
|
||||||
if action == "DirectPlay":
|
if action == "DirectPlay":
|
||||||
path = self.item[self.child][0][self.part].attrib['key']
|
path = self.item['_children'][0]['_children'][self.partNumber]['key']
|
||||||
transcodePath = self.server + path
|
transcodePath = self.server + path
|
||||||
# Be sure to have exactly ONE '?' in the path (might already have
|
# Be sure to have exactly ONE '?' in the path (might already have
|
||||||
# been returned, e.g. trailers!)
|
# been returned, e.g. trailers!)
|
||||||
|
@ -2273,15 +2257,7 @@ class API():
|
||||||
# For Direct Streaming or Transcoding
|
# For Direct Streaming or Transcoding
|
||||||
transcodePath = self.server + \
|
transcodePath = self.server + \
|
||||||
'/video/:/transcode/universal/start.m3u8?'
|
'/video/:/transcode/universal/start.m3u8?'
|
||||||
partCount = 0
|
path = self.getDataFromPartOrMedia('key')
|
||||||
for parts in self.item[self.child][0]:
|
|
||||||
partCount = partCount + 1
|
|
||||||
# Movie consists of several parts; grap one part
|
|
||||||
if partCount > 1:
|
|
||||||
path = self.item[self.child][0][self.part].attrib['key']
|
|
||||||
# Movie consists of only one part
|
|
||||||
else:
|
|
||||||
path = self.item[self.child].attrib['key']
|
|
||||||
args = {
|
args = {
|
||||||
'path': path,
|
'path': path,
|
||||||
'mediaIndex': 0, # Probably refering to XML reply sheme
|
'mediaIndex': 0, # Probably refering to XML reply sheme
|
||||||
|
@ -2337,9 +2313,9 @@ class API():
|
||||||
mapping = {}
|
mapping = {}
|
||||||
|
|
||||||
item = self.item
|
item = self.item
|
||||||
itemid = self.getKey()
|
itemid = self.getRatingKey()
|
||||||
try:
|
try:
|
||||||
mediastreams = item[self.child][0][0]
|
mediastreams = item[0][0][0]
|
||||||
except (TypeError, KeyError, IndexError):
|
except (TypeError, KeyError, IndexError):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -2372,14 +2348,14 @@ class API():
|
||||||
Returns raw API metadata XML dump for a playlist with e.g. trailers.
|
Returns raw API metadata XML dump for a playlist with e.g. trailers.
|
||||||
"""
|
"""
|
||||||
item = self.item
|
item = self.item
|
||||||
key = self.getKey()
|
key = self.getRatingKey()
|
||||||
try:
|
try:
|
||||||
uuid = item.attrib['librarySectionUUID']
|
uuid = item.attrib['librarySectionUUID']
|
||||||
# if not found: probably trying to start a trailer directly
|
# if not found: probably trying to start a trailer directly
|
||||||
# Hence no playlist needed
|
# Hence no playlist needed
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
mediatype = item[self.child].tag.lower()
|
mediatype = item[0].tag.lower()
|
||||||
trailerNumber = utils.settings('trailerNumber')
|
trailerNumber = utils.settings('trailerNumber')
|
||||||
if not trailerNumber:
|
if not trailerNumber:
|
||||||
trailerNumber = '3'
|
trailerNumber = '3'
|
||||||
|
@ -2407,4 +2383,4 @@ class API():
|
||||||
"""
|
"""
|
||||||
Returns the parts of the specified video child in the XML response
|
Returns the parts of the specified video child in the XML response
|
||||||
"""
|
"""
|
||||||
return self.item[self.child][0]
|
return self.item[0][0]
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from urllib import urlencode
|
from urllib import urlencode
|
||||||
|
from ast import literal_eval
|
||||||
|
from urlparse import urlparse, parse_qs
|
||||||
|
import re
|
||||||
|
|
||||||
from xbmcaddon import Addon
|
from xbmcaddon import Addon
|
||||||
|
|
||||||
from downloadutils import DownloadUtils
|
import downloadutils
|
||||||
from utils import logMsg
|
from utils import logMsg
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,6 +14,13 @@ addonName = Addon().getAddonInfo('name')
|
||||||
title = "%s %s" % (addonName, __name__)
|
title = "%s %s" % (addonName, __name__)
|
||||||
|
|
||||||
|
|
||||||
|
def PlexToKodiTimefactor():
|
||||||
|
"""
|
||||||
|
Kodi measures time in seconds, but Plex in milliseconds
|
||||||
|
"""
|
||||||
|
return 1.0 / 1000.0
|
||||||
|
|
||||||
|
|
||||||
def GetItemClassFromType(itemType):
|
def GetItemClassFromType(itemType):
|
||||||
classes = {
|
classes = {
|
||||||
'movie': 'Movies',
|
'movie': 'Movies',
|
||||||
|
@ -21,6 +31,42 @@ def GetItemClassFromType(itemType):
|
||||||
return classes[itemType]
|
return classes[itemType]
|
||||||
|
|
||||||
|
|
||||||
|
def GetPlexKeyNumber(plexKey):
|
||||||
|
"""
|
||||||
|
Deconstructs e.g. '/library/metadata/xxxx' to the tuple
|
||||||
|
|
||||||
|
('library/metadata', 'xxxx')
|
||||||
|
|
||||||
|
Returns ('','') if nothing is found
|
||||||
|
"""
|
||||||
|
regex = re.compile(r'''/(.+)/(\d+)$''')
|
||||||
|
try:
|
||||||
|
result = regex.findall(plexKey)[0]
|
||||||
|
except IndexError:
|
||||||
|
result = ('', '')
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def ParseContainerKey(containerKey):
|
||||||
|
"""
|
||||||
|
Parses e.g. /playQueues/3045?own=1&repeat=0&window=200 to:
|
||||||
|
'playQueues', '3045', {'window': ['200'], 'own': ['1'], 'repeat': ['0']}
|
||||||
|
|
||||||
|
Output hence: library, key, query (query as a special dict)
|
||||||
|
"""
|
||||||
|
result = urlparse(containerKey)
|
||||||
|
library, key = GetPlexKeyNumber(result.path)
|
||||||
|
query = parse_qs(result.query)
|
||||||
|
return library, key, query
|
||||||
|
|
||||||
|
|
||||||
|
def LiteralEval(string):
|
||||||
|
"""
|
||||||
|
Turns a string e.g. in a dict, safely :-)
|
||||||
|
"""
|
||||||
|
return literal_eval(string)
|
||||||
|
|
||||||
|
|
||||||
def GetMethodFromPlexType(plexType):
|
def GetMethodFromPlexType(plexType):
|
||||||
methods = {
|
methods = {
|
||||||
'movie': 'add_update',
|
'movie': 'add_update',
|
||||||
|
@ -46,18 +92,25 @@ def EmbyItemtypes():
|
||||||
return ['Movie', 'Series', 'Season', 'Episode']
|
return ['Movie', 'Series', 'Season', 'Episode']
|
||||||
|
|
||||||
|
|
||||||
def XbmcPhoto():
|
def GetPlayQueue(playQueueID):
|
||||||
return "photo"
|
"""
|
||||||
def XbmcVideo():
|
Fetches the PMS playqueue with the playQueueID as a JSON
|
||||||
return "video"
|
|
||||||
def XbmcAudio():
|
Returns False if something went wrong
|
||||||
return "audio"
|
"""
|
||||||
def PlexPhoto():
|
url = "{server}/playQueues/%s" % playQueueID
|
||||||
return "photo"
|
headerOptions = {'Accept': 'application/json'}
|
||||||
def PlexVideo():
|
json = downloadutils.DownloadUtils().downloadUrl(url, headerOptions=headerOptions)
|
||||||
return "video"
|
try:
|
||||||
def PlexAudio():
|
json = json.json()
|
||||||
return "music"
|
except:
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
json['_children']
|
||||||
|
json['playQueueID']
|
||||||
|
except KeyError:
|
||||||
|
return False
|
||||||
|
return json
|
||||||
|
|
||||||
|
|
||||||
def GetPlexMetadata(key):
|
def GetPlexMetadata(key):
|
||||||
|
@ -87,7 +140,7 @@ def GetPlexMetadata(key):
|
||||||
}
|
}
|
||||||
url = url + '?' + urlencode(arguments)
|
url = url + '?' + urlencode(arguments)
|
||||||
headerOptions = {'Accept': 'application/xml'}
|
headerOptions = {'Accept': 'application/xml'}
|
||||||
xml = DownloadUtils().downloadUrl(url, headerOptions=headerOptions)
|
xml = downloadutils.DownloadUtils().downloadUrl(url, headerOptions=headerOptions)
|
||||||
# Did we receive a valid XML?
|
# Did we receive a valid XML?
|
||||||
try:
|
try:
|
||||||
xml.tag
|
xml.tag
|
||||||
|
@ -108,7 +161,7 @@ def GetAllPlexChildren(key):
|
||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
url = "{server}/library/metadata/%s/children" % key
|
url = "{server}/library/metadata/%s/children" % key
|
||||||
jsondata = DownloadUtils().downloadUrl(url)
|
jsondata = downloadutils.DownloadUtils().downloadUrl(url)
|
||||||
try:
|
try:
|
||||||
result = jsondata['_children']
|
result = jsondata['_children']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -125,7 +178,7 @@ def GetPlexSectionResults(viewId, headerOptions={}):
|
||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
url = "{server}/library/sections/%s/all" % viewId
|
url = "{server}/library/sections/%s/all" % viewId
|
||||||
jsondata = DownloadUtils().downloadUrl(url, headerOptions=headerOptions)
|
jsondata = downloadutils.DownloadUtils().downloadUrl(url, headerOptions=headerOptions)
|
||||||
try:
|
try:
|
||||||
result = jsondata['_children']
|
result = jsondata['_children']
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
@ -146,37 +199,38 @@ def GetPlexSectionResults(viewId, headerOptions={}):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def GetPlexUpdatedItems(viewId, unixTime, headerOptions={}):
|
def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None,
|
||||||
"""
|
headerOptions={}):
|
||||||
Returns a list (raw JSON or XML API dump) of all Plex items in the Plex
|
|
||||||
section with key = viewId AFTER the unixTime
|
|
||||||
"""
|
|
||||||
result = []
|
|
||||||
url = "{server}/library/sections/%s/allLeaves?updatedAt>=%s" \
|
|
||||||
% (viewId, unixTime)
|
|
||||||
jsondata = DownloadUtils().downloadUrl(url, headerOptions=headerOptions)
|
|
||||||
try:
|
|
||||||
result = jsondata['_children']
|
|
||||||
except KeyError:
|
|
||||||
logMsg(title,
|
|
||||||
"Error retrieving all items for Plex section %s and time %s"
|
|
||||||
% (viewId, unixTime), -1)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def GetAllPlexLeaves(viewId, headerOptions={}):
|
|
||||||
"""
|
"""
|
||||||
Returns a list (raw JSON or XML API dump) of all Plex subitems for the
|
Returns a list (raw JSON or XML API dump) of all Plex subitems for the
|
||||||
key.
|
key.
|
||||||
(e.g. /library/sections/2/allLeaves pointing to all TV shows)
|
(e.g. /library/sections/2/allLeaves pointing to all TV shows)
|
||||||
|
|
||||||
Input:
|
Input:
|
||||||
viewId Id of Plex library, e.g. '2'
|
viewId Id of Plex library, e.g. '2'
|
||||||
headerOptions to override the download headers
|
lastViewedAt Unix timestamp; only retrieves PMS items viewed
|
||||||
|
since that point of time until now.
|
||||||
|
updatedAt Unix timestamp; only retrieves PMS items updated
|
||||||
|
by the PMS since that point of time until now.
|
||||||
|
headerOptions to override any download headers
|
||||||
|
|
||||||
|
If lastViewedAt and updatedAt=None, ALL PMS items are returned.
|
||||||
|
|
||||||
|
Warning: lastViewedAt and updatedAt are combined with AND by the PMS!
|
||||||
|
|
||||||
|
Relevant "master time": PMS server. I guess this COULD lead to problems,
|
||||||
|
e.g. when server and client are in different time zones.
|
||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
url = "{server}/library/sections/%s/allLeaves" % viewId
|
args = []
|
||||||
jsondata = DownloadUtils().downloadUrl(url, headerOptions=headerOptions)
|
url = "{server}/library/sections/%s/allLeaves?" % viewId
|
||||||
|
if lastViewedAt:
|
||||||
|
args.append('lastViewedAt>=%s' % lastViewedAt)
|
||||||
|
if updatedAt:
|
||||||
|
args.append('updatedAt>=%s' % updatedAt)
|
||||||
|
args = '&'.join(args)
|
||||||
|
jsondata = downloadutils.DownloadUtils().downloadUrl(
|
||||||
|
url+args, headerOptions=headerOptions)
|
||||||
try:
|
try:
|
||||||
result = jsondata['_children']
|
result = jsondata['_children']
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
@ -213,7 +267,7 @@ def GetPlexCollections(mediatype):
|
||||||
"""
|
"""
|
||||||
collections = []
|
collections = []
|
||||||
url = "{server}/library/sections"
|
url = "{server}/library/sections"
|
||||||
jsondata = DownloadUtils().downloadUrl(url)
|
jsondata = downloadutils.DownloadUtils().downloadUrl(url)
|
||||||
try:
|
try:
|
||||||
result = jsondata['_children']
|
result = jsondata['_children']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
|
@ -31,36 +31,65 @@ import embydb_functions
|
||||||
|
|
||||||
#################################################################################################
|
#################################################################################################
|
||||||
|
|
||||||
|
# For logging only
|
||||||
|
title = " %s %s" % (clientinfo.ClientInfo().getAddonName(), __name__)
|
||||||
|
|
||||||
|
|
||||||
|
def plexCompanion(fullurl, params=None):
|
||||||
|
params = PlexFunctions.LiteralEval(params[26:])
|
||||||
|
utils.logMsg("entrypoint - plexCompanion",
|
||||||
|
"params is: %s" % params, -1)
|
||||||
|
# {'protocol': 'http',
|
||||||
|
# 'containerKey': '/playQueues/3045?own=1&repeat=0&window=200',
|
||||||
|
# 'offset': '0',
|
||||||
|
# 'commandID': '20',
|
||||||
|
# 'token': 'transient-0243a39f-4c7d-495f-a5c8-6991b622b5a6',
|
||||||
|
# 'key': '/library/metadata/470',
|
||||||
|
# 'address': '192.168.0.2',
|
||||||
|
# 'machineIdentifier': '3eb2fc28af89500e000db2e07f8e8234d159f2c4',
|
||||||
|
# 'type': 'video',
|
||||||
|
# 'port': '32400'}
|
||||||
|
|
||||||
|
if (params.get('machineIdentifier') !=
|
||||||
|
utils.window('plex_machineIdentifier')):
|
||||||
|
utils.logMsg(
|
||||||
|
title,
|
||||||
|
"Command was not for us, machineIdentifier controller: %s, "
|
||||||
|
"our machineIdentifier : %s"
|
||||||
|
% (params.get('machineIdentifier'),
|
||||||
|
utils.window('plex_machineIdentifier')), -1)
|
||||||
|
return
|
||||||
|
utils.window('plex_key', params.get('key'))
|
||||||
|
library, key, query = PlexFunctions(params.get('containerKey'))
|
||||||
|
# Construct a container key that works always (get rid of playlist args)
|
||||||
|
utils.window('plex_containerKey', '/'+library+'/'+key)
|
||||||
|
# Assume it's video when something goes wrong
|
||||||
|
playbackType = params.get('type', 'video')
|
||||||
|
|
||||||
|
if 'playQueues' in library:
|
||||||
|
utils.logMsg(title, "Playing a playQueue. Query was: %s" % query, 1)
|
||||||
|
# Playing a playlist that we need to fetch from PMS
|
||||||
|
playQueue = PlexFunctions.GetPlayQueue(key)
|
||||||
|
if not playQueue:
|
||||||
|
utils.logMsg(
|
||||||
|
title, "Error getting PMS playlist for key %s" % key, -1)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Set window properties to make them available for other threads
|
||||||
|
utils.window('plex_playQueueID', playQueue['playQueueID'])
|
||||||
|
utils.window('plex_playQueueVersion', playQueue['playQueueVersion'])
|
||||||
|
utils.window('plex_playQueueShuffled', playQueue['playQueueShuffled'])
|
||||||
|
utils.window(
|
||||||
|
'plex_playQueueSelectedItemID',
|
||||||
|
playQueue['playQueueSelectedItemID'])
|
||||||
|
utils.window(
|
||||||
|
'plex_playQueueSelectedItemOffset',
|
||||||
|
playQueue['playQueueSelectedItemOffset'])
|
||||||
|
|
||||||
|
pbutils.PlaybackUtils(playQueue['_children']).StartPlay(
|
||||||
|
resume=playQueue['playQueueSelectedItemOffset'],
|
||||||
|
resumeItem=playQueue['playQueueSelectedItemID'])
|
||||||
|
|
||||||
def plexCompanion(fullurl, resume=None):
|
|
||||||
regex = re.compile(r'''/(\d+)$''')
|
|
||||||
itemid = regex.findall(fullurl)
|
|
||||||
try:
|
|
||||||
itemid = itemid[0]
|
|
||||||
except IndexError:
|
|
||||||
# No matches found, url not like:
|
|
||||||
# http://192.168.0.2:32400/library/metadata/243480
|
|
||||||
utils.logMsg("entrypoint - plexCompanion",
|
|
||||||
"Could not parse url: %s" % fullurl, -1)
|
|
||||||
return False
|
|
||||||
# Initialize embydb
|
|
||||||
embyconn = utils.kodiSQL('emby')
|
|
||||||
embycursor = embyconn.cursor()
|
|
||||||
emby = embydb_functions.Embydb_Functions(embycursor)
|
|
||||||
# Get dbid using itemid
|
|
||||||
# Works only for library items, not e.g. for trailers
|
|
||||||
try:
|
|
||||||
dbid = emby.getItem_byId(itemid)[0]
|
|
||||||
except TypeError:
|
|
||||||
# Trailers and the like
|
|
||||||
dbid = None
|
|
||||||
embyconn.close()
|
|
||||||
# Fix resume timing
|
|
||||||
if resume:
|
|
||||||
if resume == '0':
|
|
||||||
resume = None
|
|
||||||
else:
|
|
||||||
resume = round(float(resume) / 1000.0, 6)
|
|
||||||
# Start playing
|
# Start playing
|
||||||
item = PlexFunctions.GetPlexMetadata(itemid)
|
item = PlexFunctions.GetPlexMetadata(itemid)
|
||||||
pbutils.PlaybackUtils(item).play(itemid, dbid, seektime=resume)
|
pbutils.PlaybackUtils(item).play(itemid, dbid, seektime=resume)
|
||||||
|
|
|
@ -42,6 +42,7 @@ class InitialSetup():
|
||||||
clientId = self.clientInfo.getDeviceId()
|
clientId = self.clientInfo.getDeviceId()
|
||||||
serverid = self.userClient.getServerId()
|
serverid = self.userClient.getServerId()
|
||||||
myplexlogin, plexhome, plexLogin, plexToken = self.plx.GetPlexLoginFromSettings()
|
myplexlogin, plexhome, plexLogin, plexToken = self.plx.GetPlexLoginFromSettings()
|
||||||
|
dialog = xbmcgui.Dialog()
|
||||||
|
|
||||||
# Optionally sign into plex.tv. Will not be called on very first run
|
# Optionally sign into plex.tv. Will not be called on very first run
|
||||||
# as plexToken will be ''
|
# as plexToken will be ''
|
||||||
|
@ -49,7 +50,6 @@ class InitialSetup():
|
||||||
chk = self.plx.CheckConnection('plex.tv', plexToken)
|
chk = self.plx.CheckConnection('plex.tv', plexToken)
|
||||||
# HTTP Error: unauthorized
|
# HTTP Error: unauthorized
|
||||||
if chk == 401:
|
if chk == 401:
|
||||||
dialog = xbmcgui.Dialog()
|
|
||||||
dialog.ok(
|
dialog.ok(
|
||||||
self.addonName,
|
self.addonName,
|
||||||
'Could not login to plex.tv.',
|
'Could not login to plex.tv.',
|
||||||
|
@ -60,7 +60,6 @@ class InitialSetup():
|
||||||
plexLogin = result['username']
|
plexLogin = result['username']
|
||||||
plexToken = result['token']
|
plexToken = result['token']
|
||||||
elif chk is False or chk >= 400:
|
elif chk is False or chk >= 400:
|
||||||
dialog = xbmcgui.Dialog()
|
|
||||||
dialog.ok(
|
dialog.ok(
|
||||||
self.addonName,
|
self.addonName,
|
||||||
'Problems connecting to plex.tv.',
|
'Problems connecting to plex.tv.',
|
||||||
|
@ -81,12 +80,8 @@ class InitialSetup():
|
||||||
plexLogin = result['username']
|
plexLogin = result['username']
|
||||||
plexToken = result['token']
|
plexToken = result['token']
|
||||||
# Get g_PMS list of servers (saved to plx.g_PMS)
|
# Get g_PMS list of servers (saved to plx.g_PMS)
|
||||||
serverNum = 1
|
while True:
|
||||||
while serverNum > 0:
|
tokenDict = {'MyPlexToken': plexToken} if plexToken else {}
|
||||||
if plexToken:
|
|
||||||
tokenDict = {'MyPlexToken': plexToken}
|
|
||||||
else:
|
|
||||||
tokenDict = {}
|
|
||||||
# Populate g_PMS variable with the found Plex servers
|
# Populate g_PMS variable with the found Plex servers
|
||||||
self.plx.discoverPMS(clientId,
|
self.plx.discoverPMS(clientId,
|
||||||
None,
|
None,
|
||||||
|
@ -100,8 +95,12 @@ class InitialSetup():
|
||||||
# Get a nicer list
|
# Get a nicer list
|
||||||
dialoglist = []
|
dialoglist = []
|
||||||
# Exit if no servers found
|
# Exit if no servers found
|
||||||
serverNum = len(serverlist)
|
if len(serverlist) == 0:
|
||||||
if serverNum == 0:
|
dialog.ok(
|
||||||
|
self.addonName,
|
||||||
|
'Could not find any Plex server in the network.'
|
||||||
|
'Aborting...'
|
||||||
|
)
|
||||||
break
|
break
|
||||||
for server in serverlist:
|
for server in serverlist:
|
||||||
if server['local'] == '1':
|
if server['local'] == '1':
|
||||||
|
@ -109,7 +108,6 @@ class InitialSetup():
|
||||||
dialoglist.append(str(server['name']) + ' (nearby)')
|
dialoglist.append(str(server['name']) + ' (nearby)')
|
||||||
else:
|
else:
|
||||||
dialoglist.append(str(server['name']))
|
dialoglist.append(str(server['name']))
|
||||||
dialog = xbmcgui.Dialog()
|
|
||||||
resp = dialog.select(
|
resp = dialog.select(
|
||||||
'Choose your Plex server',
|
'Choose your Plex server',
|
||||||
dialoglist)
|
dialoglist)
|
||||||
|
@ -119,11 +117,11 @@ class InitialSetup():
|
||||||
server['port']
|
server['port']
|
||||||
# Deactive SSL verification if the server is local!
|
# Deactive SSL verification if the server is local!
|
||||||
if server['local'] == '1':
|
if server['local'] == '1':
|
||||||
self.addon.setSetting('sslverify', 'false')
|
utils.settings('sslverify', 'false')
|
||||||
self.logMsg("Setting SSL verify to false, because server is "
|
self.logMsg("Setting SSL verify to false, because server is "
|
||||||
"local", 1)
|
"local", 1)
|
||||||
else:
|
else:
|
||||||
self.addon.setSetting('sslverify', 'true')
|
utils.settings('sslverify', 'true')
|
||||||
self.logMsg("Setting SSL verify to true, because server is "
|
self.logMsg("Setting SSL verify to true, because server is "
|
||||||
"not local", 1)
|
"not local", 1)
|
||||||
chk = self.plx.CheckConnection(url, server['accesstoken'])
|
chk = self.plx.CheckConnection(url, server['accesstoken'])
|
||||||
|
@ -142,7 +140,6 @@ class InitialSetup():
|
||||||
break
|
break
|
||||||
# Problems connecting
|
# Problems connecting
|
||||||
elif chk >= 400 or chk is False:
|
elif chk >= 400 or chk is False:
|
||||||
dialog = xbmcgui.Dialog()
|
|
||||||
resp = dialog.yesno(self.addonName,
|
resp = dialog.yesno(self.addonName,
|
||||||
'Problems connecting to server.',
|
'Problems connecting to server.',
|
||||||
'Pick another server?')
|
'Pick another server?')
|
||||||
|
@ -158,20 +155,19 @@ class InitialSetup():
|
||||||
xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId)
|
xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId)
|
||||||
return
|
return
|
||||||
# Write to Kodi settings file
|
# Write to Kodi settings file
|
||||||
self.addon.setSetting('plex_machineIdentifier', activeServer)
|
utils.settings('plex_machineIdentifier', activeServer)
|
||||||
self.addon.setSetting('ipaddress', server['ip'])
|
utils.settings('ipaddress', server['ip'])
|
||||||
self.addon.setSetting('port', server['port'])
|
utils.settings('port', server['port'])
|
||||||
if server['scheme'] == 'https':
|
if server['scheme'] == 'https':
|
||||||
self.addon.setSetting('https', 'true')
|
utils.settings('https', 'true')
|
||||||
else:
|
else:
|
||||||
self.addon.setSetting('https', 'false')
|
utils.settings('https', 'false')
|
||||||
self.logMsg("Wrote to Kodi user settings file:", 0)
|
self.logMsg("Wrote to Kodi user settings file:", 0)
|
||||||
self.logMsg("PMS machineIdentifier: %s, ip: %s, port: %s, https: %s "
|
self.logMsg("PMS machineIdentifier: %s, ip: %s, port: %s, https: %s "
|
||||||
% (activeServer, server['ip'], server['port'],
|
% (activeServer, server['ip'], server['port'],
|
||||||
server['scheme']), 0)
|
server['scheme']), 0)
|
||||||
|
|
||||||
##### ADDITIONAL PROMPTS #####
|
##### ADDITIONAL PROMPTS #####
|
||||||
dialog = xbmcgui.Dialog()
|
|
||||||
directPaths = dialog.yesno(
|
directPaths = dialog.yesno(
|
||||||
heading="%s: Playback Mode" % self.addonName,
|
heading="%s: Playback Mode" % self.addonName,
|
||||||
line1=(
|
line1=(
|
||||||
|
|
|
@ -263,7 +263,7 @@ class Movies(Items):
|
||||||
API = PlexAPI.API(itemList)
|
API = PlexAPI.API(itemList)
|
||||||
for itemNumber in range(len(itemList)):
|
for itemNumber in range(len(itemList)):
|
||||||
API.setChildNumber(itemNumber)
|
API.setChildNumber(itemNumber)
|
||||||
itemid = API.getKey()
|
itemid = API.getRatingKey()
|
||||||
# Get key and db entry on the Kodi db side
|
# Get key and db entry on the Kodi db side
|
||||||
fileid = self.emby_db.getItem_byId(itemid)[1]
|
fileid = self.emby_db.getItem_byId(itemid)[1]
|
||||||
# Grab the user's viewcount, resume points etc. from PMS' answer
|
# Grab the user's viewcount, resume points etc. from PMS' answer
|
||||||
|
@ -276,6 +276,7 @@ class Movies(Items):
|
||||||
userdata['LastPlayedDate'])
|
userdata['LastPlayedDate'])
|
||||||
|
|
||||||
def add_update(self, item, viewtag=None, viewid=None):
|
def add_update(self, item, viewtag=None, viewid=None):
|
||||||
|
self.logMsg("Entering add_update", 1)
|
||||||
# Process single movie
|
# Process single movie
|
||||||
kodicursor = self.kodicursor
|
kodicursor = self.kodicursor
|
||||||
emby_db = self.emby_db
|
emby_db = self.emby_db
|
||||||
|
@ -286,7 +287,7 @@ class Movies(Items):
|
||||||
# If the item already exist in the local Kodi DB we'll perform a full item update
|
# If the item already exist in the local Kodi DB we'll perform a full item update
|
||||||
# If the item doesn't exist, we'll add it to the database
|
# If the item doesn't exist, we'll add it to the database
|
||||||
update_item = True
|
update_item = True
|
||||||
itemid = API.getKey()
|
itemid = API.getRatingKey()
|
||||||
# Cannot parse XML, abort
|
# Cannot parse XML, abort
|
||||||
if not itemid:
|
if not itemid:
|
||||||
self.logMsg("Cannot parse XML data for movie", -1)
|
self.logMsg("Cannot parse XML data for movie", -1)
|
||||||
|
@ -346,22 +347,22 @@ class Movies(Items):
|
||||||
# Find one trailer
|
# Find one trailer
|
||||||
trailer = None
|
trailer = None
|
||||||
extras = API.getExtras()
|
extras = API.getExtras()
|
||||||
for item in extras:
|
for extra in extras:
|
||||||
# Only get 1st trailer element
|
# Only get 1st trailer element
|
||||||
if item['extraType'] == '1':
|
if extra['extraType'] == '1':
|
||||||
trailer = item['key']
|
trailer = extra['key']
|
||||||
trailer = "plugin://plugin.video.plexkodiconnect/trailer/?id=%s&mode=play" % trailer
|
trailer = "plugin://plugin.video.plexkodiconnect/trailer/?id=%s&mode=play" % trailer
|
||||||
self.logMsg("Trailer for %s: %s" % (itemid, trailer), 2)
|
self.logMsg("Trailer for %s: %s" % (itemid, trailer), 2)
|
||||||
break
|
break
|
||||||
|
|
||||||
##### GET THE FILE AND PATH #####
|
##### GET THE FILE AND PATH #####
|
||||||
playurl = API.getFilePath()
|
playurl = API.getKey()
|
||||||
|
filename = playurl
|
||||||
if "\\" in playurl:
|
# if "\\" in playurl:
|
||||||
# Local path
|
# # Local path
|
||||||
filename = playurl.rsplit("\\", 1)[1]
|
# filename = playurl.rsplit("\\", 1)[1]
|
||||||
else: # Network share
|
# else: # Network share
|
||||||
filename = playurl.rsplit("/", 1)[1]
|
# filename = playurl.rsplit("/", 1)[1]
|
||||||
|
|
||||||
if self.directpath:
|
if self.directpath:
|
||||||
# Direct paths is set the Kodi way
|
# Direct paths is set the Kodi way
|
||||||
|
@ -386,13 +387,13 @@ class Movies(Items):
|
||||||
path = "plugin://plugin.video.plexkodiconnect.movies/"
|
path = "plugin://plugin.video.plexkodiconnect.movies/"
|
||||||
params = {
|
params = {
|
||||||
|
|
||||||
'filename': filename.encode('utf-8'),
|
#'filename': filename.encode('utf-8'),
|
||||||
|
'filename': filename,
|
||||||
'id': itemid,
|
'id': itemid,
|
||||||
'dbid': movieid,
|
'dbid': movieid,
|
||||||
'mode': "play"
|
'mode': "play"
|
||||||
}
|
}
|
||||||
filename = "%s?%s" % (path, urllib.urlencode(params))
|
filename = "%s?%s" % (path, urllib.urlencode(params))
|
||||||
|
|
||||||
##### UPDATE THE MOVIE #####
|
##### UPDATE THE MOVIE #####
|
||||||
if update_item:
|
if update_item:
|
||||||
self.logMsg("UPDATE movie itemid: %s - Title: %s" % (itemid, title), 1)
|
self.logMsg("UPDATE movie itemid: %s - Title: %s" % (itemid, title), 1)
|
||||||
|
@ -462,24 +463,33 @@ class Movies(Items):
|
||||||
# Process cast
|
# Process cast
|
||||||
people = API.getPeopleList()
|
people = API.getPeopleList()
|
||||||
kodi_db.addPeople(movieid, people, "movie")
|
kodi_db.addPeople(movieid, people, "movie")
|
||||||
|
self.logMsg('People added', 2)
|
||||||
# Process genres
|
# Process genres
|
||||||
kodi_db.addGenres(movieid, genres, "movie")
|
kodi_db.addGenres(movieid, genres, "movie")
|
||||||
|
self.logMsg('Genres added', 2)
|
||||||
# Process artwork
|
# Process artwork
|
||||||
allartworks = API.getAllArtwork()
|
allartworks = API.getAllArtwork()
|
||||||
|
self.logMsg('Artwork processed', 2)
|
||||||
artwork.addArtwork(allartworks, movieid, "movie", kodicursor)
|
artwork.addArtwork(allartworks, movieid, "movie", kodicursor)
|
||||||
|
self.logMsg('Artwork added', 2)
|
||||||
# Process stream details
|
# Process stream details
|
||||||
streams = API.getMediaStreams()
|
streams = API.getMediaStreams()
|
||||||
|
self.logMsg('Streames processed', 2)
|
||||||
kodi_db.addStreams(fileid, streams, runtime)
|
kodi_db.addStreams(fileid, streams, runtime)
|
||||||
|
self.logMsg('Streames added', 2)
|
||||||
# Process studios
|
# Process studios
|
||||||
kodi_db.addStudios(movieid, studios, "movie")
|
kodi_db.addStudios(movieid, studios, "movie")
|
||||||
|
self.logMsg('Studios added', 2)
|
||||||
# Process tags: view, emby tags
|
# Process tags: view, emby tags
|
||||||
tags = [viewtag]
|
tags = [viewtag]
|
||||||
# tags.extend(item['Tags'])
|
# tags.extend(item['Tags'])
|
||||||
# if userdata['Favorite']:
|
# if userdata['Favorite']:
|
||||||
# tags.append("Favorite movies")
|
# tags.append("Favorite movies")
|
||||||
kodi_db.addTags(movieid, tags, "movie")
|
kodi_db.addTags(movieid, tags, "movie")
|
||||||
|
self.logMsg('Tags added', 2)
|
||||||
# Process playstates
|
# Process playstates
|
||||||
kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
|
kodi_db.addPlaystate(fileid, resume, runtime, playcount, dateplayed)
|
||||||
|
self.logMsg('Done processing %s' % itemid, 2)
|
||||||
|
|
||||||
def remove(self, itemid):
|
def remove(self, itemid):
|
||||||
# Remove movieid, fileid, emby reference
|
# Remove movieid, fileid, emby reference
|
||||||
|
@ -598,7 +608,7 @@ class MusicVideos(Items):
|
||||||
|
|
||||||
|
|
||||||
##### GET THE FILE AND PATH #####
|
##### GET THE FILE AND PATH #####
|
||||||
playurl = API.getFilePath()
|
playurl = API.getKey()
|
||||||
|
|
||||||
if "\\" in playurl:
|
if "\\" in playurl:
|
||||||
# Local path
|
# Local path
|
||||||
|
@ -883,7 +893,7 @@ class TVShows(Items):
|
||||||
API = PlexAPI.API(itemList)
|
API = PlexAPI.API(itemList)
|
||||||
for itemNumber in range(len(itemList)):
|
for itemNumber in range(len(itemList)):
|
||||||
API.setChildNumber(itemNumber)
|
API.setChildNumber(itemNumber)
|
||||||
itemid = API.getKey()
|
itemid = API.getRatingKey()
|
||||||
# Get key and db entry on the Kodi db side
|
# Get key and db entry on the Kodi db side
|
||||||
fileid = self.emby_db.getItem_byId(itemid)[1]
|
fileid = self.emby_db.getItem_byId(itemid)[1]
|
||||||
# Grab the user's viewcount, resume points etc. from PMS' answer
|
# Grab the user's viewcount, resume points etc. from PMS' answer
|
||||||
|
@ -909,7 +919,7 @@ class TVShows(Items):
|
||||||
# If the item already exist in the local Kodi DB we'll perform a full item update
|
# If the item already exist in the local Kodi DB we'll perform a full item update
|
||||||
# If the item doesn't exist, we'll add it to the database
|
# If the item doesn't exist, we'll add it to the database
|
||||||
update_item = True
|
update_item = True
|
||||||
itemid = API.getKey()
|
itemid = API.getRatingKey()
|
||||||
if not itemid:
|
if not itemid:
|
||||||
self.logMsg("Cannot parse XML data for TV show", -1)
|
self.logMsg("Cannot parse XML data for TV show", -1)
|
||||||
return
|
return
|
||||||
|
@ -943,7 +953,7 @@ class TVShows(Items):
|
||||||
studio = None
|
studio = None
|
||||||
|
|
||||||
##### GET THE FILE AND PATH #####
|
##### GET THE FILE AND PATH #####
|
||||||
playurl = API.getFilePath()
|
playurl = API.getKey()
|
||||||
|
|
||||||
if self.directpath:
|
if self.directpath:
|
||||||
# Direct paths is set the Kodi way
|
# Direct paths is set the Kodi way
|
||||||
|
@ -1070,7 +1080,7 @@ class TVShows(Items):
|
||||||
def add_updateSeason(self, item, viewid=None, viewtag=None):
|
def add_updateSeason(self, item, viewid=None, viewtag=None):
|
||||||
API = PlexAPI.API(item)
|
API = PlexAPI.API(item)
|
||||||
showid = viewid
|
showid = viewid
|
||||||
itemid = API.getKey()
|
itemid = API.getRatingKey()
|
||||||
kodicursor = self.kodicursor
|
kodicursor = self.kodicursor
|
||||||
emby_db = self.emby_db
|
emby_db = self.emby_db
|
||||||
kodi_db = self.kodi_db
|
kodi_db = self.kodi_db
|
||||||
|
@ -1116,7 +1126,7 @@ class TVShows(Items):
|
||||||
# If the item already exist in the local Kodi DB we'll perform a full item update
|
# If the item already exist in the local Kodi DB we'll perform a full item update
|
||||||
# If the item doesn't exist, we'll add it to the database
|
# If the item doesn't exist, we'll add it to the database
|
||||||
update_item = True
|
update_item = True
|
||||||
itemid = API.getKey()
|
itemid = API.getRatingKey()
|
||||||
emby_dbitem = emby_db.getItem_byId(itemid)
|
emby_dbitem = emby_db.getItem_byId(itemid)
|
||||||
self.logMsg("Processing episode with Plex Id: %s" % itemid, 2)
|
self.logMsg("Processing episode with Plex Id: %s" % itemid, 2)
|
||||||
try:
|
try:
|
||||||
|
@ -1150,8 +1160,12 @@ class TVShows(Items):
|
||||||
resume, runtime = API.getRuntime()
|
resume, runtime = API.getRuntime()
|
||||||
premieredate = API.getPremiereDate()
|
premieredate = API.getPremiereDate()
|
||||||
|
|
||||||
|
self.logMsg("Retrieved metadata for %s" % itemid, 2)
|
||||||
|
|
||||||
# episode details
|
# episode details
|
||||||
seriesId, seriesName, season, episode = API.getEpisodeDetails()
|
seriesId, seriesName, season, episode = API.getEpisodeDetails()
|
||||||
|
self.logMsg("Got episode details: %s %s: s%se%s"
|
||||||
|
% (seriesId, seriesName, season, episode), 2)
|
||||||
|
|
||||||
if season is None:
|
if season is None:
|
||||||
if item.get('AbsoluteEpisodeNumber'):
|
if item.get('AbsoluteEpisodeNumber'):
|
||||||
|
@ -1180,27 +1194,30 @@ class TVShows(Items):
|
||||||
try:
|
try:
|
||||||
showid = show[0]
|
showid = show[0]
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# Show is missing from database
|
# self.logMsg("Show is missing from database, trying to add", 2)
|
||||||
show = self.emby.getItem(seriesId)
|
# show = self.emby.getItem(seriesId)
|
||||||
self.add_update(show)
|
# self.logMsg("Show now: %s. Trying to add new show" % show, 2)
|
||||||
show = emby_db.getItem_byId(seriesId)
|
# self.add_update(show)
|
||||||
try:
|
# show = emby_db.getItem_byId(seriesId)
|
||||||
showid = show[0]
|
# try:
|
||||||
except TypeError:
|
# showid = show[0]
|
||||||
self.logMsg("Skipping: %s. Unable to add series: %s." % (itemid, seriesId), -1)
|
# except TypeError:
|
||||||
return False
|
# self.logMsg("Skipping: %s. Unable to add series: %s." % (itemid, seriesId), -1)
|
||||||
|
self.logMsg("Parent tvshow now found, skip item", 2)
|
||||||
|
return False
|
||||||
|
self.logMsg("showid: %s" % showid, 2)
|
||||||
seasonid = kodi_db.addSeason(showid, season)
|
seasonid = kodi_db.addSeason(showid, season)
|
||||||
|
self.logMsg("seasonid: %s" % seasonid, 2)
|
||||||
|
|
||||||
|
|
||||||
##### GET THE FILE AND PATH #####
|
##### GET THE FILE AND PATH #####
|
||||||
playurl = API.getFilePath()
|
playurl = API.getKey()
|
||||||
|
filename = playurl
|
||||||
|
|
||||||
if "\\" in playurl:
|
# if "\\" in playurl:
|
||||||
# Local path
|
# # Local path
|
||||||
filename = playurl.rsplit("\\", 1)[1]
|
# filename = playurl.rsplit("\\", 1)[1]
|
||||||
else: # Network share
|
# else: # Network share
|
||||||
filename = playurl.rsplit("/", 1)[1]
|
# filename = playurl.rsplit("/", 1)[1]
|
||||||
|
|
||||||
if self.directpath:
|
if self.directpath:
|
||||||
# Direct paths is set the Kodi way
|
# Direct paths is set the Kodi way
|
||||||
|
@ -1225,7 +1242,8 @@ class TVShows(Items):
|
||||||
path = "plugin://plugin.video.plexkodiconnect.tvshows/%s/" % seriesId
|
path = "plugin://plugin.video.plexkodiconnect.tvshows/%s/" % seriesId
|
||||||
params = {
|
params = {
|
||||||
|
|
||||||
'filename': filename.encode('utf-8'),
|
#'filename': filename.encode('utf-8'),
|
||||||
|
'filename': filename,
|
||||||
'id': itemid,
|
'id': itemid,
|
||||||
'dbid': episodeid,
|
'dbid': episodeid,
|
||||||
'mode': "play"
|
'mode': "play"
|
||||||
|
@ -1234,7 +1252,7 @@ class TVShows(Items):
|
||||||
|
|
||||||
##### UPDATE THE EPISODE #####
|
##### UPDATE THE EPISODE #####
|
||||||
if update_item:
|
if update_item:
|
||||||
self.logMsg("UPDATE episode itemid: %s - Title: %s" % (itemid, title), 1)
|
self.logMsg("UPDATE episode itemid: %s" % (itemid), 1)
|
||||||
|
|
||||||
# Update the movie entry
|
# Update the movie entry
|
||||||
if kodiversion in (16, 17):
|
if kodiversion in (16, 17):
|
||||||
|
@ -1268,7 +1286,7 @@ class TVShows(Items):
|
||||||
|
|
||||||
##### OR ADD THE EPISODE #####
|
##### OR ADD THE EPISODE #####
|
||||||
else:
|
else:
|
||||||
self.logMsg("ADD episode itemid: %s - Title: %s" % (itemid, title), 1)
|
self.logMsg("ADD episode itemid: %s" % (itemid), 1)
|
||||||
|
|
||||||
# Add path
|
# Add path
|
||||||
pathid = kodi_db.addPath(path)
|
pathid = kodi_db.addPath(path)
|
||||||
|
@ -1850,7 +1868,7 @@ class Music(Items):
|
||||||
path = "%s/emby/Audio/%s/" % (self.server, itemid)
|
path = "%s/emby/Audio/%s/" % (self.server, itemid)
|
||||||
filename = "stream.mp3"
|
filename = "stream.mp3"
|
||||||
else:
|
else:
|
||||||
playurl = API.getFilePath()
|
playurl = API.getKey()
|
||||||
|
|
||||||
if "\\" in playurl:
|
if "\\" in playurl:
|
||||||
# Local path
|
# Local path
|
||||||
|
|
|
@ -27,7 +27,7 @@ import PlexFunctions
|
||||||
##################################################################################################
|
##################################################################################################
|
||||||
|
|
||||||
|
|
||||||
@utils.ThreadMethodsStopsync
|
@utils.ThreadMethodsAdditionalStop('emby_shouldStop')
|
||||||
@utils.ThreadMethods
|
@utils.ThreadMethods
|
||||||
class ThreadedGetMetadata(threading.Thread):
|
class ThreadedGetMetadata(threading.Thread):
|
||||||
"""
|
"""
|
||||||
|
@ -41,10 +41,11 @@ class ThreadedGetMetadata(threading.Thread):
|
||||||
the downloaded metadata XMLs as etree objects
|
the downloaded metadata XMLs as etree objects
|
||||||
lock threading.Lock(), used for counting where we are
|
lock threading.Lock(), used for counting where we are
|
||||||
"""
|
"""
|
||||||
def __init__(self, queue, out_queue, lock):
|
def __init__(self, queue, out_queue, lock, errorQueue):
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.out_queue = out_queue
|
self.out_queue = out_queue
|
||||||
self.lock = lock
|
self.lock = lock
|
||||||
|
self.errorQueue = errorQueue
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
@ -54,35 +55,41 @@ class ThreadedGetMetadata(threading.Thread):
|
||||||
lock = self.lock
|
lock = self.lock
|
||||||
threadStopped = self.threadStopped
|
threadStopped = self.threadStopped
|
||||||
global getMetadataCount
|
global getMetadataCount
|
||||||
while threadStopped() is False:
|
try:
|
||||||
# grabs Plex item from queue
|
while threadStopped() is False:
|
||||||
try:
|
# grabs Plex item from queue
|
||||||
updateItem = queue.get(block=False)
|
try:
|
||||||
# Empty queue
|
updateItem = queue.get(block=False)
|
||||||
except Queue.Empty:
|
# Empty queue
|
||||||
continue
|
except Queue.Empty:
|
||||||
# Download Metadata
|
continue
|
||||||
try:
|
# Download Metadata
|
||||||
plexXML = PlexFunctions.GetPlexMetadata(updateItem['itemId'])
|
plexXML = PlexFunctions.GetPlexMetadata(updateItem['itemId'])
|
||||||
except:
|
try:
|
||||||
raise
|
plexXML.tag
|
||||||
# check whether valid XML
|
except:
|
||||||
if plexXML:
|
# Did not receive a valid XML - skip that one for now
|
||||||
|
queue.task_done()
|
||||||
|
continue
|
||||||
|
# Get rid of first XML level:
|
||||||
|
|
||||||
updateItem['XML'] = plexXML
|
updateItem['XML'] = plexXML
|
||||||
# place item into out queue
|
# place item into out queue
|
||||||
out_queue.put(updateItem)
|
out_queue.put(updateItem)
|
||||||
del plexXML
|
del plexXML
|
||||||
del updateItem
|
del updateItem
|
||||||
# If we don't have a valid XML, don't put that into the queue
|
# If we don't have a valid XML, don't put that into the queue
|
||||||
# but skip this item for now
|
# but skip this item for now
|
||||||
# Keep track of where we are at
|
# Keep track of where we are at
|
||||||
with lock:
|
with lock:
|
||||||
getMetadataCount += 1
|
getMetadataCount += 1
|
||||||
# signals to queue job is done
|
# signals to queue job is done
|
||||||
queue.task_done()
|
queue.task_done()
|
||||||
|
except:
|
||||||
|
self.errorQueue.put(sys.exc_info())
|
||||||
|
|
||||||
|
|
||||||
@utils.ThreadMethodsStopsync
|
@utils.ThreadMethodsAdditionalStop('emby_shouldStop')
|
||||||
@utils.ThreadMethods
|
@utils.ThreadMethods
|
||||||
class ThreadedProcessMetadata(threading.Thread):
|
class ThreadedProcessMetadata(threading.Thread):
|
||||||
"""
|
"""
|
||||||
|
@ -96,10 +103,11 @@ class ThreadedProcessMetadata(threading.Thread):
|
||||||
e.g. 'Movies' => itemtypes.Movies()
|
e.g. 'Movies' => itemtypes.Movies()
|
||||||
lock: threading.Lock(), used for counting where we are
|
lock: threading.Lock(), used for counting where we are
|
||||||
"""
|
"""
|
||||||
def __init__(self, queue, itemType, lock):
|
def __init__(self, queue, itemType, lock, errorQueue):
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.lock = lock
|
self.lock = lock
|
||||||
self.itemType = itemType
|
self.itemType = itemType
|
||||||
|
self.errorQueue = errorQueue
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
@ -111,35 +119,40 @@ class ThreadedProcessMetadata(threading.Thread):
|
||||||
threadStopped = self.threadStopped
|
threadStopped = self.threadStopped
|
||||||
global processMetadataCount
|
global processMetadataCount
|
||||||
global processingViewName
|
global processingViewName
|
||||||
with itemFkt() as item:
|
try:
|
||||||
while threadStopped() is False:
|
with itemFkt() as item:
|
||||||
# grabs item from queue
|
while threadStopped() is False:
|
||||||
try:
|
# grabs item from queue
|
||||||
updateItem = queue.get(block=False)
|
try:
|
||||||
# Empty queue
|
updateItem = queue.get(block=False)
|
||||||
except Queue.Empty:
|
# Empty queue
|
||||||
continue
|
except Queue.Empty:
|
||||||
# Do the work; lock to be sure we've only got 1 Thread
|
continue
|
||||||
plexitem = updateItem['XML']
|
# Do the work; lock to be sure we've only got 1 Thread
|
||||||
method = updateItem['method']
|
plexitem = updateItem['XML']
|
||||||
viewName = updateItem['viewName']
|
method = updateItem['method']
|
||||||
viewId = updateItem['viewId']
|
viewName = updateItem['viewName']
|
||||||
title = updateItem['title']
|
viewId = updateItem['viewId']
|
||||||
itemSubFkt = getattr(item, method)
|
title = updateItem['title']
|
||||||
with lock:
|
itemSubFkt = getattr(item, method)
|
||||||
itemSubFkt(plexitem,
|
with lock:
|
||||||
viewtag=viewName,
|
itemSubFkt(plexitem,
|
||||||
viewid=viewId)
|
viewtag=viewName,
|
||||||
# Keep track of where we are at
|
viewid=viewId)
|
||||||
processMetadataCount += 1
|
# Keep track of where we are at
|
||||||
processingViewName = title
|
processMetadataCount += 1
|
||||||
del plexitem
|
processingViewName = title
|
||||||
del updateItem
|
del plexitem
|
||||||
# signals to queue job is done
|
del updateItem
|
||||||
self.queue.task_done()
|
# signals to queue job is done
|
||||||
|
self.queue.task_done()
|
||||||
|
except:
|
||||||
|
xbmc.log('An error occured')
|
||||||
|
xbmc.log(sys.exc_info())
|
||||||
|
self.errorQueue.put(sys.exc_info())
|
||||||
|
|
||||||
|
|
||||||
@utils.ThreadMethodsStopsync
|
@utils.ThreadMethodsAdditionalStop('emby_shouldStop')
|
||||||
@utils.ThreadMethods
|
@utils.ThreadMethods
|
||||||
class ThreadedShowSyncInfo(threading.Thread):
|
class ThreadedShowSyncInfo(threading.Thread):
|
||||||
"""
|
"""
|
||||||
|
@ -184,10 +197,15 @@ class ThreadedShowSyncInfo(threading.Thread):
|
||||||
percentage = int(float(totalProgress) / float(total)*100.0)
|
percentage = int(float(totalProgress) / float(total)*100.0)
|
||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
percentage = 0
|
percentage = 0
|
||||||
dialog.update(percentage,
|
try:
|
||||||
message="Downloaded: %s, Processed: %s: %s"
|
dialog.update(
|
||||||
% (getMetadataProgress,
|
percentage,
|
||||||
processMetadataProgress, viewName))
|
message="Downloaded: %s, Processed: %s: %s"
|
||||||
|
% (getMetadataProgress, processMetadataProgress,
|
||||||
|
viewName))
|
||||||
|
except:
|
||||||
|
# Unicode formating of the string?!?
|
||||||
|
pass
|
||||||
# Sleep for x milliseconds
|
# Sleep for x milliseconds
|
||||||
xbmc.sleep(500)
|
xbmc.sleep(500)
|
||||||
dialog.close()
|
dialog.close()
|
||||||
|
@ -195,7 +213,7 @@ class ThreadedShowSyncInfo(threading.Thread):
|
||||||
|
|
||||||
@utils.logging
|
@utils.logging
|
||||||
@utils.ThreadMethodsAdditionalSuspend('suspend_LibraryThread')
|
@utils.ThreadMethodsAdditionalSuspend('suspend_LibraryThread')
|
||||||
@utils.ThreadMethodsStopsync
|
@utils.ThreadMethodsAdditionalStop('emby_shouldStop')
|
||||||
@utils.ThreadMethods
|
@utils.ThreadMethods
|
||||||
class LibrarySync(threading.Thread):
|
class LibrarySync(threading.Thread):
|
||||||
|
|
||||||
|
@ -213,6 +231,9 @@ class LibrarySync(threading.Thread):
|
||||||
|
|
||||||
self.__dict__ = self._shared_state
|
self.__dict__ = self._shared_state
|
||||||
|
|
||||||
|
# How long should we look into the past for fast syncing items (in s)
|
||||||
|
self.syncPast = 60
|
||||||
|
|
||||||
self.clientInfo = clientinfo.ClientInfo()
|
self.clientInfo = clientinfo.ClientInfo()
|
||||||
self.doUtils = downloadutils.DownloadUtils()
|
self.doUtils = downloadutils.DownloadUtils()
|
||||||
self.user = userclient.UserClient()
|
self.user = userclient.UserClient()
|
||||||
|
@ -257,13 +278,14 @@ class LibrarySync(threading.Thread):
|
||||||
"""
|
"""
|
||||||
self.compare = True
|
self.compare = True
|
||||||
# Get last sync time
|
# Get last sync time
|
||||||
lastSync = utils.window('LastIncrementalSync')
|
lastSync = self.lastSync - self.syncPast
|
||||||
if not lastSync:
|
if not lastSync:
|
||||||
# Original Emby format:
|
# Original Emby format:
|
||||||
# lastSync = "2016-01-01T00:00:00Z"
|
# lastSync = "2016-01-01T00:00:00Z"
|
||||||
# January 1, 2015 at midnight:
|
# January 1, 2015 at midnight:
|
||||||
lastSync = '1420070400'
|
lastSync = 1420070400
|
||||||
self.logMsg("Last sync run: %s" % lastSync, 1)
|
# Set new timestamp NOW because sync might take a while
|
||||||
|
self.saveLastSync()
|
||||||
|
|
||||||
# Get all PMS items already saved in Kodi
|
# Get all PMS items already saved in Kodi
|
||||||
embyconn = utils.kodiSQL('emby')
|
embyconn = utils.kodiSQL('emby')
|
||||||
|
@ -287,7 +309,8 @@ class LibrarySync(threading.Thread):
|
||||||
if self.threadStopped():
|
if self.threadStopped():
|
||||||
return True
|
return True
|
||||||
# Get items per view
|
# Get items per view
|
||||||
items = PlexFunctions.GetPlexUpdatedItems(view['id'], lastSync)
|
items = PlexFunctions.GetAllPlexLeaves(
|
||||||
|
view['id'], updatedAt=lastSync)
|
||||||
if not items:
|
if not items:
|
||||||
continue
|
continue
|
||||||
# Get one itemtype, because they're the same in the PMS section
|
# Get one itemtype, because they're the same in the PMS section
|
||||||
|
@ -311,18 +334,16 @@ class LibrarySync(threading.Thread):
|
||||||
for view in self.views:
|
for view in self.views:
|
||||||
self.PlexUpdateWatched(
|
self.PlexUpdateWatched(
|
||||||
view['id'],
|
view['id'],
|
||||||
PlexFunctions.GetItemClassFromType(view['itemtype']))
|
PlexFunctions.GetItemClassFromType(view['itemtype']),
|
||||||
|
lastViewedAt=lastSync)
|
||||||
# Reset and return
|
# Reset and return
|
||||||
self.saveLastSync()
|
|
||||||
self.allKodiElementsId = {}
|
self.allKodiElementsId = {}
|
||||||
self.allPlexElementsId = {}
|
self.allPlexElementsId = {}
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def saveLastSync(self):
|
def saveLastSync(self):
|
||||||
# Save last sync time
|
# Save last sync time
|
||||||
lastSync = str(utils.getUnixTimestamp())
|
self.lastSync = utils.getUnixTimestamp()
|
||||||
self.logMsg("New sync time: %s" % lastSync, 1)
|
|
||||||
utils.window('LastIncrementalSync', value=lastSync)
|
|
||||||
|
|
||||||
def initializeDBs(self):
|
def initializeDBs(self):
|
||||||
"""
|
"""
|
||||||
|
@ -348,7 +369,7 @@ class LibrarySync(threading.Thread):
|
||||||
|
|
||||||
def fullSync(self, manualrun=False, repair=False):
|
def fullSync(self, manualrun=False, repair=False):
|
||||||
# Only run once when first setting up. Can be run manually.
|
# Only run once when first setting up. Can be run manually.
|
||||||
self.compare = manualrun
|
self.compare = manualrun or repair
|
||||||
music_enabled = utils.settings('enableMusic') == "true"
|
music_enabled = utils.settings('enableMusic') == "true"
|
||||||
|
|
||||||
# Add sources
|
# Add sources
|
||||||
|
@ -361,7 +382,8 @@ class LibrarySync(threading.Thread):
|
||||||
else:
|
else:
|
||||||
message = "Initial sync"
|
message = "Initial sync"
|
||||||
utils.window('emby_initialScan', value="true")
|
utils.window('emby_initialScan', value="true")
|
||||||
|
# Set new timestamp NOW because sync might take a while
|
||||||
|
self.saveLastSync()
|
||||||
starttotal = datetime.now()
|
starttotal = datetime.now()
|
||||||
|
|
||||||
# Ensure that DBs exist if called for very first time
|
# Ensure that DBs exist if called for very first time
|
||||||
|
@ -418,7 +440,6 @@ class LibrarySync(threading.Thread):
|
||||||
# musiccursor.close()
|
# musiccursor.close()
|
||||||
|
|
||||||
xbmc.executebuiltin('UpdateLibrary(video)')
|
xbmc.executebuiltin('UpdateLibrary(video)')
|
||||||
self.saveLastSync()
|
|
||||||
elapsedtotal = datetime.now() - starttotal
|
elapsedtotal = datetime.now() - starttotal
|
||||||
|
|
||||||
utils.window('emby_initialScan', clear=True)
|
utils.window('emby_initialScan', clear=True)
|
||||||
|
@ -572,7 +593,7 @@ class LibrarySync(threading.Thread):
|
||||||
|
|
||||||
Output: self.updatelist, self.allPlexElementsId
|
Output: self.updatelist, self.allPlexElementsId
|
||||||
self.updatelist APPENDED(!!) list itemids (Plex Keys as
|
self.updatelist APPENDED(!!) list itemids (Plex Keys as
|
||||||
as received from API.getKey())
|
as received from API.getRatingKey())
|
||||||
One item in this list is of the form:
|
One item in this list is of the form:
|
||||||
'itemId': xxx,
|
'itemId': xxx,
|
||||||
'itemType': 'Movies','TVShows', ...
|
'itemType': 'Movies','TVShows', ...
|
||||||
|
@ -594,7 +615,7 @@ class LibrarySync(threading.Thread):
|
||||||
return False
|
return False
|
||||||
API = PlexAPI.API(item)
|
API = PlexAPI.API(item)
|
||||||
plex_checksum = API.getChecksum()
|
plex_checksum = API.getChecksum()
|
||||||
itemId = API.getKey()
|
itemId = API.getRatingKey()
|
||||||
title, sorttitle = API.getTitle()
|
title, sorttitle = API.getTitle()
|
||||||
self.allPlexElementsId[itemId] = plex_checksum
|
self.allPlexElementsId[itemId] = plex_checksum
|
||||||
kodi_checksum = self.allKodiElementsId.get(itemId)
|
kodi_checksum = self.allKodiElementsId.get(itemId)
|
||||||
|
@ -616,7 +637,7 @@ class LibrarySync(threading.Thread):
|
||||||
if self.threadStopped():
|
if self.threadStopped():
|
||||||
return False
|
return False
|
||||||
API = PlexAPI.API(item)
|
API = PlexAPI.API(item)
|
||||||
itemId = API.getKey()
|
itemId = API.getRatingKey()
|
||||||
title, sorttitle = API.getTitle()
|
title, sorttitle = API.getTitle()
|
||||||
plex_checksum = API.getChecksum()
|
plex_checksum = API.getChecksum()
|
||||||
self.allPlexElementsId[itemId] = plex_checksum
|
self.allPlexElementsId[itemId] = plex_checksum
|
||||||
|
@ -648,6 +669,7 @@ class LibrarySync(threading.Thread):
|
||||||
self.logMsg("Starting sync threads", 1)
|
self.logMsg("Starting sync threads", 1)
|
||||||
getMetadataQueue = Queue.Queue()
|
getMetadataQueue = Queue.Queue()
|
||||||
processMetadataQueue = Queue.Queue(maxsize=100)
|
processMetadataQueue = Queue.Queue(maxsize=100)
|
||||||
|
errorQueue = Queue.Queue()
|
||||||
getMetadataLock = threading.Lock()
|
getMetadataLock = threading.Lock()
|
||||||
processMetadataLock = threading.Lock()
|
processMetadataLock = threading.Lock()
|
||||||
# To keep track
|
# To keep track
|
||||||
|
@ -665,20 +687,12 @@ class LibrarySync(threading.Thread):
|
||||||
for i in range(min(self.syncThreadNumber, itemNumber)):
|
for i in range(min(self.syncThreadNumber, itemNumber)):
|
||||||
thread = ThreadedGetMetadata(getMetadataQueue,
|
thread = ThreadedGetMetadata(getMetadataQueue,
|
||||||
processMetadataQueue,
|
processMetadataQueue,
|
||||||
getMetadataLock)
|
getMetadataLock,
|
||||||
|
errorQueue)
|
||||||
thread.setDaemon(True)
|
thread.setDaemon(True)
|
||||||
thread.start()
|
thread.start()
|
||||||
threads.append(thread)
|
threads.append(thread)
|
||||||
self.logMsg("Download threads spawned", 1)
|
self.logMsg("Download threads spawned", 1)
|
||||||
# Spawn one more thread to process Metadata, once downloaded
|
|
||||||
thread = ThreadedProcessMetadata(processMetadataQueue,
|
|
||||||
itemType,
|
|
||||||
processMetadataLock)
|
|
||||||
thread.setDaemon(True)
|
|
||||||
thread.start()
|
|
||||||
threads.append(thread)
|
|
||||||
self.logMsg("Processing thread spawned", 1)
|
|
||||||
|
|
||||||
# Start one thread to show sync progress
|
# Start one thread to show sync progress
|
||||||
dialog = xbmcgui.DialogProgressBG()
|
dialog = xbmcgui.DialogProgressBG()
|
||||||
thread = ThreadedShowSyncInfo(dialog,
|
thread = ThreadedShowSyncInfo(dialog,
|
||||||
|
@ -689,9 +703,32 @@ class LibrarySync(threading.Thread):
|
||||||
thread.start()
|
thread.start()
|
||||||
threads.append(thread)
|
threads.append(thread)
|
||||||
self.logMsg("Kodi Infobox thread spawned", 1)
|
self.logMsg("Kodi Infobox thread spawned", 1)
|
||||||
|
# Spawn one more thread to process Metadata, once downloaded
|
||||||
|
thread = ThreadedProcessMetadata(processMetadataQueue,
|
||||||
|
itemType,
|
||||||
|
processMetadataLock,
|
||||||
|
errorQueue)
|
||||||
|
thread.setDaemon(True)
|
||||||
|
thread.start()
|
||||||
|
threads.append(thread)
|
||||||
|
self.logMsg("Processing thread spawned", 1)
|
||||||
|
|
||||||
# Wait until finished
|
# Wait until finished
|
||||||
getMetadataQueue.join()
|
while True:
|
||||||
processMetadataQueue.join()
|
try:
|
||||||
|
exc = errorQueue.get(block=False)
|
||||||
|
except Queue.Empty:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
exc_type, exc_obj, exc_trace = exc
|
||||||
|
# deal with the exception
|
||||||
|
self.logMsg("Error occured in thread", -1)
|
||||||
|
self.logMsg(str(exc_type) + str(exc_obj), -1)
|
||||||
|
self.logMsg(exc_trace, -1)
|
||||||
|
if getMetadataQueue.empty() and processMetadataQueue.empty():
|
||||||
|
break
|
||||||
|
xbmc.sleep(500)
|
||||||
|
|
||||||
# Kill threads
|
# Kill threads
|
||||||
self.logMsg("Waiting to kill threads", 1)
|
self.logMsg("Waiting to kill threads", 1)
|
||||||
for thread in threads:
|
for thread in threads:
|
||||||
|
@ -770,24 +807,24 @@ class LibrarySync(threading.Thread):
|
||||||
self.logMsg("%s sync is finished." % itemType, 1)
|
self.logMsg("%s sync is finished." % itemType, 1)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def PlexUpdateWatched(self, viewId, itemType):
|
def PlexUpdateWatched(self, viewId, itemType,
|
||||||
|
lastViewedAt=None, updatedAt=None):
|
||||||
"""
|
"""
|
||||||
Updates ALL plex elements' view status ('watched' or 'unwatched') and
|
Updates plex elements' view status ('watched' or 'unwatched') and
|
||||||
also updates resume times.
|
also updates resume times.
|
||||||
This is done by downloading one XML for ALL elements with viewId
|
This is done by downloading one XML for ALL elements with viewId
|
||||||
"""
|
"""
|
||||||
starttotal = datetime.now()
|
|
||||||
# Download XML, not JSON, because PMS JSON seems to be damaged
|
# Download XML, not JSON, because PMS JSON seems to be damaged
|
||||||
headerOptions = {'Accept': 'application/xml'}
|
headerOptions = {'Accept': 'application/xml'}
|
||||||
plexItems = PlexFunctions.GetAllPlexLeaves(
|
plexItems = PlexFunctions.GetAllPlexLeaves(
|
||||||
viewId, headerOptions=headerOptions)
|
viewId,
|
||||||
itemMth = getattr(itemtypes, itemType)
|
lastViewedAt=lastViewedAt,
|
||||||
with itemMth() as method:
|
updatedAt=updatedAt,
|
||||||
method.updateUserdata(plexItems)
|
headerOptions=headerOptions)
|
||||||
|
if plexItems:
|
||||||
elapsedtotal = datetime.now() - starttotal
|
itemMth = getattr(itemtypes, itemType)
|
||||||
self.logMsg("Syncing userdata for itemtype %s and viewid %s took "
|
with itemMth() as method:
|
||||||
"%s seconds" % (itemType, viewId, elapsedtotal), 1)
|
method.updateUserdata(plexItems)
|
||||||
|
|
||||||
def musicvideos(self, embycursor, kodicursor, pdialog):
|
def musicvideos(self, embycursor, kodicursor, pdialog):
|
||||||
# Get musicvideos from emby
|
# Get musicvideos from emby
|
||||||
|
|
|
@ -15,6 +15,7 @@ import playutils as putils
|
||||||
import playlist
|
import playlist
|
||||||
import read_embyserver as embyserver
|
import read_embyserver as embyserver
|
||||||
import utils
|
import utils
|
||||||
|
import embydb_functions
|
||||||
|
|
||||||
import PlexAPI
|
import PlexAPI
|
||||||
|
|
||||||
|
@ -23,12 +24,10 @@ import PlexAPI
|
||||||
|
|
||||||
@utils.logging
|
@utils.logging
|
||||||
class PlaybackUtils():
|
class PlaybackUtils():
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, item):
|
def __init__(self, item):
|
||||||
|
|
||||||
self.item = item
|
self.item = item
|
||||||
self.API = PlexAPI.API(self.item)
|
|
||||||
|
|
||||||
self.doUtils = downloadutils.DownloadUtils()
|
self.doUtils = downloadutils.DownloadUtils()
|
||||||
|
|
||||||
|
@ -40,21 +39,132 @@ class PlaybackUtils():
|
||||||
self.emby = embyserver.Read_EmbyServer()
|
self.emby = embyserver.Read_EmbyServer()
|
||||||
self.pl = playlist.Playlist()
|
self.pl = playlist.Playlist()
|
||||||
|
|
||||||
def play(self, itemid, dbid=None, seektime=None):
|
def StartPlay(self, resume=None, resumeItem=None):
|
||||||
|
self.logMsg("StartPlay called with resume=%s, resumeItem=%s"
|
||||||
|
% (resume, resumeItem), 1)
|
||||||
|
# Setup Kodi playlist (e.g. make new one or append or even update)
|
||||||
|
# Why should we have different behaviour if user is on home screen?!?
|
||||||
|
# self.homeScreen = xbmc.getCondVisibility('Window.IsActive(home)')
|
||||||
|
self.playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO)
|
||||||
|
# Clear playlist since we're always using PMS playQueues
|
||||||
|
self.playlist.clear()
|
||||||
|
|
||||||
self.logMsg("Play called with itemid: %s, dbid: %s, seektime: %s."
|
self.startPos = max(self.playlist.getposition(), 0) # Can return -1
|
||||||
% (itemid, dbid, seektime), 1)
|
self.sizePlaylist = self.playlist.size()
|
||||||
|
self.currentPosition = self.startPos
|
||||||
|
self.logMsg("Playlist start position: %s" % self.startPos, 1)
|
||||||
|
self.logMsg("Playlist position we're starting with: %s"
|
||||||
|
% self.currentPosition, 1)
|
||||||
|
self.logMsg("Playlist size: %s" % self.sizePlaylist, 1)
|
||||||
|
|
||||||
doUtils = self.doUtils
|
self.plexResumeItemId = resumeItem
|
||||||
item = self.item
|
# Where should we ultimately start playback?
|
||||||
API = self.API
|
self.resumePost = self.startPos
|
||||||
|
|
||||||
|
if resume:
|
||||||
|
if resume == '0':
|
||||||
|
resume = None
|
||||||
|
else:
|
||||||
|
resume = int(resume)
|
||||||
|
|
||||||
|
# Run through the passed PMS playlist and construct playlist
|
||||||
|
for mediaItem in self.item:
|
||||||
|
self.AddMediaItemToPlaylist(mediaItem)
|
||||||
|
# Kick off playback
|
||||||
|
Player = xbmc.Player()
|
||||||
|
Player.play(self.playlist, startpos=self.resumePost)
|
||||||
|
if resume:
|
||||||
|
try:
|
||||||
|
Player.seekTime(resume)
|
||||||
|
except:
|
||||||
|
self.logMsg("Could not use resume: %s. Start from beginning."
|
||||||
|
% resume, 0)
|
||||||
|
|
||||||
|
def AddMediaItemToPlaylist(self, item):
|
||||||
|
"""
|
||||||
|
Feed with ONE media item from PMS json response
|
||||||
|
(on level with e.g. key=/library/metadata/220493 present)
|
||||||
|
|
||||||
|
An item may consist of several parts (e.g. movie in 2 pieces/files)
|
||||||
|
"""
|
||||||
|
API = PlexAPI.API(item)
|
||||||
|
playutils = putils.PlayUtils(item)
|
||||||
|
|
||||||
|
# e.g. itemid='219155'
|
||||||
|
itemid = API.getRatingKey()
|
||||||
|
# Get DB id from Kodi by using plex id, if that works
|
||||||
|
embyconn = utils.kodiSQL('emby')
|
||||||
|
embycursor = embyconn.cursor()
|
||||||
|
emby = embydb_functions.Embydb_Functions(embycursor)
|
||||||
|
try:
|
||||||
|
dbid = emby.getItem_byId(itemid)[0]
|
||||||
|
except TypeError:
|
||||||
|
# Trailers and the like that are not in the kodi DB
|
||||||
|
dbid = None
|
||||||
|
embyconn.close()
|
||||||
|
|
||||||
|
# Get playurls per part and process them
|
||||||
|
for playurl in playutils.getPlayUrl():
|
||||||
|
# One new listitem per part
|
||||||
|
listitem = xbmcgui.ListItem()
|
||||||
|
# For items that are not (yet) synced to Kodi lib, e.g. trailers
|
||||||
|
if not dbid:
|
||||||
|
self.logMsg("Add item to playlist without Kodi DB id", 1)
|
||||||
|
# Add Plex credentials to url because Kodi will have no headers
|
||||||
|
playurl = API.addPlexCredentialsToUrl(playurl)
|
||||||
|
listitem.setPath(playurl)
|
||||||
|
self.setProperties(playurl, listitem)
|
||||||
|
# Set artwork already done in setProperties
|
||||||
|
self.playlist.add(
|
||||||
|
playurl, listitem, index=self.currentPosition)
|
||||||
|
self.currentPosition += 1
|
||||||
|
else:
|
||||||
|
self.logMsg("Add item to playlist with existing Kodi DB id", 1)
|
||||||
|
self.pl.addtoPlaylist(dbid, API.getType())
|
||||||
|
self.currentPosition += 1
|
||||||
|
|
||||||
|
# For transcoding only, ask for audio/subs pref
|
||||||
|
if utils.window('emby_%s.playmethod' % playurl) == "Transcode":
|
||||||
|
playurl = playutils.audioSubsPref(playurl, listitem)
|
||||||
|
utils.window('emby_%s.playmethod' % playurl, value="Transcode")
|
||||||
|
|
||||||
|
playQueueItemID = API.GetPlayQueueItemID()
|
||||||
|
# Is this the position where we should start playback?
|
||||||
|
if playQueueItemID == self.plexResumeItemId:
|
||||||
|
self.logMsg(
|
||||||
|
"Figure we should start playback at position %s "
|
||||||
|
"with playQueueItemID %s"
|
||||||
|
% (self.currentPosition, playQueueItemID), 2)
|
||||||
|
self.resumePost = self.currentPosition
|
||||||
|
# We need to keep track of playQueueItemIDs for Plex Companion
|
||||||
|
utils.window(
|
||||||
|
'plex_%s.playQueueItemID' % playurl, API.GetPlayQueueItemID())
|
||||||
|
utils.window(
|
||||||
|
'plex_%s.playlistPosition' % playurl, self.currentPosition)
|
||||||
|
|
||||||
|
# Log the playlist that we end up with
|
||||||
|
self.pl.verifyPlaylist()
|
||||||
|
|
||||||
|
def play(self, item):
|
||||||
|
|
||||||
|
API = PlexAPI.API(item)
|
||||||
listitem = xbmcgui.ListItem()
|
listitem = xbmcgui.ListItem()
|
||||||
playutils = putils.PlayUtils(item)
|
playutils = putils.PlayUtils(item)
|
||||||
|
|
||||||
# Set child number to the very last one, because that's what we want
|
# e.g. itemid='219155'
|
||||||
# to play ultimately
|
itemid = API.getRatingKey()
|
||||||
API.setChildNumber(-1)
|
# Get DB id from Kodi by using plex id, if that works
|
||||||
playurl = playutils.getPlayUrl(child=-1)
|
embyconn = utils.kodiSQL('emby')
|
||||||
|
embycursor = embyconn.cursor()
|
||||||
|
emby = embydb_functions.Embydb_Functions(embycursor)
|
||||||
|
try:
|
||||||
|
dbid = emby.getItem_byId(itemid)[0]
|
||||||
|
except TypeError:
|
||||||
|
# Trailers and the like that are not in the kodi DB
|
||||||
|
dbid = None
|
||||||
|
embyconn.close()
|
||||||
|
|
||||||
|
playurl = playutils.getPlayUrl()
|
||||||
if not playurl:
|
if not playurl:
|
||||||
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
|
return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem)
|
||||||
|
|
||||||
|
@ -82,10 +192,9 @@ class PlaybackUtils():
|
||||||
self.logMsg("Playlist size: %s" % sizePlaylist, 1)
|
self.logMsg("Playlist size: %s" % sizePlaylist, 1)
|
||||||
|
|
||||||
############### RESUME POINT ################
|
############### RESUME POINT ################
|
||||||
|
|
||||||
if seektime is None:
|
userdata = API.getUserData()
|
||||||
userdata = API.getUserData()
|
seektime = userdata['Resume']
|
||||||
seektime = userdata['Resume']
|
|
||||||
|
|
||||||
# We need to ensure we add the intro and additional parts only once.
|
# We need to ensure we add the intro and additional parts only once.
|
||||||
# Otherwise we get a loop.
|
# Otherwise we get a loop.
|
||||||
|
@ -132,7 +241,7 @@ class PlaybackUtils():
|
||||||
self.pl.insertintoPlaylist(currentPosition, url=introPlayurl)
|
self.pl.insertintoPlaylist(currentPosition, url=introPlayurl)
|
||||||
introsPlaylist = True
|
introsPlaylist = True
|
||||||
currentPosition += 1
|
currentPosition += 1
|
||||||
self.logMsg("Key: %s" % API.getKey(), 1)
|
self.logMsg("Key: %s" % API.getRatingKey(), 1)
|
||||||
self.logMsg("Successfally added trailer number %s" % i, 1)
|
self.logMsg("Successfally added trailer number %s" % i, 1)
|
||||||
# Set "working point" to the movie (last one in playlist)
|
# Set "working point" to the movie (last one in playlist)
|
||||||
API.setChildNumber(-1)
|
API.setChildNumber(-1)
|
||||||
|
@ -220,7 +329,7 @@ class PlaybackUtils():
|
||||||
# Set all properties necessary for plugin path playback
|
# Set all properties necessary for plugin path playback
|
||||||
item = self.item
|
item = self.item
|
||||||
# itemid = item['Id']
|
# itemid = item['Id']
|
||||||
itemid = self.API.getKey()
|
itemid = self.API.getRatingKey()
|
||||||
# itemtype = item['Type']
|
# itemtype = item['Type']
|
||||||
itemtype = self.API.getType()
|
itemtype = self.API.getType()
|
||||||
resume, runtime = self.API.getRuntime()
|
resume, runtime = self.API.getRuntime()
|
||||||
|
@ -230,8 +339,9 @@ class PlaybackUtils():
|
||||||
utils.window('%s.type' % embyitem, value=itemtype)
|
utils.window('%s.type' % embyitem, value=itemtype)
|
||||||
utils.window('%s.itemid' % embyitem, value=itemid)
|
utils.window('%s.itemid' % embyitem, value=itemid)
|
||||||
|
|
||||||
if itemtype == "Episode":
|
if itemtype == "episode":
|
||||||
utils.window('%s.refreshid' % embyitem, value=item.get('SeriesId'))
|
utils.window('%s.refreshid' % embyitem,
|
||||||
|
value=item.get('parentRatingKey'))
|
||||||
else:
|
else:
|
||||||
utils.window('%s.refreshid' % embyitem, value=itemid)
|
utils.window('%s.refreshid' % embyitem, value=itemid)
|
||||||
|
|
||||||
|
@ -282,10 +392,6 @@ class PlaybackUtils():
|
||||||
return externalsubs
|
return externalsubs
|
||||||
|
|
||||||
def setArtwork(self, listItem):
|
def setArtwork(self, listItem):
|
||||||
# Set up item and item info
|
|
||||||
item = self.item
|
|
||||||
artwork = self.artwork
|
|
||||||
|
|
||||||
# allartwork = artwork.getAllArtwork(item, parentInfo=True)
|
# allartwork = artwork.getAllArtwork(item, parentInfo=True)
|
||||||
allartwork = self.API.getAllArtwork(parentInfo=True)
|
allartwork = self.API.getAllArtwork(parentInfo=True)
|
||||||
# Set artwork for listitem
|
# Set artwork for listitem
|
||||||
|
|
|
@ -21,6 +21,7 @@ class PlayUtils():
|
||||||
def __init__(self, item):
|
def __init__(self, item):
|
||||||
|
|
||||||
self.item = item
|
self.item = item
|
||||||
|
self.API = PlexAPI.API(item)
|
||||||
|
|
||||||
self.clientInfo = clientinfo.ClientInfo()
|
self.clientInfo = clientinfo.ClientInfo()
|
||||||
|
|
||||||
|
@ -28,54 +29,44 @@ class PlayUtils():
|
||||||
self.server = utils.window('emby_server%s' % self.userid)
|
self.server = utils.window('emby_server%s' % self.userid)
|
||||||
self.machineIdentifier = utils.window('plex_machineIdentifier')
|
self.machineIdentifier = utils.window('plex_machineIdentifier')
|
||||||
|
|
||||||
self.API = PlexAPI.API(item)
|
def getPlayUrl(self):
|
||||||
|
"""
|
||||||
|
Returns a list of playurls, one per part in item
|
||||||
|
"""
|
||||||
|
playurls = []
|
||||||
|
# TODO: multiple media parts for e.g. trailers: replace [0] here
|
||||||
|
partCount = len(self.item['_children'][0]['_children'])
|
||||||
|
for partNumber in range(partCount):
|
||||||
|
playurl = None
|
||||||
|
self.API.setPartNumber(partNumber)
|
||||||
|
|
||||||
def getPlayUrl(self, child=0, partIndex=None):
|
if self.isDirectPlay():
|
||||||
item = self.item
|
self.logMsg("File is direct playing.", 1)
|
||||||
# NO, I am not very fond of this construct!
|
playurl = self.API.getTranscodeVideoPath('DirectPlay')
|
||||||
self.API.setChildNumber(child)
|
playurl = playurl.encode('utf-8')
|
||||||
if partIndex is not None:
|
# Set playmethod property
|
||||||
self.API.setPartNumber(partIndex)
|
utils.window('emby_%s.playmethod' % playurl, "DirectPlay")
|
||||||
playurl = None
|
|
||||||
|
|
||||||
if item.get('Type') in ["Recording","TvChannel"] and item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http":
|
|
||||||
#Is this the right way to play a Live TV or recordings ?
|
|
||||||
self.logMsg("File protocol is http (livetv).", 1)
|
|
||||||
playurl = "%s/emby/Videos/%s/live.m3u8?static=true" % (self.server, item['Id'])
|
|
||||||
utils.window('emby_%s.playmethod' % playurl, value="DirectPlay")
|
|
||||||
|
|
||||||
# if item.get('MediaSources') and item['MediaSources'][0]['Protocol'] == "Http":
|
elif self.isDirectStream():
|
||||||
# # Only play as http
|
self.logMsg("File is direct streaming.", 1)
|
||||||
# self.logMsg("File protocol is http.", 1)
|
playurl = self.API.getTranscodeVideoPath('DirectStream')
|
||||||
# playurl = self.httpPlay()
|
# Set playmethod property
|
||||||
# utils.window('emby_%s.playmethod' % playurl, value="DirectStream")
|
utils.window('emby_%s.playmethod' % playurl, "DirectStream")
|
||||||
|
|
||||||
if self.isDirectPlay():
|
elif self.isTranscoding():
|
||||||
self.logMsg("File is direct playing.", 1)
|
self.logMsg("File is transcoding.", 1)
|
||||||
playurl = self.API.getTranscodeVideoPath('DirectPlay')
|
quality = {
|
||||||
playurl = playurl.encode('utf-8')
|
'maxVideoBitrate': self.getBitrate()
|
||||||
# Set playmethod property
|
}
|
||||||
utils.window('emby_%s.playmethod' % playurl, value="DirectPlay")
|
playurl = self.API.getTranscodeVideoPath('Transcode',
|
||||||
|
quality=quality)
|
||||||
|
# Set playmethod property
|
||||||
|
utils.window('emby_%s.playmethod' % playurl, value="Transcode")
|
||||||
|
|
||||||
elif self.isDirectStream():
|
playurls.append(playurl)
|
||||||
self.logMsg("File is direct streaming.", 1)
|
|
||||||
playurl = self.API.getTranscodeVideoPath('DirectStream')
|
|
||||||
# Set playmethod property
|
|
||||||
utils.window('emby_%s.playmethod' % playurl, value="DirectStream")
|
|
||||||
|
|
||||||
elif self.isTranscoding():
|
self.logMsg("The playurls are: %s" % playurls, 1)
|
||||||
self.logMsg("File is transcoding.", 1)
|
return playurls
|
||||||
quality = {
|
|
||||||
'maxVideoBitrate': self.getBitrate()
|
|
||||||
}
|
|
||||||
playurl = self.API.getTranscodeVideoPath(
|
|
||||||
'Transcode',
|
|
||||||
quality=quality
|
|
||||||
)
|
|
||||||
# Set playmethod property
|
|
||||||
utils.window('emby_%s.playmethod' % playurl, value="Transcode")
|
|
||||||
self.logMsg("The playurl is: %s" % playurl, 1)
|
|
||||||
return playurl
|
|
||||||
|
|
||||||
def httpPlay(self):
|
def httpPlay(self):
|
||||||
# Audio, Video, Photo
|
# Audio, Video, Photo
|
||||||
|
@ -155,25 +146,24 @@ class PlayUtils():
|
||||||
videoCodec = self.API.getVideoCodec()
|
videoCodec = self.API.getVideoCodec()
|
||||||
codec = videoCodec['videocodec']
|
codec = videoCodec['videocodec']
|
||||||
resolution = videoCodec['resolution']
|
resolution = videoCodec['resolution']
|
||||||
if ((utils.settings('transcodeH265') == "true") and
|
# 720p
|
||||||
("hevc" in codec) and
|
if ((utils.settings('transcode720H265') == "true") and
|
||||||
(resolution == "1080")):
|
("h265" in codec) and
|
||||||
# Avoid HEVC(H265) 1080p
|
(resolution in "720 1080")):
|
||||||
self.logMsg("Option to transcode 1080P/HEVC enabled.", 0)
|
self.logMsg("Option to transcode 720P/h265 enabled.", 0)
|
||||||
return False
|
return False
|
||||||
else:
|
# 1080p
|
||||||
return True
|
if ((utils.settings('transcodeH265') == "true") and
|
||||||
|
("h265" in codec) and
|
||||||
|
(resolution == "1080")):
|
||||||
|
self.logMsg("Option to transcode 1080P/h265 enabled.", 0)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def isDirectStream(self):
|
def isDirectStream(self):
|
||||||
if not self.h265enabled():
|
if not self.h265enabled():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
elif (utils.settings('transcode720H265') == "true" and
|
|
||||||
item['MediaSources'][0]['Name'].startswith(("720P/HEVC","720P/H265"))):
|
|
||||||
# Avoid H265 720p
|
|
||||||
self.logMsg("Option to transcode 720P/H265 enabled.", 1)
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Requirement: BitRate, supported encoding
|
# Requirement: BitRate, supported encoding
|
||||||
# canDirectStream = item['MediaSources'][0]['SupportsDirectStream']
|
# canDirectStream = item['MediaSources'][0]['SupportsDirectStream']
|
||||||
# Plex: always able?!?
|
# Plex: always able?!?
|
||||||
|
@ -192,7 +182,7 @@ class PlayUtils():
|
||||||
|
|
||||||
server = self.server
|
server = self.server
|
||||||
|
|
||||||
itemid = self.API.getKey()
|
itemid = self.API.getRatingKey()
|
||||||
type = self.API.getType()
|
type = self.API.getType()
|
||||||
|
|
||||||
# if 'Path' in item and item['Path'].endswith('.strm'):
|
# if 'Path' in item and item['Path'].endswith('.strm'):
|
||||||
|
@ -211,7 +201,7 @@ class PlayUtils():
|
||||||
|
|
||||||
settings = self.getBitrate()
|
settings = self.getBitrate()
|
||||||
|
|
||||||
sourceBitrate = self.API.getBitrate()
|
sourceBitrate = int(self.API.getDataFromPartOrMedia())
|
||||||
self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s" % (settings, sourceBitrate), 1)
|
self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s" % (settings, sourceBitrate), 1)
|
||||||
if settings < sourceBitrate:
|
if settings < sourceBitrate:
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -71,11 +71,9 @@ def jsonrpc(action, arguments = {}):
|
||||||
"id" : 1 ,
|
"id" : 1 ,
|
||||||
"method" : "JSONRPC.Ping" })
|
"method" : "JSONRPC.Ping" })
|
||||||
elif action.lower() == "playmedia":
|
elif action.lower() == "playmedia":
|
||||||
fullurl=arguments[0]
|
|
||||||
resume=arguments[1]
|
|
||||||
xbmc.Player().play("plugin://plugin.video.plexkodiconnect/"
|
xbmc.Player().play("plugin://plugin.video.plexkodiconnect/"
|
||||||
"?mode=companion&resume=%s&id=%s"
|
"?mode=companion&arguments=%s"
|
||||||
% (resume, fullurl))
|
% arguments)
|
||||||
return True
|
return True
|
||||||
elif arguments:
|
elif arguments:
|
||||||
request=json.dumps({ "id" : 1,
|
request=json.dumps({ "id" : 1,
|
||||||
|
|
|
@ -111,16 +111,33 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
printDebug("adjusting the volume to %s%%" % volume)
|
printDebug("adjusting the volume to %s%%" % volume)
|
||||||
jsonrpc("Application.SetVolume", {"volume": volume})
|
jsonrpc("Application.SetVolume", {"volume": volume})
|
||||||
elif "/playMedia" in request_path:
|
elif "/playMedia" in request_path:
|
||||||
|
playQueueVersion = int(params.get('playQueueVersion', 1))
|
||||||
|
if playQueueVersion < subMgr.playQueueVersion:
|
||||||
|
# playQueue was updated; ignore this command for now
|
||||||
|
return
|
||||||
|
if playQueueVersion > subMgr.playQueueVersion:
|
||||||
|
# TODO: we should probably update something else now :-)
|
||||||
|
subMgr.playQueueVersion = playQueueVersion
|
||||||
s.response(getOKMsg(), getPlexHeaders())
|
s.response(getOKMsg(), getPlexHeaders())
|
||||||
resume = params.get('viewOffset', params.get('offset', "0"))
|
offset = params.get('viewOffset', params.get('offset', "0"))
|
||||||
protocol = params.get('protocol', "http")
|
protocol = params.get('protocol', "http")
|
||||||
address = params.get('address', s.client_address[0])
|
address = params.get('address', s.client_address[0])
|
||||||
server = getServerByHost(address)
|
server = getServerByHost(address)
|
||||||
port = params.get('port', server.get('port', '32400'))
|
port = params.get('port', server.get('port', '32400'))
|
||||||
fullurl = protocol+"://"+address+":"+port+params['key']
|
try:
|
||||||
printDebug("playMedia command -> fullurl: %s" % fullurl)
|
containerKey = urlparse(params.get('containerKey')).path
|
||||||
jsonrpc("playmedia", [fullurl, resume])
|
except:
|
||||||
|
containerKey = ''
|
||||||
|
regex = re.compile(r'''/playQueues/(\d+)$''')
|
||||||
|
try:
|
||||||
|
playQueueID = regex.findall(containerKey)[0]
|
||||||
|
except IndexError:
|
||||||
|
playQueueID = ''
|
||||||
|
|
||||||
|
jsonrpc("playmedia", params)
|
||||||
subMgr.lastkey = params['key']
|
subMgr.lastkey = params['key']
|
||||||
|
subMgr.containerKey = containerKey
|
||||||
|
subMgr.playQueueID = playQueueID
|
||||||
subMgr.server = server.get('server', 'localhost')
|
subMgr.server = server.get('server', 'localhost')
|
||||||
subMgr.port = port
|
subMgr.port = port
|
||||||
subMgr.protocol = protocol
|
subMgr.protocol = protocol
|
||||||
|
|
|
@ -62,7 +62,7 @@ class plexgdm:
|
||||||
print "PlexGDM: %s" % message
|
print "PlexGDM: %s" % message
|
||||||
|
|
||||||
def clientDetails(self, c_id, c_name, c_post, c_product, c_version):
|
def clientDetails(self, c_id, c_name, c_post, c_product, c_version):
|
||||||
self.client_data = "Content-Type: plex/media-player\r\nResource-Identifier: %s\r\nName: %s\r\nPort: %s\r\nProduct: %s\r\nVersion: %s\r\nProtocol: plex\r\nProtocol-Version: 1\r\nProtocol-Capabilities: navigation,playback,timeline\r\nDevice-Class: HTPC" % ( c_id, c_name, c_post, c_product, c_version )
|
self.client_data = "Content-Type: plex/media-player\r\nResource-Identifier: %s\r\nName: %s\r\nPort: %s\r\nProduct: %s\r\nVersion: %s\r\nProtocol: plex\r\nProtocol-Version: 1\r\nProtocol-Capabilities: timeline,playback,navigation,mirror,playqueues\r\nDevice-Class: HTPC" % ( c_id, c_name, c_post, c_product, c_version )
|
||||||
self.client_id = c_id
|
self.client_id = c_id
|
||||||
|
|
||||||
def getClientDetails(self):
|
def getClientDetails(self):
|
||||||
|
|
|
@ -12,6 +12,9 @@ class SubscriptionManager:
|
||||||
self.subscribers = {}
|
self.subscribers = {}
|
||||||
self.info = {}
|
self.info = {}
|
||||||
self.lastkey = ""
|
self.lastkey = ""
|
||||||
|
self.containerKey = ""
|
||||||
|
self.playQueueID = ''
|
||||||
|
self.playQueueVersion = 1
|
||||||
self.lastratingkey = ""
|
self.lastratingkey = ""
|
||||||
self.volume = 0
|
self.volume = 0
|
||||||
self.guid = ""
|
self.guid = ""
|
||||||
|
@ -75,12 +78,15 @@ class SubscriptionManager:
|
||||||
if keyid:
|
if keyid:
|
||||||
self.lastkey = "/library/metadata/%s"%keyid
|
self.lastkey = "/library/metadata/%s"%keyid
|
||||||
self.lastratingkey = keyid
|
self.lastratingkey = keyid
|
||||||
ret += ' containerKey="%s"' % (self.lastkey)
|
ret += ' containerKey="%s"' % (self.containerKey)
|
||||||
ret += ' key="%s"' % (self.lastkey)
|
ret += ' key="%s"' % (self.lastkey)
|
||||||
ret += ' ratingKey="%s"' % (self.lastratingkey)
|
ret += ' ratingKey="%s"' % (self.lastratingkey)
|
||||||
if pbmc_server:
|
if pbmc_server:
|
||||||
(self.server, self.port) = pbmc_server.split(':')
|
(self.server, self.port) = pbmc_server.split(':')
|
||||||
serv = getServerByHost(self.server)
|
serv = getServerByHost(self.server)
|
||||||
|
if self.playQueueID:
|
||||||
|
ret += ' playQueueID="%s"' % self.playQueueID
|
||||||
|
ret += ' playQueueVersion="%s"' % self.playQueueVersion
|
||||||
ret += ' duration="%s"' % info['duration']
|
ret += ' duration="%s"' % info['duration']
|
||||||
ret += ' seekRange="0-%s"' % info['duration']
|
ret += ' seekRange="0-%s"' % info['duration']
|
||||||
ret += ' controllable="%s"' % self.controllable()
|
ret += ' controllable="%s"' % self.controllable()
|
||||||
|
@ -119,12 +125,15 @@ class SubscriptionManager:
|
||||||
for p in players.values():
|
for p in players.values():
|
||||||
info = self.playerprops[p.get('playerid')]
|
info = self.playerprops[p.get('playerid')]
|
||||||
params = {}
|
params = {}
|
||||||
params['containerKey'] = (self.lastkey or "/library/metadata/900000")
|
params['containerKey'] = (self.containerKey or "/library/metadata/900000")
|
||||||
|
if self.playQueueID:
|
||||||
|
params['playQueueID'] = self.playQueueID
|
||||||
params['key'] = (self.lastkey or "/library/metadata/900000")
|
params['key'] = (self.lastkey or "/library/metadata/900000")
|
||||||
params['ratingKey'] = (self.lastratingkey or "900000")
|
params['ratingKey'] = (self.lastratingkey or "900000")
|
||||||
params['state'] = info['state']
|
params['state'] = info['state']
|
||||||
params['time'] = info['time']
|
params['time'] = info['time']
|
||||||
params['duration'] = info['duration']
|
params['duration'] = info['duration']
|
||||||
|
params['playQueueVersion'] = self.playQueueVersion
|
||||||
serv = getServerByHost(self.server)
|
serv = getServerByHost(self.server)
|
||||||
url = serv.get('protocol', 'http') + '://' \
|
url = serv.get('protocol', 'http') + '://' \
|
||||||
+ serv.get('server', 'localhost') + ':' \
|
+ serv.get('server', 'localhost') + ':' \
|
||||||
|
@ -134,11 +143,9 @@ class SubscriptionManager:
|
||||||
printDebug("params: %s" % params)
|
printDebug("params: %s" % params)
|
||||||
printDebug("players: %s" % players)
|
printDebug("players: %s" % players)
|
||||||
printDebug("sent server notification with state = %s" % params['state'])
|
printDebug("sent server notification with state = %s" % params['state'])
|
||||||
WINDOW = xbmcgui.Window(10000)
|
|
||||||
WINDOW.setProperty('plexbmc.nowplaying.sent', '1')
|
|
||||||
|
|
||||||
def controllable(self):
|
def controllable(self):
|
||||||
return "playPause,play,stop,skipPrevious,skipNext,volume,stepBack,stepForward,seekTo"
|
return "volume,shuffle,repeat,audioStream,videoStream,subtitleStream,skipPrevious,skipNext,seekTo,stepBack,stepForward,stop,playPause"
|
||||||
|
|
||||||
def addSubscriber(self, protocol, host, port, uuid, commandID):
|
def addSubscriber(self, protocol, host, port, uuid, commandID):
|
||||||
sub = Subscriber(protocol, host, port, uuid, commandID)
|
sub = Subscriber(protocol, host, port, uuid, commandID)
|
||||||
|
@ -211,7 +218,13 @@ class Subscriber:
|
||||||
+ "/:/timeline"
|
+ "/:/timeline"
|
||||||
# Override some headers
|
# Override some headers
|
||||||
headerOptions = {
|
headerOptions = {
|
||||||
'Accept': '*/*'
|
'Content-Range': 'bytes 0-/-1',
|
||||||
|
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.52 Safari/537.17',
|
||||||
|
'Accept': '*/*',
|
||||||
|
'X-Plex-Username': 'croneter',
|
||||||
|
'Connection': 'keep-alive',
|
||||||
|
'X-Plex-Client-Capabilities': 'protocols=shoutcast,http-video;videoDecoders=h264{profile:high&resolution:1080&level:51};audioDecoders=mp3,aac,dts{bitrate:800000&channels:8},ac3{bitrate:800000&channels:8}',
|
||||||
|
'X-Plex-Client-Profile-Extra': 'add-transcode-target-audio-codec(type=videoProfile&context=streaming&protocol=*&audioCodec=dca,ac3)'
|
||||||
}
|
}
|
||||||
response = self.download.downloadUrl(
|
response = self.download.downloadUrl(
|
||||||
url,
|
url,
|
||||||
|
@ -220,6 +233,6 @@ class Subscriber:
|
||||||
headerOptions=headerOptions)
|
headerOptions=headerOptions)
|
||||||
# if not requests.post(self.host, self.port, "/:/timeline", msg, getPlexHeaders(), self.protocol):
|
# if not requests.post(self.host, self.port, "/:/timeline", msg, getPlexHeaders(), self.protocol):
|
||||||
# subMgr.removeSubscriber(self.uuid)
|
# subMgr.removeSubscriber(self.uuid)
|
||||||
if response in [False, 401]:
|
if response in [False, None, 401]:
|
||||||
subMgr.removeSubscriber(self.uuid)
|
subMgr.removeSubscriber(self.uuid)
|
||||||
subMgr = SubscriptionManager()
|
subMgr = SubscriptionManager()
|
||||||
|
|
|
@ -13,7 +13,6 @@ import utils
|
||||||
import downloadutils
|
import downloadutils
|
||||||
|
|
||||||
import PlexAPI
|
import PlexAPI
|
||||||
import librarysync
|
|
||||||
|
|
||||||
##################################################################################################
|
##################################################################################################
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import sqlite3
|
||||||
import time
|
import time
|
||||||
import unicodedata
|
import unicodedata
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
from functools import wraps, update_wrapper
|
from functools import wraps
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from calendar import timegm
|
from calendar import timegm
|
||||||
|
|
||||||
|
@ -24,19 +24,35 @@ import xbmcvfs
|
||||||
addonName = xbmcaddon.Addon().getAddonInfo('name')
|
addonName = xbmcaddon.Addon().getAddonInfo('name')
|
||||||
|
|
||||||
|
|
||||||
def ThreadMethodsStopsync(cls):
|
def LogTime(func):
|
||||||
"""
|
"""
|
||||||
Decorator to replace stopThread method to include the Kodi window property
|
Decorator for functions and methods to log the time it took to run the code
|
||||||
'emby_shouldStop'
|
"""
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
starttotal = datetime.now()
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
elapsedtotal = datetime.now() - starttotal
|
||||||
|
logMsg('%s %s' % (addonName, func.__name__),
|
||||||
|
'It took %s to run the function.' % (elapsedtotal), 1)
|
||||||
|
return result
|
||||||
|
return wrapper
|
||||||
|
|
||||||
Use with any library sync threads. @ThreadMethods still required FIRST
|
|
||||||
|
def ThreadMethodsAdditionalStop(windowAttribute):
|
||||||
"""
|
"""
|
||||||
def threadStopped(self):
|
Decorator to replace stopThread method to include the Kodi windowAttribute
|
||||||
return (self._threadStopped or
|
|
||||||
self._abortMonitor.abortRequested() or
|
Use with any sync threads. @ThreadMethods still required FIRST
|
||||||
window('emby_shouldStop') == "true")
|
"""
|
||||||
cls.threadStopped = threadStopped
|
def wrapper(cls):
|
||||||
return cls
|
def threadStopped(self):
|
||||||
|
return (self._threadStopped or
|
||||||
|
self._abortMonitor.abortRequested() or
|
||||||
|
window(windowAttribute) == "true")
|
||||||
|
cls.threadStopped = threadStopped
|
||||||
|
return cls
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def ThreadMethodsAdditionalSuspend(windowAttribute):
|
def ThreadMethodsAdditionalSuspend(windowAttribute):
|
||||||
|
@ -48,8 +64,8 @@ def ThreadMethodsAdditionalSuspend(windowAttribute):
|
||||||
"""
|
"""
|
||||||
def wrapper(cls):
|
def wrapper(cls):
|
||||||
def threadSuspended(self):
|
def threadSuspended(self):
|
||||||
return (self._threadSuspended or True if
|
return (self._threadSuspended or
|
||||||
window(windowAttribute) == 'true' else False)
|
window(windowAttribute) == 'true')
|
||||||
cls.threadSuspended = threadSuspended
|
cls.threadSuspended = threadSuspended
|
||||||
return cls
|
return cls
|
||||||
return wrapper
|
return wrapper
|
||||||
|
@ -140,7 +156,6 @@ def getUnixTimestamp(secondsIntoTheFuture=None):
|
||||||
|
|
||||||
|
|
||||||
def logMsg(title, msg, level=1):
|
def logMsg(title, msg, level=1):
|
||||||
|
|
||||||
# Get the logLevel set in UserClient
|
# Get the logLevel set in UserClient
|
||||||
try:
|
try:
|
||||||
logLevel = int(window('emby_logLevel'))
|
logLevel = int(window('emby_logLevel'))
|
||||||
|
@ -155,13 +170,19 @@ def logMsg(title, msg, level=1):
|
||||||
xbmc.log("%s -> %s : %s" % (
|
xbmc.log("%s -> %s : %s" % (
|
||||||
title, func.co_name, msg))
|
title, func.co_name, msg))
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
xbmc.log("%s -> %s : %s" % (
|
try:
|
||||||
title, func.co_name, msg.encode('utf-8')))
|
xbmc.log("%s -> %s : %s" % (
|
||||||
|
title, func.co_name, msg.encode('utf-8')))
|
||||||
|
except:
|
||||||
|
xbmc.log("%s -> %s : %s" % (title, func.co_name, 'COULDNT LOG'))
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
xbmc.log("%s -> %s" % (title, msg))
|
xbmc.log("%s -> %s" % (title, msg))
|
||||||
except UnicodeEncodeError:
|
except UnicodeEncodeError:
|
||||||
xbmc.log("%s -> %s" % (title, msg.encode('utf-8')))
|
try:
|
||||||
|
xbmc.log("%s -> %s" % (title, msg.encode('utf-8')))
|
||||||
|
except:
|
||||||
|
xbmc.log("%s -> %s " % (title, 'COULDNT LOG'))
|
||||||
|
|
||||||
|
|
||||||
def window(property, value=None, clear=False, windowid=10000):
|
def window(property, value=None, clear=False, windowid=10000):
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
<setting id="myplexlogin" label="Log into plex.tv?" type="bool" default="true" />
|
<setting id="myplexlogin" label="Log into plex.tv?" type="bool" default="true" />
|
||||||
<setting id="plexLogin" label="plex.tv username" type="text" default="" visible="eq(-1,true)" />
|
<setting id="plexLogin" label="plex.tv username" type="text" default="" visible="eq(-1,true)" />
|
||||||
<setting id="plexhome" label="Plex home in use" type="bool" default="true" visible="false" />
|
<setting id="plexhome" label="Plex home in use" type="bool" default="true" visible="false" />
|
||||||
|
<setting id="plexToken" label="plexToken" type="text" default="" visible="false" />
|
||||||
</category>
|
</category>
|
||||||
|
|
||||||
<category label="Sync Options">
|
<category label="Sync Options">
|
||||||
|
|
Loading…
Reference in a new issue