Movies now direct playing. Trailers playing but always transcoding

This commit is contained in:
tomkat83 2016-01-05 16:05:20 +01:00
parent 93ad4ae0cb
commit cef41188e0
3 changed files with 200 additions and 144 deletions

View file

@ -619,6 +619,8 @@ class PlexAPI():
'X-Plex-Version': self.plexversion,
'X-Plex-Client-Identifier': self.clientId,
'machineIdentifier': self.machineIdentifier,
'Connection': 'keep-alive',
'X-Plex-Provides': 'player',
'Accept': 'application/xml'
}
@ -1781,16 +1783,73 @@ class API():
def getBitrate(self):
"""
Returns the bitrate as an int or None
Returns the bitrate as an int. The Part bitrate is returned; if not
available in the Plex XML, the Media bitrate is returned
"""
item = self.item
try:
bitrate = item[self.child][0].attrib['bitrate']
bitrate = int(bitrate)
bitrate = item[self.child][0][self.part].attrib['bitrate']
except KeyError:
bitrate = None
bitrate = item[self.child][0].attrib['bitrate']
bitrate = int(bitrate)
return bitrate
def getDataFromPartOrMedia(self, key):
"""
Retrieves XML data 'key' first from the active part. If unsuccessful,
tries to retrieve the data from the Media response part.
If all fails, None is returned.
"""
media = self.item[self.child][0].attrib
part = self.item[self.child][0][self.part].attrib
try:
try:
value = part[key]
except KeyError:
value = media[key]
except KeyError:
value = None
return value
def getVideoCodec(self):
"""
Returns the video codec and resolution for the child and part selected.
If any data is not found on a part-level, the Media-level data is
returned.
If that also fails (e.g. for old trailers, None is returned)
Output:
{
'videocodec': xxx, e.g. 'h264'
'resolution': xxx, e.g. '720' or '1080'
'height': xxx, e.g. '816'
'width': xxx, e.g. '1920'
'aspectratio': xxx, e.g. '1.78'
'bitrate': xxx, e.g. 10642 (an int!)
'container': xxx e.g. 'mkv'
}
"""
videocodec = self.getDataFromPartOrMedia('videoCodec')
resolution = self.getDataFromPartOrMedia('videoResolution')
height = self.getDataFromPartOrMedia('height')
width = self.getDataFromPartOrMedia('width')
aspectratio = self.getDataFromPartOrMedia('aspectratio')
bitrate = self.getDataFromPartOrMedia('bitrate')
container = self.getDataFromPartOrMedia('container')
videoCodec = {
'videocodec': videocodec,
'resolution': resolution,
'height': height,
'width': width,
'aspectratio': aspectratio,
'bitrate': bitrate,
'container': container
}
return videoCodec
def getMediaStreams(self):
"""
Returns the media streams
@ -1798,7 +1857,7 @@ class API():
Output: each track contains a dictionaries
{
'video': videotrack-list, 'videocodec', 'height', 'width',
'aspectratio', video3DFormat'
'aspectratio', 'video3DFormat'
'audio': audiotrack-list, 'audiocodec', 'channels',
'audiolanguage'
'subtitle': list of subtitle languages (or "Unknown")
@ -1980,95 +2039,142 @@ class API():
allartworks['Primary'] = artwork
return allartworks
def getTranscodeVideoPath(self, action, quality={}, subtitle={}, audioboost=None, partIndex=None, options={}):
def getTranscodeVideoPath(self, action, quality={}, subtitle={}, audioboost=None, options={}):
"""
Transcode Video support; returns the URL to get a media started
Input:
action 'Transcode' OR any other string
action 'DirectPlay', 'DirectStream' or 'Transcode'
quality: {
'videoResolution': 'resolution',
'videoQuality': 'quality',
'maxVideoBitrate': 'bitrate'
}
(one or several of these options)
subtitle {'selected', 'dontBurnIn', 'size'}
audioboost e.g. 100
partIndex Index number of media part, starting with 0
options dict() of PlexConnect-options as received from aTV
Output:
final path to pull in PMS transcoder
final URL to pull in PMS transcoder
TODO: mediaIndex
"""
# path to item
transcodePath = self.server + \
'/video/:/transcode/universal/start.m3u8?'
ID = self.getKey()
if partIndex is not None:
path = self.server + '/library/metadata/' + ID
else:
path = self.item[self.child][0][self.part].attrib['key']
args = {
'session': self.clientId,
'protocol': 'hls', # also seen: 'dash'
'fastSeek': '1',
'path': path,
'mediaIndex': 0, # Probably refering to XML reply sheme
'X-Plex-Client-Capabilities': "protocols=http-live-streaming,"
"http-streaming-video,"
"http-streaming-video-720p,"
"http-streaming-video-1080p,"
"http-mp4-streaming,"
"http-mp4-video,"
"http-mp4-video-720p,"
"http-mp4-video-1080p;"
# Set Client capabilities
clientArgs = {
'X-Plex-Client-Capabilities':
"protocols=shoutcast,"
"http-live-streaming,"
"http-streaming-video,"
"http-streaming-video-720p,"
"http-streaming-video-1080p,"
"http-mp4-streaming,"
"http-mp4-video,"
"http-mp4-video-720p,"
"http-mp4-video-1080p;"
"videoDecoders="
"h264{profile:high&resolution:1080&level:51};"
"h264{profile:high&resolution:1080&level:51},"
"h265{profile:high&resolution:1080&level:51},"
"mpeg1video,"
"mpeg2video,"
"mpeg4,"
"msmpeg4,"
"mjpeg,"
"wmv2,"
"wmv3,"
"vc1,"
"cinepak,"
"h263;"
"audioDecoders="
"mp3,"
"aac{bitrate:160000},"
"ac3{channels:6},"
"dts{channels:6}"
# 'offset': 0 # Resume point
# 'directPlay': 0 # 1 if Kodi can also handle the container
"aac,"
"ac3{bitrate:800000&channels:8},"
"dts{bitrate:800000&channels:8},"
"truehd,"
"eac3,"
"dca,"
"mp2,"
"pcm,"
"wmapro,"
"wmav2,"
"wmavoice,"
"wmalossless;"
}
xargs = PlexAPI().getXArgsDeviceInfo(options=options)
# For Direct Playing
if action == "DirectPlay":
path = self.item[self.child][0][self.part].attrib['key']
transcodePath = self.server + path
# Be sure to have exactly ONE '?' in the path (might already have
# been returned, e.g. trailers!)
if '?' not in path:
transcodePath = transcodePath + '?'
url = transcodePath + \
urlencode(clientArgs) + '&' + \
urlencode(xargs)
return url
# For Direct Streaming or Transcoding
transcodePath = self.server + \
'/video/:/transcode/universal/start.m3u8?'
partCount = 0
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 = {
'path': path,
'mediaIndex': 0, # Probably refering to XML reply sheme
'partIndex': self.part,
'protocol': 'hls', # seen in the wild: 'dash', 'http', 'hls'
'offset': 0, # Resume point
'fastSeek': 1
}
# All the settings
if partIndex is not None:
args['partIndex'] = partIndex
if subtitle:
args_update = {
argsUpdate = {
'subtitles': 'burn',
'subtitleSize': subtitle['size'], # E.g. 100
'skipSubtitles': subtitle['dontBurnIn'] # '1': shut off PMS
}
args.update(args_update)
self.logMsg(
"Subtitle: selected %s, dontBurnIn %s, size %s"
% (subtitle['selected'], subtitle['dontBurnIn'],
subtitle['size']),
1
)
args.update(argsUpdate)
if audioboost:
args_update = {
argsUpdate = {
'audioBoost': audioboost
}
args.update(args_update)
self.logMsg("audioboost: %s" % audioboost, 1)
if action == 'Transcode':
# Possible Plex settings:
# 'videoResolution': vRes,
# 'maxVideoBitrate': mVB,
# : vQ
self.logMsg("Setting transcode quality to: %s" % quality, 1)
args['directStream'] = '0'
args.update(quality)
else:
args['directStream'] = '1'
args.update(argsUpdate)
xargs = PlexAPI().getXArgsDeviceInfo(options=options)
return transcodePath + urlencode(args) + '&' + urlencode(xargs)
if action == "DirectStream":
argsUpdate = {
'directPlay': '0',
'directStream': '1',
}
args.update(argsUpdate)
elif action == 'Transcode':
argsUpdate = {
'directPlay': '0',
'directStream': '0'
}
self.logMsg("Setting transcode quality to: %s" % quality, 1)
args.update(quality)
args.update(argsUpdate)
url = transcodePath + \
urlencode(clientArgs) + '&' + \
urlencode(xargs) + '&' + \
urlencode(args)
return url
def adjustResume(self, resume_seconds):
resume = 0
@ -2150,4 +2256,4 @@ class API():
"""
Returns the parts of the specified video child in the XML response
"""
return self.item[self.child][0]
return self.item[self.child][0]

View file

@ -80,6 +80,7 @@ class PlaybackUtils():
propertiesPlayback = utils.window('emby_playbackProps', windowid=10101) == "true"
introsPlaylist = False
partsPlaylist = False
dummyPlaylist = False
self.logMsg("Playlist start position: %s" % startPos, 1)
@ -117,7 +118,7 @@ class PlaybackUtils():
if playListSize > 1:
getTrailers = True
if utils.settings('askCinema') == "true":
resp = xbmcgui.Dialog().yesno("Emby Cinema Mode", "Play trailers?")
resp = xbmcgui.Dialog().yesno(self.addonName, "Play trailers?")
if not resp:
# User selected to not play trailers
getTrailers = False
@ -158,6 +159,7 @@ class PlaybackUtils():
partcount = len(parts)
if partcount > 1:
# Only add to the playlist after intros have played
partsPlaylist = True
i = 0
for part in parts:
API.setPartNumber(i)
@ -176,6 +178,7 @@ class PlaybackUtils():
self.pl.verifyPlaylist()
currentPosition += 1
i = i + 1
API.setPartNumber(0)
if dummyPlaylist:
# Added a dummy file to the playlist,
@ -201,14 +204,15 @@ class PlaybackUtils():
self.setProperties(playurl, listitem)
############### PLAYBACK ################
customPlaylist = utils.window('emby_customPlaylist', windowid=10101)
if homeScreen and seektime:
self.logMsg("Play as a widget item.", 1)
self.setListItem(listitem)
xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
elif ((introsPlaylist and utils.window('emby_customPlaylist', windowid=10101) == "true") or
(homeScreen and not sizePlaylist)):
elif ((introsPlaylist and customPlaylist == "true") or
(homeScreen and not sizePlaylist) or
(partsPlaylist and customPlaylist == "true")):
# Playlist was created just now, play it.
self.logMsg("Play playlist.", 1)
xbmc.Player().play(playlist, startpos=startPos)

View file

@ -38,7 +38,6 @@ class PlayUtils():
def getPlayUrl(self, child=0, partIndex=None):
item = self.item
# NO, I am not very fond of this construct!
self.API.setChildNumber(child)
if partIndex is not None:
@ -51,34 +50,27 @@ class PlayUtils():
# playurl = self.httpPlay()
# utils.window('emby_%s.playmethod' % playurl, value="DirectStream")
# elif self.isDirectPlay():
if self.isDirectPlay():
self.logMsg("File is direct playing.", 1)
playurl = self.API.getTranscodeVideoPath('DirectPlay')
playurl = playurl.encode('utf-8')
# Set playmethod property
utils.window('emby_%s.playmethod' % playurl, value="DirectPlay")
# self.logMsg("File is direct playing.", 1)
# playurl = self.directPlay()
# playurl = playurl.encode('utf-8')
# # Set playmethod property
# utils.window('emby_%s.playmethod' % playurl, value="DirectPlay")
if self.isDirectStream():
elif self.isDirectStream():
self.logMsg("File is direct streaming.", 1)
playurl = self.API.getTranscodeVideoPath(
'direct',
partIndex=partIndex
)
playurl = self.API.getTranscodeVideoPath('DirectStream')
# Set playmethod property
utils.window('emby_%s.playmethod' % playurl, value="DirectStream")
elif self.isTranscoding():
self.logMsg("File is transcoding.", 1)
quality = {
'bitrate': self.getBitrate()
'maxVideoBitrate': self.getBitrate()
}
playurl = self.API.getTranscodeVideoPath(
'Transcode',
quality=quality,
partIndex=partIndex
quality=quality
)
# Set playmethod property
utils.window('emby_%s.playmethod' % playurl, value="Transcode")
@ -102,57 +94,19 @@ class PlayUtils():
def isDirectPlay(self):
item = self.item
# Requirement: Filesystem, Accessible path
if utils.settings('playFromStream') == "true":
# User forcing to play via HTTP
self.logMsg("Can't direct play, play from HTTP enabled.", 1)
self.logMsg("Can't direct play, user enabled play from HTTP.", 1)
return False
if (utils.settings('transcodeH265') == "true" and
item['MediaSources'][0]['Name'].startswith("1080P/H265")):
# Avoid H265 1080p
self.logMsg("Option to transcode 1080P/H265 enabled.", 1)
if not self.h265enabled():
return False
canDirectPlay = item['MediaSources'][0]['SupportsDirectPlay']
# Make sure direct play is supported by the server
if not canDirectPlay:
self.logMsg("Can't direct play, server doesn't allow/support it.", 1)
# Found with e.g. trailers
if self.API.getDataFromPartOrMedia('optimizedForStreaming') == '1':
return False
location = item['LocationType']
if location == "FileSystem":
# Verify the path
if not self.fileExists():
self.logMsg("Unable to direct play.")
try:
count = int(utils.settings('failCount'))
except ValueError:
count = 0
self.logMsg("Direct play failed: %s times." % count, 1)
if count < 2:
# Let the user know that direct play failed
utils.settings('failCount', value=str(count+1))
xbmcgui.Dialog().notification(
heading="Emby server",
message="Unable to direct play.",
icon="special://home/addons/plugin.video.plexkodiconnect/icon.png",
sound=False)
elif utils.settings('playFromStream') != "true":
# Permanently set direct stream as true
utils.settings('playFromStream', value="true")
utils.settings('failCount', value="0")
xbmcgui.Dialog().notification(
heading="Emby server",
message=("Direct play failed 3 times. Enabled play "
"from HTTP in the add-on settings."),
icon="special://home/addons/plugin.video.plexkodiconnect/icon.png",
sound=False)
return False
return True
def directPlay(self):
@ -164,15 +118,6 @@ class PlayUtils():
except (IndexError, KeyError):
playurl = item['Path']
if item.get('VideoType'):
# Specific format modification
type = item['VideoType']
if type == "Dvd":
playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
elif type == "Bluray":
playurl = "%s/BDMV/index.bdmv" % playurl
# Assign network protocol
if playurl.startswith('\\\\'):
playurl = playurl.replace("\\\\", "smb://")
@ -206,15 +151,21 @@ class PlayUtils():
self.logMsg("Failed to find file.")
return False
def isDirectStream(self):
item = self.item
if (utils.settings('transcodeH265') == "true" and
item[0][0].attrib('videoCodec').startswith("h265") and
item[0][0].attrib('videoResolution').startswith("1080")):
def h265enabled(self):
videoCodec = self.API.getVideoCodec()
codec = videoCodec['videocodec']
resolution = videoCodec['resolution']
if ((utils.settings('transcodeH265') == "true") and
("h265" in codec) and
(resolution == "1080")):
# Avoid H265 1080p
self.logMsg("Option to transcode 1080P/H265 enabled.", 1)
self.logMsg("Option to transcode 1080P/H265 enabled.", 0)
return False
else:
return True
def isDirectStream(self):
if not self.h265enabled():
return False
# Requirement: BitRate, supported encoding
@ -229,7 +180,6 @@ class PlayUtils():
if not self.isNetworkSufficient():
self.logMsg("The network speed is insufficient to direct stream file.", 1)
return False
return True
def directStream(self):
@ -256,11 +206,7 @@ class PlayUtils():
settings = self.getBitrate()
sourceBitrate = self.API.getBitrate()
if not sourceBitrate:
self.logMsg("Bitrate value is missing.", 0)
return True
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:
return False
return True