PlexKodiConnect/resources/lib/playutils.py

427 lines
14 KiB
Python
Raw Normal View History

2015-12-25 07:07:00 +11:00
# -*- coding: utf-8 -*-
2016-02-20 06:03:06 +11:00
###############################################################################
2015-12-25 07:07:00 +11:00
2016-02-07 22:38:50 +11:00
from urllib import urlencode
2015-12-25 07:07:00 +11:00
import xbmcgui
import xbmcvfs
import clientinfo
import utils
2016-01-02 00:40:40 +11:00
import PlexAPI
2016-02-20 06:03:06 +11:00
###############################################################################
2015-12-25 07:07:00 +11:00
2016-01-27 03:20:13 +11:00
@utils.logging
2015-12-25 07:07:00 +11:00
class PlayUtils():
2016-02-20 06:03:06 +11:00
2015-12-25 07:07:00 +11:00
def __init__(self, item):
self.item = item
2016-01-30 06:07:21 +11:00
self.API = PlexAPI.API(item)
2015-12-25 07:07:00 +11:00
self.clientInfo = clientinfo.ClientInfo()
2016-03-11 02:02:46 +11:00
self.userid = utils.window('currUserId')
self.server = utils.window('pms_server')
2016-01-02 00:40:40 +11:00
self.machineIdentifier = utils.window('plex_machineIdentifier')
2016-02-09 05:40:58 +11:00
def getPlayUrl(self, partNumber=None):
2016-01-30 06:07:21 +11:00
"""
2016-02-09 05:40:58 +11:00
Returns the playurl for the part with number partNumber
(movie might consist of several files)
playurl is utf-8 encoded!
2016-01-30 06:07:21 +11:00
"""
2016-02-17 19:13:37 +11:00
log = self.logMsg
window = utils.window
2016-02-09 05:40:58 +11:00
self.API.setPartNumber(partNumber)
playurl = None
if self.isDirectPlay():
2016-02-17 19:13:37 +11:00
log("File is direct playing.", 1)
2016-02-09 05:40:58 +11:00
playurl = self.API.getTranscodeVideoPath('DirectPlay')
playurl = playurl.encode('utf-8')
# Set playmethod property
2016-02-20 06:03:06 +11:00
window('emby_%s.playmethod' % playurl, "DirectPlay")
2016-02-09 05:40:58 +11:00
# Currently no direct streaming possible - needs investigation
# elif self.isDirectStream():
# self.logMsg("File is direct streaming.", 1)
# playurl = self.API.getTranscodeVideoPath('DirectStream')
# # Set playmethod property
# utils.window('emby_%s.playmethod' % playurl, "DirectStream")
elif self.isTranscoding():
2016-02-17 19:13:37 +11:00
log("File is transcoding.", 1)
2016-02-09 05:40:58 +11:00
quality = {
'maxVideoBitrate': self.getBitrate(),
'videoResolution': self.getResolution(),
'videoQuality': '100'
}
playurl = self.API.getTranscodeVideoPath('Transcode',
quality=quality)
playurl = playurl.encode('utf-8')
2016-02-09 05:40:58 +11:00
# Set playmethod property
2016-02-17 19:13:37 +11:00
window('emby_%s.playmethod' % playurl, value="Transcode")
2016-02-09 05:40:58 +11:00
2016-02-20 06:03:06 +11:00
log("The playurl is: %s" % playurl, 1)
2016-02-09 05:40:58 +11:00
return playurl
2015-12-25 07:07:00 +11:00
def httpPlay(self):
# Audio, Video, Photo
item = self.item
server = self.server
itemid = item['Id']
mediatype = item['MediaType']
2016-01-09 13:13:52 +11:00
if mediatype == "Audio":
2015-12-25 07:07:00 +11:00
playurl = "%s/emby/Audio/%s/stream" % (server, itemid)
else:
playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
return playurl
def isDirectPlay(self):
# Requirement: Filesystem, Accessible path
if utils.settings('playFromStream') == "true":
# User forcing to play via HTTP
self.logMsg("Can't direct play, user enabled play from HTTP.", 1)
2015-12-25 07:07:00 +11:00
return False
2016-02-07 22:38:50 +11:00
if self.h265enabled():
2015-12-25 07:07:00 +11:00
return False
return True
def directPlay(self):
item = self.item
try:
playurl = item['MediaSources'][0]['Path']
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
2016-01-31 19:10:00 +11:00
elif type == "BluRay":
2015-12-25 07:07:00 +11:00
playurl = "%s/BDMV/index.bdmv" % playurl
# Assign network protocol
if playurl.startswith('\\\\'):
playurl = playurl.replace("\\\\", "smb://")
playurl = playurl.replace("\\", "/")
if "apple.com" in playurl:
USER_AGENT = "QuickTime/7.7.4"
playurl += "?|User-Agent=%s" % USER_AGENT
return playurl
def fileExists(self):
2016-02-17 19:13:37 +11:00
log = self.logMsg
2015-12-25 07:07:00 +11:00
if 'Path' not in self.item:
# File has no path defined in server
return False
# Convert path to direct play
path = self.directPlay()
2016-02-17 19:13:37 +11:00
log("Verifying path: %s" % path, 1)
2015-12-25 07:07:00 +11:00
if xbmcvfs.exists(path):
2016-02-17 19:13:37 +11:00
log("Path exists.", 1)
2015-12-25 07:07:00 +11:00
return True
elif ":" not in path:
2016-02-17 19:13:37 +11:00
log("Can't verify path, assumed linux. Still try to direct play.", 1)
2015-12-25 07:07:00 +11:00
return True
else:
2016-02-17 19:13:37 +11:00
log("Failed to find file.", 1)
2015-12-25 07:07:00 +11:00
return False
def h265enabled(self):
2016-02-07 22:38:50 +11:00
"""
Returns True if we need to transcode
"""
videoCodec = self.API.getVideoCodec()
2016-02-07 23:26:28 +11:00
self.logMsg("videoCodec: %s" % videoCodec, 2)
codec = videoCodec['videocodec']
resolution = videoCodec['resolution']
2016-02-07 22:38:50 +11:00
h265 = self.getH265()
2016-02-07 23:26:28 +11:00
try:
if not ('h265' in codec or 'hevc' in codec) or (h265 is None):
return False
# E.g. trailers without a codec of None
except TypeError:
return False
2016-02-07 22:38:50 +11:00
if resolution >= h265:
self.logMsg("Option to transcode h265 enabled. Resolution media: "
"%s, transcoding limit resolution: %s"
% (resolution, h265), 1)
return True
return False
def isDirectStream(self):
if not self.h265enabled():
2015-12-25 07:07:00 +11:00
return False
# Requirement: BitRate, supported encoding
2016-01-02 00:40:40 +11:00
# canDirectStream = item['MediaSources'][0]['SupportsDirectStream']
# Plex: always able?!?
canDirectStream = True
2015-12-25 07:07:00 +11:00
# Make sure the server supports it
if not canDirectStream:
return False
# Verify the bitrate
if not self.isNetworkSufficient():
2016-02-20 06:03:06 +11:00
self.logMsg(
"The network speed is insufficient to direct stream file.", 1)
2015-12-25 07:07:00 +11:00
return False
return True
def directStream(self):
server = self.server
2016-01-30 06:07:21 +11:00
itemid = self.API.getRatingKey()
type = self.API.getType()
2015-12-25 07:07:00 +11:00
2016-01-02 00:40:40 +11:00
# if 'Path' in item and item['Path'].endswith('.strm'):
# # Allow strm loading when direct streaming
# playurl = self.directPlay()
if type == "Audio":
2015-12-25 07:07:00 +11:00
playurl = "%s/emby/Audio/%s/stream.mp3" % (server, itemid)
else:
playurl = "%s/emby/Videos/%s/stream?static=true" % (server, itemid)
2016-01-02 00:40:40 +11:00
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.API.replaceURLtags()
2015-12-25 07:07:00 +11:00
return playurl
def isNetworkSufficient(self):
settings = self.getBitrate()
2015-12-25 07:07:00 +11:00
2016-02-03 23:01:13 +11:00
sourceBitrate = int(self.API.getDataFromPartOrMedia('bitrate'))
self.logMsg("The add-on settings bitrate is: %s, the video bitrate required is: %s" % (settings, sourceBitrate), 1)
if settings < sourceBitrate:
return False
2015-12-25 07:07:00 +11:00
return True
def isTranscoding(self):
2016-01-02 00:40:40 +11:00
# I hope Plex transcodes everything
return True
2015-12-25 07:07:00 +11:00
item = self.item
canTranscode = item['MediaSources'][0]['SupportsTranscoding']
# Make sure the server supports it
if not canTranscode:
return False
return True
def transcoding(self):
item = self.item
if 'Path' in item and item['Path'].endswith('.strm'):
# Allow strm loading when transcoding
playurl = self.directPlay()
else:
itemid = item['Id']
deviceId = self.clientInfo.getDeviceId()
playurl = (
"%s/emby/Videos/%s/master.m3u8?MediaSourceId=%s"
% (self.server, itemid, itemid)
)
playurl = (
"%s&VideoCodec=h264&AudioCodec=ac3&MaxAudioChannels=6&deviceId=%s&VideoBitrate=%s"
% (playurl, deviceId, self.getBitrate()*1000))
return playurl
def getBitrate(self):
# get the addon video quality
2016-02-07 22:38:50 +11:00
videoQuality = utils.settings('transcoderVideoQualities')
2015-12-25 07:07:00 +11:00
bitrate = {
2016-02-07 22:38:50 +11:00
'0': 320,
'1': 720,
'2': 1500,
2015-12-25 07:07:00 +11:00
'3': 2000,
2016-02-07 22:38:50 +11:00
'4': 3000,
'5': 4000,
'6': 8000,
'7': 10000,
'8': 12000,
'9': 20000,
'10': 40000,
2015-12-25 07:07:00 +11:00
}
# max bit rate supported by server (max signed 32bit integer)
return bitrate.get(videoQuality, 2147483)
2016-02-07 22:38:50 +11:00
def getH265(self):
chosen = utils.settings('transcodeH265')
H265 = {
'0': None,
'1': 480,
'2': 720,
'3': 1080
}
return H265.get(chosen, None)
def getResolution(self):
chosen = utils.settings('transcoderVideoQualities')
res = {
'0': '420x420',
'1': '576x320',
'2': '720x480',
'3': '1024x768',
'4': '1280x720',
'5': '1280x720',
'6': '1920x1080',
'7': '1920x1080',
'8': '1920x1080',
'9': '1920x1080',
'10': '1920x1080',
}
return res[chosen]
def audioSubsPref(self, listitem, url, part=None):
2016-02-17 19:13:37 +11:00
lang = utils.language
dialog = xbmcgui.Dialog()
2015-12-25 07:07:00 +11:00
# For transcoding only
# Present the list of audio to select from
2016-02-07 22:38:50 +11:00
audioStreamsList = []
2015-12-25 07:07:00 +11:00
audioStreams = []
2016-02-07 22:38:50 +11:00
# audioStreamsChannelsList = []
subtitleStreamsList = []
subtitleStreams = ['1 No subtitles']
downloadableStreams = []
2016-02-07 22:38:50 +11:00
# selectAudioIndex = ""
2015-12-25 07:07:00 +11:00
selectSubsIndex = ""
2016-02-07 22:38:50 +11:00
playurlprefs = {}
2015-12-25 07:07:00 +11:00
2016-02-05 06:23:04 +11:00
# Set part where we're at
self.API.setPartNumber(part)
2015-12-25 07:07:00 +11:00
try:
2016-02-07 22:38:50 +11:00
mediastreams = self.item[0][part]
2015-12-25 07:07:00 +11:00
except (TypeError, KeyError, IndexError):
return
2016-02-07 22:38:50 +11:00
audioNum = 0
# Remember 'no subtitles'
subNum = 1
2015-12-25 07:07:00 +11:00
for stream in mediastreams:
# Since Emby returns all possible tracks together, have to sort them.
2016-02-07 22:38:50 +11:00
index = stream.attrib['id']
type = stream.attrib['streamType']
2015-12-25 07:07:00 +11:00
2016-02-05 06:23:04 +11:00
# Audio
if type == "2":
2016-02-07 22:38:50 +11:00
codec = stream.attrib['codec']
channelLayout = stream.attrib.get('audioChannelLayout', "")
2015-12-25 07:07:00 +11:00
try:
2016-02-07 22:38:50 +11:00
track = "%s %s - %s %s" % (audioNum+1, stream.attrib['language'], codec, channelLayout)
2015-12-25 07:07:00 +11:00
except:
2016-02-07 22:38:50 +11:00
track = "%s 'unknown' - %s %s" % (audioNum+1, codec, channelLayout)
2015-12-25 07:07:00 +11:00
2016-02-07 22:38:50 +11:00
#audioStreamsChannelsList[audioNum] = stream.attrib['channels']
audioStreamsList.append(index)
2016-03-07 23:38:45 +11:00
audioStreams.append(track.encode('utf-8'))
2016-02-07 22:38:50 +11:00
audioNum += 1
2015-12-25 07:07:00 +11:00
2016-02-05 06:23:04 +11:00
# Subtitles
elif type == "3":
'''if stream['IsExternal']:
continue'''
2015-12-25 07:07:00 +11:00
try:
2016-02-07 22:38:50 +11:00
track = "%s %s" % (subNum+1, stream.attrib['language'])
2015-12-25 07:07:00 +11:00
except:
2016-02-07 22:38:50 +11:00
track = "%s 'unknown' (%s)" % (subNum+1, stream.attrib['codec'])
2015-12-25 07:07:00 +11:00
2016-02-07 22:38:50 +11:00
default = stream.attrib.get('default')
forced = stream.attrib.get('forced')
downloadable = stream.attrib.get('key')
2015-12-25 07:07:00 +11:00
if default:
track = "%s - Default" % track
if forced:
track = "%s - Forced" % track
if downloadable:
downloadableStreams.append(index)
2015-12-25 07:07:00 +11:00
2016-02-07 22:38:50 +11:00
subtitleStreamsList.append(index)
2016-03-07 23:38:45 +11:00
subtitleStreams.append(track.encode('utf-8'))
2016-02-07 22:38:50 +11:00
subNum += 1
2015-12-25 07:07:00 +11:00
2016-02-07 22:38:50 +11:00
if audioNum > 1:
resp = dialog.select(lang(33013), audioStreams)
2015-12-25 07:07:00 +11:00
if resp > -1:
# User selected audio
2016-02-07 22:38:50 +11:00
playurlprefs['audioStreamID'] = audioStreamsList[resp]
else: # User backed out of selection - let PMS decide
pass
2015-12-25 07:07:00 +11:00
else: # There's only one audiotrack.
2016-02-07 22:38:50 +11:00
playurlprefs['audioStreamID'] = audioStreamsList[0]
# Add audio boost
playurlprefs['audioBoost'] = utils.settings('audioBoost')
2015-12-25 07:07:00 +11:00
2016-02-07 22:38:50 +11:00
if subNum > 1:
resp = dialog.select(lang(33014), subtitleStreams)
2015-12-25 07:07:00 +11:00
if resp == 0:
# User selected no subtitles
2016-02-07 22:38:50 +11:00
playurlprefs["skipSubtitles"] = 1
2015-12-25 07:07:00 +11:00
elif resp > -1:
# User selected subtitles
2016-02-07 22:38:50 +11:00
selectSubsIndex = subtitleStreamsList[resp-1]
# Load subtitles in the listitem if downloadable
if selectSubsIndex in downloadableStreams:
2016-02-07 22:38:50 +11:00
url = "%s/library/streams/%s" \
% (self.server, selectSubsIndex)
2016-02-20 02:10:19 +11:00
url = self.API.addPlexHeadersToUrl(url)
2016-02-07 22:38:50 +11:00
self.logMsg("Downloadable sub: %s: %s" % (selectSubsIndex, url), 1)
listitem.setSubtitles([url])
else:
2016-02-07 22:38:50 +11:00
self.logMsg('Need to burn in subtitle %s' % selectSubsIndex, 1)
playurlprefs["subtitleStreamID"] = selectSubsIndex
playurlprefs["subtitleSize"] = utils.settings('subtitleSize')
2015-12-25 07:07:00 +11:00
else: # User backed out of selection
2016-02-07 22:38:50 +11:00
pass
# Tell the PMS what we want with a PUT request
# url = self.server + '/library/parts/' + self.item[0][part].attrib['id']
# PlexFunctions.SelectStreams(url, playurlprefs)
url += '&' + urlencode(playurlprefs)
2015-12-25 07:07:00 +11:00
# Get number of channels for selected audio track
2016-02-07 22:38:50 +11:00
# audioChannels = audioStreamsChannelsList.get(selectAudioIndex, 0)
# if audioChannels > 2:
# playurlprefs += "&AudioBitrate=384000"
# else:
# playurlprefs += "&AudioBitrate=192000"
2015-12-25 07:07:00 +11:00
2016-02-07 22:38:50 +11:00
return url