PlexKodiConnect/resources/lib/playutils.py

418 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-09-02 04:02:00 +10:00
import logging
2016-02-07 22:38:50 +11:00
from urllib import urlencode
2015-12-25 07:07:00 +11:00
import xbmcgui
import xbmcvfs
2016-09-02 04:02:00 +10:00
from utils import window, settings, tryEncode, language as lang
2015-12-25 07:07:00 +11:00
2016-01-02 00:40:40 +11:00
import PlexAPI
2016-09-02 04:02:00 +10:00
###############################################################################
log = logging.getLogger("PLEX."+__name__)
2016-02-20 06:03:06 +11:00
###############################################################################
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
2016-09-02 04:02:00 +10:00
self.userid = window('currUserId')
self.server = window('pms_server')
self.machineIdentifier = window('plex_machineIdentifier')
2016-01-02 00:40:40 +11:00
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-09 05:40:58 +11:00
self.API.setPartNumber(partNumber)
self.API.getMediastreamNumber()
2016-04-12 02:57:20 +10:00
playurl = self.isDirectPlay()
2016-02-09 05:40:58 +11:00
2016-07-13 03:14:46 +10:00
if playurl is not None:
2016-09-02 04:02:00 +10:00
log.info("File is direct playing.")
playurl = tryEncode(playurl)
2016-02-09 05:40:58 +11:00
# Set playmethod property
2016-09-02 04:02:00 +10:00
window('emby_%s.playmethod' % playurl, "DirectPlay")
2016-02-09 05:40:58 +11:00
2016-04-12 02:57:20 +10:00
elif self.isDirectStream():
2016-09-02 04:02:00 +10:00
log.info("File is direct streaming.")
playurl = tryEncode(
self.API.getTranscodeVideoPath('DirectStream'))
2016-04-12 02:57:20 +10:00
# Set playmethod property
2016-09-02 04:02:00 +10:00
window('emby_%s.playmethod' % playurl, "DirectStream")
2016-02-09 05:40:58 +11:00
else:
2016-09-02 04:02:00 +10:00
log.info("File is transcoding.")
playurl = tryEncode(self.API.getTranscodeVideoPath(
2016-04-12 02:57:20 +10:00
'Transcode',
quality={
'maxVideoBitrate': self.get_bitrate(),
'videoResolution': self.get_resolution(),
'videoQuality': '100'
}))
2016-02-09 05:40:58 +11:00
# Set playmethod property
2016-09-02 04:02:00 +10:00
window('emby_%s.playmethod' % playurl, value="Transcode")
2016-02-09 05:40:58 +11:00
2016-09-02 04:02:00 +10:00
log.info("The playurl is: %s" % playurl)
2016-02-09 05:40:58 +11:00
return playurl
2015-12-25 07:07:00 +11:00
def httpPlay(self):
# Audio, Video, Photo
itemid = self.item['Id']
mediatype = self.item['MediaType']
2015-12-25 07:07:00 +11:00
2016-01-09 13:13:52 +11:00
if mediatype == "Audio":
playurl = "%s/emby/Audio/%s/stream" % (self.server, itemid)
2015-12-25 07:07:00 +11:00
else:
playurl = "%s/emby/Videos/%s/stream?static=true" % (self.server, itemid)
2015-12-25 07:07:00 +11:00
return playurl
def isDirectPlay(self):
2016-04-12 02:57:20 +10:00
"""
Returns the path/playurl if we can direct play, None otherwise
2016-04-12 02:57:20 +10:00
"""
2016-04-17 21:36:41 +10:00
# True for e.g. plex.tv watch later
if self.API.shouldStream() is True:
2016-09-02 04:02:00 +10:00
log.info("Plex item optimized for direct streaming")
2016-07-13 03:14:46 +10:00
return
# set to either 'Direct Stream=1' or 'Transcode=2'
# and NOT to 'Direct Play=0'
2016-09-02 04:02:00 +10:00
if settings('playType') != "0":
2015-12-25 07:07:00 +11:00
# User forcing to play via HTTP
2016-09-02 04:02:00 +10:00
log.info("User chose to not direct play")
2016-07-13 03:14:46 +10:00
return
if self.mustTranscode():
2016-07-13 03:14:46 +10:00
return
return self.API.validatePlayurl(self.API.getFilePath(),
self.API.getType(),
forceCheck=True)
2015-12-25 07:07:00 +11:00
def directPlay(self):
try:
playurl = self.item['MediaSources'][0]['Path']
2015-12-25 07:07:00 +11:00
except (IndexError, KeyError):
playurl = self.item['Path']
2015-12-25 07:07:00 +11:00
if self.item.get('VideoType'):
2015-12-25 07:07:00 +11:00
# Specific format modification
if self.item['VideoType'] == "Dvd":
2015-12-25 07:07:00 +11:00
playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
elif self.item['VideoType'] == "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):
if 'Path' not in self.item:
# File has no path defined in server
return False
# Convert path to direct play
path = self.directPlay()
2016-09-02 04:02:00 +10:00
log.info("Verifying path: %s" % path)
2015-12-25 07:07:00 +11:00
if xbmcvfs.exists(path):
2016-09-02 04:02:00 +10:00
log.info("Path exists.")
2015-12-25 07:07:00 +11:00
return True
elif ":" not in path:
2016-09-02 04:02:00 +10:00
log.info("Can't verify path, assumed linux. Still try to direct play.")
2015-12-25 07:07:00 +11:00
return True
else:
2016-09-02 04:02:00 +10:00
log.info("Failed to find file.")
2015-12-25 07:07:00 +11:00
return False
def mustTranscode(self):
2016-02-07 22:38:50 +11:00
"""
Returns True if we need to transcode because
- codec is in h265
- 10bit video codec
2016-05-16 02:01:13 +10:00
- HEVC codec
2016-11-07 01:37:22 +11:00
- window variable 'plex_forcetranscode' set to 'true'
(excepting trailers etc.)
- video bitrate above specified settings bitrate
if the corresponding file settings are set to 'true'
2016-02-07 22:38:50 +11:00
"""
videoCodec = self.API.getVideoCodec()
2016-09-02 04:02:00 +10:00
log.info("videoCodec: %s" % videoCodec)
if self.API.getType() in ('clip', 'track'):
log.info('Plex clip or music track, not transcoding')
return False
if window('plex_forcetranscode') == 'true':
log.info('User chose to force-transcode')
return True
2016-09-02 04:02:00 +10:00
if (settings('transcodeHi10P') == 'true' and
videoCodec['bitDepth'] == '10'):
2016-09-02 04:02:00 +10:00
log.info('Option to transcode 10bit video content enabled.')
return True
codec = videoCodec['videocodec']
if codec is None:
# e.g. trailers. Avoids TypeError with "'h265' in codec"
2016-09-02 04:02:00 +10:00
log.info('No codec from PMS, not transcoding.')
return False
try:
bitrate = int(videoCodec['bitrate'])
except (TypeError, ValueError):
log.info('No video bitrate from PMS, not transcoding.')
return False
if bitrate > self.get_max_bitrate():
log.info('Video bitrate of %s is higher than the maximal video'
'bitrate of %s that the user chose. Transcoding'
% (bitrate, self.get_max_bitrate()))
2016-11-07 01:37:22 +11:00
return True
try:
resolution = int(videoCodec['resolution'])
except (TypeError, ValueError):
2016-09-02 04:02:00 +10:00
log.info('No video resolution from PMS, not transcoding.')
return False
if 'h265' in codec or 'hevc' in codec:
if resolution >= self.getH265():
log.info("Option to transcode h265/HEVC enabled. Resolution "
"of the media: %s, transcoding limit resolution: %s"
2016-09-02 04:02:00 +10:00
% (str(resolution), str(self.getH265())))
return True
2016-02-07 22:38:50 +11:00
return False
def isDirectStream(self):
2016-05-08 20:33:13 +10:00
# Never transcode Music
if self.API.getType() == 'track':
return True
# set to 'Transcode=2'
2016-09-02 04:02:00 +10:00
if settings('playType') == "2":
# User forcing to play via HTTP
2016-09-02 04:02:00 +10:00
log.info("User chose to transcode")
return False
if self.mustTranscode():
2015-12-25 07:07:00 +11:00
return False
return True
def get_max_bitrate(self):
2015-12-25 07:07:00 +11:00
# get the addon video quality
videoQuality = settings('maxVideoQualities')
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,
'11': 99999999 # deactivated
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):
"""
Returns the user settings for transcoding h265: boundary resolutions
of 480, 720 or 1080 as an int
OR 9999999 (int) if user chose not to transcode
"""
2016-02-07 22:38:50 +11:00
H265 = {
'0': 99999999,
2016-02-07 22:38:50 +11:00
'1': 480,
'2': 720,
'3': 1080
}
2016-09-02 04:02:00 +10:00
return H265[settings('transcodeH265')]
2016-02-07 22:38:50 +11:00
def get_bitrate(self):
"""
Get the desired transcoding bitrate from the settings
"""
videoQuality = settings('transcoderVideoQualities')
bitrate = {
'0': 320,
'1': 720,
'2': 1500,
'3': 2000,
'4': 3000,
'5': 4000,
'6': 8000,
'7': 10000,
'8': 12000,
'9': 20000,
'10': 40000,
}
# max bit rate supported by server (max signed 32bit integer)
return bitrate.get(videoQuality, 2147483)
def get_resolution(self):
"""
Get the desired transcoding resolutions from the settings
"""
2016-09-02 04:02:00 +10:00
chosen = settings('transcoderVideoQualities')
2016-02-07 22:38:50 +11:00
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
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)
if part is None:
part = 0
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 url
2015-12-25 07:07:00 +11:00
2016-02-07 22:38:50 +11:00
audioNum = 0
# Remember 'no subtitles'
subNum = 1
defaultSub = None
2015-12-25 07:07:00 +11:00
for stream in mediastreams:
2016-09-02 04:02:00 +10:00
# Since Plex returns all possible tracks together, have to sort
# them.
index = stream.attrib.get('id')
type = stream.attrib.get('streamType')
2015-12-25 07:07:00 +11:00
2016-02-05 06:23:04 +11:00
# Audio
if type == "2":
codec = stream.attrib.get('codec')
2016-02-07 22:38:50 +11:00
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)
audioStreamsList.append(index)
2016-09-02 04:02:00 +10:00
audioStreams.append(tryEncode(track))
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":
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:
track = "%s 'unknown' (%s)" % (subNum+1, stream.attrib.get('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)
2016-09-08 22:51:57 +10:00
else:
track = "%s (burn-in)" % track
if stream.attrib.get('selected') == '1' and downloadable:
# Only show subs without asking user if they can be
# turned off
defaultSub = index
2015-12-25 07:07:00 +11:00
2016-02-07 22:38:50 +11:00
subtitleStreamsList.append(index)
2016-09-02 04:02:00 +10:00
subtitleStreams.append(tryEncode(track))
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
2016-09-02 04:02:00 +10:00
playurlprefs['audioBoost'] = settings('audioBoost')
2015-12-25 07:07:00 +11:00
selectSubsIndex = None
2016-02-07 22:38:50 +11:00
if subNum > 1:
if (settings('pickPlexSubtitles') == 'true' and
defaultSub is not None):
log.info('Using default Plex subtitle: %s' % defaultSub)
selectSubsIndex = defaultSub
else:
resp = dialog.select(lang(33014), subtitleStreams)
if resp > 0:
selectSubsIndex = subtitleStreamsList[resp-1]
else:
# User selected no subtitles or backed out of dialog
playurlprefs["skipSubtitles"] = 1
if selectSubsIndex is not None:
# Load subtitles in the listitem if downloadable
if selectSubsIndex in downloadableStreams:
sub_url = self.API.addPlexHeadersToUrl(
"%s/library/streams/%s"
% (self.server, selectSubsIndex))
log.info("Downloadable sub: %s: %s"
% (selectSubsIndex, sub_url))
listitem.setSubtitles([tryEncode(sub_url)])
# Don't additionally burn in subtitles
playurlprefs["skipSubtitles"] = 1
else:
2016-09-02 04:02:00 +10:00
log.info('Need to burn in subtitle %s' % selectSubsIndex)
2016-02-07 22:38:50 +11:00
playurlprefs["subtitleStreamID"] = selectSubsIndex
2016-09-02 04:02:00 +10:00
playurlprefs["subtitleSize"] = settings('subtitleSize')
2016-02-07 22:38:50 +11:00
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