PlexKodiConnect/resources/lib/playutils.py

364 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
###############################################################################
2017-12-10 00:35:08 +11:00
from logging import getLogger
2018-01-24 05:13:47 +11:00
from downloadutils import DownloadUtils as DU
2016-02-07 22:38:50 +11:00
2018-01-08 03:50:30 +11:00
from utils import window, settings, language as lang, dialog, tryEncode
2017-03-06 02:30:39 +11:00
import variables as v
2016-01-02 00:40:40 +11:00
2016-09-02 04:02:00 +10:00
###############################################################################
2018-01-08 03:50:30 +11:00
LOG = 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
2018-01-08 03:50:30 +11:00
def __init__(self, api, playqueue_item):
2018-01-24 05:13:47 +11:00
"""
init with api (PlexAPI wrapper of the PMS xml element) and
playqueue_item (Playlist_Item())
"""
2018-01-08 03:50:30 +11:00
self.api = api
self.item = playqueue_item
2016-01-02 00:40:40 +11:00
2018-01-08 03:50:30 +11:00
def getPlayUrl(self):
2016-01-30 06:07:21 +11:00
"""
2018-01-08 03:50:30 +11:00
Returns the playurl for the part
2016-02-09 05:40:58 +11:00
(movie might consist of several files)
2018-01-08 03:50:30 +11:00
playurl is in unicode!
2016-01-30 06:07:21 +11:00
"""
2018-01-08 03:50:30 +11:00
self.api.getMediastreamNumber()
2016-04-12 02:57:20 +10:00
playurl = self.isDirectPlay()
2016-07-13 03:14:46 +10:00
if playurl is not None:
2018-01-08 03:50:30 +11:00
LOG.info("File is direct playing.")
self.item.playmethod = 'DirectPlay'
2016-04-12 02:57:20 +10:00
elif self.isDirectStream():
2018-01-08 03:50:30 +11:00
LOG.info("File is direct streaming.")
playurl = self.api.getTranscodeVideoPath('DirectStream')
self.item.playmethod = 'DirectStream'
else:
2018-01-08 03:50:30 +11:00
LOG.info("File is transcoding.")
playurl = self.api.getTranscodeVideoPath(
2016-04-12 02:57:20 +10:00
'Transcode',
quality={
2016-12-21 02:13:19 +11:00
'maxVideoBitrate': self.get_bitrate(),
'videoResolution': self.get_resolution(),
'videoQuality': '100',
'mediaBufferSize': int(settings('kodi_video_cache'))/1024,
2018-01-08 03:50:30 +11:00
})
self.item.playmethod = 'Transcode'
LOG.info("The playurl is: %s", playurl)
2018-01-22 21:20:37 +11:00
self.item.file = playurl
2016-02-09 05:40:58 +11:00
return playurl
2015-12-25 07:07:00 +11:00
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
2018-01-08 03:50:30 +11:00
if self.api.shouldStream() is True:
LOG.info("Plex item optimized for direct streaming")
2016-07-13 03:14:46 +10:00
return
2017-11-05 22:51:45 +11:00
# Check whether we have a strm file that we need to throw at Kodi 1:1
2018-01-08 03:50:30 +11:00
path = self.api.getFilePath()
2017-11-05 22:51:45 +11:00
if path is not None and path.endswith('.strm'):
2018-01-08 03:50:30 +11:00
LOG.info('.strm file detected')
playurl = self.api.validatePlayurl(path,
self.api.getType(),
2017-11-05 22:51:45 +11:00
forceCheck=True)
2018-01-08 03:50:30 +11:00
return playurl
# 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
2018-01-08 03:50:30 +11: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
2018-01-08 03:50:30 +11:00
return self.api.validatePlayurl(path,
self.api.getType(),
forceCheck=True)
2015-12-25 07:07:00 +11:00
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.)
2016-12-21 02:13:19 +11:00
- video bitrate above specified settings bitrate
if the corresponding file settings are set to 'true'
2016-02-07 22:38:50 +11:00
"""
2018-01-08 03:50:30 +11:00
if self.api.getType() in (v.PLEX_TYPE_CLIP, v.PLEX_TYPE_SONG):
LOG.info('Plex clip or music track, not transcoding')
2016-12-21 02:13:19 +11:00
return False
2018-01-08 03:50:30 +11:00
videoCodec = self.api.getVideoCodec()
LOG.info("videoCodec: %s" % videoCodec)
2016-12-21 02:13:19 +11:00
if window('plex_forcetranscode') == 'true':
2018-01-08 03:50:30 +11:00
LOG.info('User chose to force-transcode')
2016-12-21 02:13:19 +11:00
return True
codec = videoCodec['videocodec']
if codec is None:
# e.g. trailers. Avoids TypeError with "'h265' in codec"
2018-01-08 03:50:30 +11:00
LOG.info('No codec from PMS, not transcoding.')
return False
if ((settings('transcodeHi10P') == 'true' and
videoCodec['bitDepth'] == '10') and
('h264' in codec)):
2018-01-08 03:50:30 +11:00
LOG.info('Option to transcode 10bit h264 video content enabled.')
return True
2016-12-21 02:13:19 +11:00
try:
bitrate = int(videoCodec['bitrate'])
except (TypeError, ValueError):
2018-01-08 03:50:30 +11:00
LOG.info('No video bitrate from PMS, not transcoding.')
2016-12-21 02:13:19 +11:00
return False
if bitrate > self.get_max_bitrate():
2018-01-08 03:50:30 +11:00
LOG.info('Video bitrate of %s is higher than the maximal video'
2016-12-21 02:13:19 +11:00
'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):
2018-01-08 03:50:30 +11:00
LOG.info('No video resolution from PMS, not transcoding.')
return False
2016-12-21 02:13:19 +11:00
if 'h265' in codec or 'hevc' in codec:
if resolution >= self.getH265():
2018-01-08 03:50:30 +11:00
LOG.info("Option to transcode h265/HEVC enabled. Resolution "
2016-12-21 02:13:19 +11:00
"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
2018-01-08 03:50:30 +11:00
if self.api.getType() == 'track':
2016-05-08 20:33:13 +10:00
return True
# set to 'Transcode=2'
2016-09-02 04:02:00 +10:00
if settings('playType') == "2":
# User forcing to play via HTTP
2018-01-08 03:50:30 +11:00
LOG.info("User chose to transcode")
return False
if self.mustTranscode():
2015-12-25 07:07:00 +11:00
return False
return True
2016-12-21 02:13:19 +11:00
def get_max_bitrate(self):
2015-12-25 07:07:00 +11:00
# get the addon video quality
2016-12-21 02:13:19 +11:00
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,
2016-12-21 02:13:19 +11:00
'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 = {
2016-12-21 02:13:19 +11:00
'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
2016-12-21 02:13:19 +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]
2018-01-08 03:50:30 +11:00
def audio_subtitle_prefs(self, listitem):
2017-05-02 03:51:10 +10:00
"""
For transcoding only
2015-12-25 07:07:00 +11:00
2017-05-02 03:51:10 +10:00
Called at the very beginning of play; used to change audio and subtitle
stream by a PUT request to the PMS
"""
# Set media and part where we're at
2018-01-08 03:50:30 +11:00
if self.api.mediastream is None:
self.api.getMediastreamNumber()
2015-12-25 07:07:00 +11:00
try:
2018-01-08 03:50:30 +11:00
mediastreams = self.api.plex_media_streams()
2017-05-02 03:51:10 +10:00
except (TypeError, IndexError):
2018-01-08 03:50:30 +11:00
LOG.error('Could not get media %s, part %s',
self.api.mediastream, self.api.part)
2017-05-02 03:51:10 +10:00
return
part_id = mediastreams.attrib['id']
audio_streams_list = []
audio_streams = []
subtitle_streams_list = []
# No subtitles as an option
subtitle_streams = [lang(39706)]
downloadable_streams = []
download_subs = []
# selectAudioIndex = ""
select_subs_index = ""
audio_numb = 0
2016-02-07 22:38:50 +11:00
# Remember 'no subtitles'
2017-05-02 03:51:10 +10:00
sub_num = 1
default_sub = 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')
2017-05-02 03:51:10 +10:00
typus = stream.attrib.get('streamType')
2016-02-05 06:23:04 +11:00
# Audio
2017-05-02 03:51:10 +10:00
if typus == "2":
codec = stream.attrib.get('codec')
2018-01-08 03:50:30 +11:00
channellayout = stream.attrib.get('audioChannelLayout', "")
2015-12-25 07:07:00 +11:00
try:
2017-05-02 03:51:10 +10:00
track = "%s %s - %s %s" % (audio_numb+1,
2017-05-01 19:05:51 +10:00
stream.attrib['language'],
codec,
2018-01-08 03:50:30 +11:00
channellayout)
except KeyError:
2017-05-02 03:51:10 +10:00
track = "%s %s - %s %s" % (audio_numb+1,
lang(39707), # unknown
codec,
2018-01-08 03:50:30 +11:00
channellayout)
2017-05-02 03:51:10 +10:00
audio_streams_list.append(index)
audio_streams.append(tryEncode(track))
audio_numb += 1
2015-12-25 07:07:00 +11:00
2016-02-05 06:23:04 +11:00
# Subtitles
2017-05-02 03:51:10 +10:00
elif typus == "3":
2015-12-25 07:07:00 +11:00
try:
2017-05-02 03:51:10 +10:00
track = "%s %s" % (sub_num+1, stream.attrib['language'])
except KeyError:
track = "%s %s (%s)" % (sub_num+1,
lang(39707), # unknown
stream.attrib.get('codec'))
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:
2017-05-02 03:51:10 +10:00
track = "%s - %s" % (track, lang(39708)) # Default
2015-12-25 07:07:00 +11:00
if forced:
2017-05-02 03:51:10 +10:00
track = "%s - %s" % (track, lang(39709)) # Forced
if downloadable:
2017-05-02 03:51:10 +10:00
# We do know the language - temporarily download
if 'language' in stream.attrib:
2018-01-08 03:50:30 +11:00
path = self.api.download_external_subtitles(
2017-05-02 03:51:10 +10:00
'{server}%s' % stream.attrib['key'],
"subtitle.%s.%s" % (stream.attrib['language'],
stream.attrib['codec']))
# We don't know the language - no need to download
else:
2018-01-08 03:50:30 +11:00
path = self.api.addPlexCredentialsToUrl(
2017-05-18 00:00:43 +10:00
"%s%s" % (window('pms_server'),
stream.attrib['key']))
2017-05-02 03:51:10 +10:00
downloadable_streams.append(index)
download_subs.append(tryEncode(path))
2016-09-08 22:51:57 +10:00
else:
2017-05-02 03:51:10 +10:00
track = "%s (%s)" % (track, lang(39710)) # burn-in
if stream.attrib.get('selected') == '1' and downloadable:
# Only show subs without asking user if they can be
# turned off
2017-05-02 03:51:10 +10:00
default_sub = index
2015-12-25 07:07:00 +11:00
2017-05-02 03:51:10 +10:00
subtitle_streams_list.append(index)
subtitle_streams.append(tryEncode(track))
sub_num += 1
2015-12-25 07:07:00 +11:00
2017-05-02 03:51:10 +10:00
if audio_numb > 1:
resp = dialog('select', lang(33013), audio_streams)
2015-12-25 07:07:00 +11:00
if resp > -1:
2017-05-02 03:51:10 +10:00
# User selected some audio track
args = {
'audioStreamID': audio_streams_list[resp],
'allParts': 1
}
2018-01-24 05:13:47 +11:00
DU().downloadUrl('{server}/library/parts/%s' % part_id,
action_type='PUT',
parameters=args)
2017-05-02 03:51:10 +10:00
if sub_num == 1:
# No subtitles
return
select_subs_index = None
if (settings('pickPlexSubtitles') == 'true' and
default_sub is not None):
2018-01-08 03:50:30 +11:00
LOG.info('Using default Plex subtitle: %s', default_sub)
2017-05-02 03:51:10 +10:00
select_subs_index = default_sub
2017-05-01 19:05:51 +10:00
else:
2017-05-02 03:51:10 +10:00
resp = dialog('select', lang(33014), subtitle_streams)
if resp > 0:
select_subs_index = subtitle_streams_list[resp-1]
else:
2017-05-02 03:51:10 +10:00
# User selected no subtitles or backed out of dialog
select_subs_index = ''
2018-01-08 03:50:30 +11:00
LOG.debug('Adding external subtitles: %s', download_subs)
2017-05-02 03:51:10 +10:00
# Enable Kodi to switch autonomously to downloadable subtitles
if download_subs:
listitem.setSubtitles(download_subs)
2018-01-08 03:50:30 +11:00
# Don't additionally burn in subtitles
2017-05-02 03:51:10 +10:00
if select_subs_index in downloadable_streams:
select_subs_index = ''
2018-01-08 03:50:30 +11:00
# Now prep the PMS for our choice
2017-05-02 03:51:10 +10:00
args = {
'subtitleStreamID': select_subs_index,
'allParts': 1
}
2018-01-24 05:13:47 +11:00
DU().downloadUrl('{server}/library/parts/%s' % part_id,
action_type='PUT',
parameters=args)