Direct playing movie works

This commit is contained in:
tomkat83 2016-01-01 14:40:40 +01:00
parent 681e57b350
commit bc65b6082d
6 changed files with 239 additions and 129 deletions

View file

@ -41,7 +41,6 @@ import xbmc
import struct
import time
import urllib
import urllib2
import httplib
import socket
@ -52,6 +51,7 @@ import Queue
import traceback
import re
import json
try:
import xml.etree.cElementTree as etree
@ -87,6 +87,12 @@ class PlexAPI():
self.deviceName = client.getDeviceName()
self.plexversion = client.getVersion()
self.platform = client.getPlatform()
self.userId = utils.window('emby_currUser')
self.token = utils.window('emby_accessToken%s' % self.userId)
self.server = utils.window('emby_server%s' % self.userId)
self.plexLogin = utils.settings('plexLogin')
self.plexToken = utils.settings('plexToken')
self.machineIdentifier = utils.window('plex_machineIdentifier')
self.doUtils = downloadutils.DownloadUtils()
@ -167,6 +173,7 @@ class PlexAPI():
headerOptions={'X-Plex-Token': token}
)
self.logMsg("Response was: %s" % r, 2)
# List of exception returns, when connection failed
exceptionlist = [
'',
401
@ -596,22 +603,30 @@ class PlexAPI():
requests. An authentication option is NOT yet added.
Inputs:
JSON=True will enforce a JSON answer
options: dictionary of options that will override the
standard header options otherwise set.
JSON=True will enforce a JSON answer, never mind any options
Output:
header dictionary
"""
# Get addon infos
xargs = dict()
xargs['User-agent'] = self.addonName
xargs['X-Plex-Device'] = self.deviceName
# xargs['X-Plex-Model'] = ''
xargs['X-Plex-Platform'] = self.platform
xargs['X-Plex-Client-Platform'] = self.platform
xargs['X-Plex-Product'] = self.addonName
xargs['X-Plex-Version'] = self.plexversion
xargs['X-Plex-Client-Identifier'] = self.clientId
xargs = {
'User-agent': self.addonName,
'X-Plex-Device': self.deviceName,
'X-Plex-Platform': self.platform,
'X-Plex-Client-Platform': self.platform,
'X-Plex-Product': self.addonName,
'X-Plex-Version': self.plexversion,
'X-Plex-Client-Identifier': self.clientId,
'machineIdentifier': self.machineIdentifier,
'Accept': 'application/xml'
}
try:
xargs['X-Plex-Token'] = self.token
except NameError:
# no token needed/saved yet
pass
if JSON:
xargs['Accept'] = 'application/json'
if options:
@ -821,31 +836,6 @@ class PlexAPI():
dprint(__name__, 1, "====== MyPlex sign out XML finished ======")
dprint(__name__, 0, 'MyPlex Sign Out done')
def UserAccessRestricted(self, username):
"""
Returns True if the user's access is restricted (parental restrictions)
False otherwise.
Returns False also if access cannot be checked because plex.tv cannot
be reached.
"""
plexToken = utils.settings('plexToken')
users = self.MyPlexListHomeUsers(plexToken)
# If an error is encountered, set to False
if not users:
self.logMsg("Could not check user access restrictions.", 1)
self.logMsg("Setting restrictions to False.", 1)
return False
for user in users:
if username in user['title']:
restricted = user['restricted']
if restricted == '1':
restricted = True
else:
restricted = False
self.logMsg("Successfully checked user parental access for %s: restricted access is set to %s" % (username, restricted), 1)
return restricted
def GetUserArtworkURL(self, username):
"""
Returns the URL for the user's Avatar. Or False if something went
@ -877,8 +867,8 @@ class PlexAPI():
Will return empty strings if failed.
"""
string = self.__language__
plexToken = utils.settings('plexToken')
plexLogin = utils.settings('plexLogin')
plexLogin = self.plexLogin
plexToken = self.plexToken
self.logMsg("Getting user list.", 1)
# Get list of Plex home users
users = self.MyPlexListHomeUsers(plexToken)
@ -1021,54 +1011,6 @@ class PlexAPI():
users.append(user.attrib)
return users
def getTranscodeVideoPath(self, path, AuthToken, options, action, quality, subtitle, audio, partIndex):
"""
Transcode Video support
parameters:
path
AuthToken
options - dict() of PlexConnect-options as received from aTV
action - transcoder action: Auto, Directplay, Transcode
quality - (resolution, quality, bitrate)
subtitle - {'selected', 'dontBurnIn', 'size'}
audio - {'boost'}
result:
final path to pull in PMS transcoder
"""
UDID = options['PlexConnectUDID']
transcodePath = '/video/:/transcode/universal/start.m3u8?'
vRes = quality[0]
vQ = quality[1]
mVB = quality[2]
dprint(__name__, 1, "Setting transcode quality Res:{0} Q:{1} {2}Mbps", vRes, vQ, mVB)
dprint(__name__, 1, "Subtitle: selected {0}, dontBurnIn {1}, size {2}", subtitle['selected'], subtitle['dontBurnIn'], subtitle['size'])
dprint(__name__, 1, "Audio: boost {0}", audio['boost'])
args = dict()
args['session'] = UDID
args['protocol'] = 'hls'
args['videoResolution'] = vRes
args['maxVideoBitrate'] = mVB
args['videoQuality'] = vQ
args['directStream'] = '0' if action=='Transcode' else '1'
# 'directPlay' - handled by the client in MEDIARUL()
args['subtitleSize'] = subtitle['size']
args['skipSubtitles'] = subtitle['dontBurnIn'] #'1' # shut off PMS subtitles. Todo: skip only for aTV native/SRT (or other supported)
args['audioBoost'] = audio['boost']
args['fastSeek'] = '1'
args['path'] = path
args['partIndex'] = partIndex
xargs = getXArgsDeviceInfo(options)
xargs['X-Plex-Client-Capabilities'] = "protocols=http-live-streaming,http-mp4-streaming,http-streaming-video,http-streaming-video-720p,http-mp4-video,http-mp4-video-720p;videoDecoders=h264{profile:high&resolution:1080&level:41};audioDecoders=mp3,aac{bitrate:160000}"
if not AuthToken=='':
xargs['X-Plex-Token'] = AuthToken
return transcodePath + urlencode(args) + '&' + urlencode(xargs)
def getDirectVideoPath(self, key, AuthToken):
"""
Direct Video Play support
@ -1309,7 +1251,7 @@ class PlexAPI():
'includePopularLeaves': 1,
'includeConcerts': 1
}
url = url + '?' + urllib.urlencode(arguments)
url = url + '?' + urlencode(arguments)
headerOptions = {'Accept': 'application/xml'}
xml = self.doUtils.downloadUrl(url, headerOptions=headerOptions)
if not xml:
@ -1324,6 +1266,7 @@ class API():
self.item = item
self.clientinfo = clientinfo.ClientInfo()
self.addonName = self.clientinfo.getAddonName()
self.clientId = self.clientinfo.getDeviceId()
self.userId = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userId)
self.token = utils.window('emby_accessToken%s' % self.userId)
@ -1771,7 +1714,7 @@ class API():
token = {'X-Plex-Token': self.token}
xargs = PlexAPI().getXArgsDeviceInfo(options=token)
xargs.update(arguments)
url = "%s?%s" % (url, urllib.urlencode(xargs))
url = "%s?%s" % (url, urlencode(xargs))
return url
def getMediaStreams(self):
@ -1939,5 +1882,141 @@ class API():
"MaxWidth=%s&MaxHeight=%s&Format=original&Tag=%s%s"
% (server, parentId, maxWidth, maxHeight, parentTag, customquery))
allartworks['Primary'] = artwork
return allartworks
def getTranscodeVideoPath(self, action, quality={}, subtitle={}, audioboost=None, partIndex=None, options={}):
"""
Transcode Video support
Input:
action 'Transcode' OR any other string
quality: {'videoResolution': 'resolution',
'videoQuality': 'quality',
'maxVideoBitrate': 'bitrate'}
subtitle {'selected', 'dontBurnIn', 'size'}
audioboost Guess this takes an int as str
partIndex No idea
options dict() of PlexConnect-options as received from aTV
Output:
final path to pull in PMS transcoder
"""
# path to item
transcodePath = self.server + \
'/video/:/transcode/universal/start.m3u8?'
ID = self.getKey()
path = self.server + '/library/metadata/' + ID
args = {
'session': self.clientId,
'protocol': 'hls',
'fastSeek': '1',
'path': path,
'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;"
"videoDecoders="
"h264{profile:high&resolution:1080&level:51};"
"audioDecoders="
"mp3,"
"aac{bitrate:160000},"
"ac3{channels:6},"
"dts{channels:6}"
# 'partIndex': partIndex What do we do with this?!?
}
# All the settings
if subtitle:
args_update = {
'subtitleSize': subtitle['size'],
'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
)
if audioboost:
args_update = {
'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'
xargs = PlexAPI().getXArgsDeviceInfo(options=options)
return transcodePath + urlencode(args) + '&' + urlencode(xargs)
def adjustResume(self, resume_seconds):
resume = 0
if resume_seconds:
resume = round(float(resume_seconds), 6)
jumpback = int(utils.settings('resumeJumpBack'))
if resume > jumpback:
# To avoid negative bookmark
resume = resume - jumpback
return resume
def returnParts(self):
"""
TODO
"""
item = self.item
PartCount = 0
# Run through parts
for child in item[0][0]:
if child.tag == 'Part':
PartCount += PartCount
def externalSubs(self, playurl):
externalsubs = []
mapping = {}
item = self.item
itemid = self.getKey()
try:
mediastreams = item[0][0][0]
except (TypeError, KeyError, IndexError):
return
kodiindex = 0
for stream in mediastreams:
# index = stream['Index']
index = stream.attrib['id']
# Since Emby returns all possible tracks together, have to pull only external subtitles.
# IsTextSubtitleStream if true, is available to download from emby.
if (stream.attrib['streamType'] == "3" and
stream.attrib['format']):
# Direct stream
# PLEX: TODO!!
url = ("%s/Videos/%s/%s/Subtitles/%s/Stream.srt"
% (self.server, itemid, itemid, index))
# map external subtitles for mapping
mapping[kodiindex] = index
externalsubs.append(url)
kodiindex += 1
mapping = json.dumps(mapping)
utils.window('emby_%s.indexMapping' % playurl, value=mapping)
return externalsubs

View file

@ -25,13 +25,14 @@ import playbackutils as pbutils
import playutils
import api
import PlexAPI
#################################################################################################
def doPlayback(itemid, dbid):
emby = embyserver.Read_EmbyServer()
item = emby.getItem(itemid)
item = PlexAPI.PlexAPI().GetPlexMetadata(itemid) # Now xml, not json!
pbutils.PlaybackUtils(item).play(itemid, dbid)
##### DO RESET AUTH #####

View file

@ -360,7 +360,7 @@ class Movies(Items):
utils.window('emby_pathverified', value="true")
else:
# Set plugin path and media flags using real filename
path = "plugin://plugin.video.plexkodiconnect.movies/"
path = "plugin://plugin.video.emby.movies/"
params = {
'filename': filename.encode('utf-8'),
@ -704,7 +704,7 @@ class HomeVideos(Items):
utils.window('emby_pathverified', value="true")
else:
# Set plugin path and media flags using real filename
path = "plugin://plugin.video.plexkodiconnect.movies/"
path = "plugin://plugin.video.emby.movies/"
params = {
'filename': filename.encode('utf-8'),

View file

@ -18,6 +18,8 @@ import playlist
import read_embyserver as embyserver
import utils
import PlexAPI
#################################################################################################
@ -27,7 +29,7 @@ class PlaybackUtils():
def __init__(self, item):
self.item = item
self.API = api.API(self.item)
self.API = PlexAPI.API(self.item)
self.clientInfo = clientinfo.ClientInfo()
self.addonName = self.clientInfo.getAddonName()
@ -107,8 +109,9 @@ class PlaybackUtils():
currentPosition += 1
############### -- CHECK FOR INTROS ################
if utils.settings('enableCinema') == "true" and not seektime:
# PLEX: todo. Seems like Plex returns a playlist WITH trailers
# if utils.settings('enableCinema') == "true" and not seektime:
if False:
# if we have any play them when the movie/show is not being resumed
url = "{server}/emby/Users/{UserId}/Items/%s/Intros?format=json" % itemid
intros = doUtils.downloadUrl(url)
@ -145,14 +148,17 @@ class PlaybackUtils():
# Extend our current playlist with the actual item to play
# only if there's no playlist first
self.logMsg("Adding main item to playlist.", 1)
self.pl.addtoPlaylist(dbid, item['Type'].lower())
# self.pl.addtoPlaylist(dbid, item['Type'].lower())
self.pl.addtoPlaylist(dbid, item[0].attrib['type'].lower())
# Ensure that additional parts are played after the main item
currentPosition += 1
############### -- CHECK FOR ADDITIONAL PARTS ################
if item.get('PartCount'):
# Plex: TODO. Guess parts are sent back like trailers.
# if item.get('PartCount'):
if False:
# Only add to the playlist after intros have played
partcount = item['PartCount']
url = "{server}/emby/Videos/%s/AdditionalParts?format=json" % itemid
@ -215,11 +221,14 @@ class PlaybackUtils():
def setProperties(self, playurl, listitem):
# Set all properties necessary for plugin path playback
item = self.item
itemid = item['Id']
itemtype = item['Type']
# itemid = item['Id']
itemid = self.API.getKey()
# itemtype = item['Type']
itemtype = item[0].attrib['type']
resume, runtime = self.API.getRuntime()
embyitem = "emby_%s" % playurl
utils.window('%s.runtime' % embyitem, value=str(item.get('RunTimeTicks')))
utils.window('%s.runtime' % embyitem, value=str(runtime))
utils.window('%s.type' % embyitem, value=itemtype)
utils.window('%s.itemid' % embyitem, value=itemid)
@ -231,7 +240,8 @@ class PlaybackUtils():
# Append external subtitles to stream
playmethod = utils.window('%s.playmethod' % embyitem)
# Only for direct play and direct stream
subtitles = self.externalSubs(playurl)
# subtitles = self.externalSubs(playurl)
subtitles = self.API.externalSubs(playurl)
if playmethod in ("DirectStream", "Transcode"):
# Direct play automatically appends external
listitem.setSubtitles(subtitles)
@ -278,7 +288,8 @@ class PlaybackUtils():
item = self.item
artwork = self.artwork
allartwork = artwork.getAllArtwork(item, parentInfo=True)
# allartwork = artwork.getAllArtwork(item, parentInfo=True)
allartwork = self.API.getAllArtwork(parentInfo=True)
# Set artwork for listitem
arttypes = {

View file

@ -9,6 +9,8 @@ import xbmcvfs
import clientinfo
import utils
import PlexAPI
#################################################################################################
@ -24,6 +26,9 @@ class PlayUtils():
self.userid = utils.window('emby_currUser')
self.server = utils.window('emby_server%s' % self.userid)
self.machineIdentifier = utils.window('plex_machineIdentifier')
self.plx = PlexAPI.API(item)
def logMsg(self, msg, lvl=1):
@ -36,34 +41,39 @@ class PlayUtils():
item = self.item
playurl = None
if item['MediaSources'][0]['Protocol'] == "Http":
# Only play as http
self.logMsg("File protocol is http.", 1)
playurl = self.httpPlay()
utils.window('emby_%s.playmethod' % playurl, value="DirectStream")
# if item['MediaSources'][0]['Protocol'] == "Http":
# # Only play as http
# self.logMsg("File protocol is http.", 1)
# playurl = self.httpPlay()
# utils.window('emby_%s.playmethod' % playurl, value="DirectStream")
elif self.isDirectPlay():
# elif self.isDirectPlay():
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")
# 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")
elif self.isDirectStream():
if self.isDirectStream():
self.logMsg("File is direct streaming.", 1)
playurl = self.directStream()
playurl = self.plx.getTranscodeVideoPath('direct')
# Set playmethod property
utils.window('emby_%s.playmethod' % playurl, value="DirectStream")
elif self.isTranscoding():
self.logMsg("File is transcoding.", 1)
playurl = self.transcoding()
quality = {
'bitrate': self.getBitrate()
}
playurl = self.plx.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):
@ -192,13 +202,16 @@ class PlayUtils():
item = self.item
if (utils.settings('transcodeH265') == "true" and
item['MediaSources'][0]['Name'].startswith("1080P/H265")):
item[0][0].attrib('videoCodec').startswith("h265") and
item[0][0].attrib('videoResolution').startswith("1080")):
# Avoid H265 1080p
self.logMsg("Option to transcode 1080P/H265 enabled.", 1)
return False
# Requirement: BitRate, supported encoding
canDirectStream = item['MediaSources'][0]['SupportsDirectStream']
# canDirectStream = item['MediaSources'][0]['SupportsDirectStream']
# Plex: always able?!?
canDirectStream = True
# Make sure the server supports it
if not canDirectStream:
return False
@ -215,16 +228,18 @@ class PlayUtils():
item = self.item
server = self.server
itemid = item['Id']
type = item['Type']
itemid = self.plx.getKey()
type = item[0].tag
if 'Path' in item and item['Path'].endswith('.strm'):
# Allow strm loading when direct streaming
playurl = self.directPlay()
elif type == "Audio":
# if 'Path' in item and item['Path'].endswith('.strm'):
# # Allow strm loading when direct streaming
# playurl = self.directPlay()
if type == "Audio":
playurl = "%s/emby/Audio/%s/stream.mp3" % (server, itemid)
else:
playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
playurl = "{server}/player/playback/playMedia?key=%2Flibrary%2Fmetadata%2F%s&offset=0&X-Plex-Client-Identifier={clientId}&machineIdentifier={SERVER ID}&address={SERVER IP}&port={SERVER PORT}&protocol=http&path=http%3A%2F%2F{SERVER IP}%3A{SERVER PORT}%2Flibrary%2Fmetadata%2F{MEDIA ID}" % (itemid)
playurl = self.plx.replaceURLtags()
return playurl
@ -233,7 +248,7 @@ class PlayUtils():
settings = self.getBitrate()*1000
try:
sourceBitrate = int(self.item['MediaSources'][0]['Bitrate'])
sourceBitrate = int(self.item[0][0].attrib['bitrate'])
except (KeyError, TypeError):
self.logMsg("Bitrate value is missing.", 1)
else:
@ -245,7 +260,8 @@ class PlayUtils():
return True
def isTranscoding(self):
# I hope Plex transcodes everything
return True
item = self.item
canTranscode = item['MediaSources'][0]['SupportsTranscoding']

View file

@ -234,12 +234,13 @@ class UserClient(threading.Thread):
self.currUserId = userId
self.currServer = self.getServer()
self.currToken = self.getToken()
self.machineIdentifier = self.getServerId()
self.ssl = self.getSSLverify()
self.sslcert = self.getSSL()
# Test the validity of current token
if authenticated == False:
url = "%s/emby/Users/%s?format=json" % (self.currServer, userId)
url = "%s/clients" % (self.currServer)
utils.window('emby_currUser', value=userId)
utils.window('emby_accessToken%s' % userId, value=self.currToken)
result = doUtils.downloadUrl(url)
@ -254,6 +255,7 @@ class UserClient(threading.Thread):
utils.window('emby_accessToken%s' % userId, value=self.currToken)
utils.window('emby_server%s' % userId, value=self.currServer)
utils.window('emby_server_%s' % userId, value=self.getServer(prefix=False))
utils.window('plex_machineIdentifier', value=self.machineIdentifier)
# Set DownloadUtils values
doUtils.setUsername(username)
@ -279,6 +281,7 @@ class UserClient(threading.Thread):
username = self.getUsername()
server = self.getServer()
machineIdentifier = self.getServerId()
# If there's no settings.xml
if not hasSettings: