Revamp playback start, part 1

This commit is contained in:
tomkat83 2018-01-07 17:50:30 +01:00
parent 671424ecbe
commit f0a2955b83
4 changed files with 157 additions and 115 deletions

View file

@ -1181,6 +1181,13 @@ class API():
"""
return self.item.attrib.get('key', '')
def plex_media_streams(self):
"""
Returns the media streams directly from the PMS xml.
Mind self.mediastream to be set before and self.part!
"""
return self.item[self.mediastream][self.part]
def getFilePath(self, forceFirstMediaStream=False):
"""
Returns the direct path to this item, e.g. '\\NAS\movies\movie.mkv'
@ -2343,9 +2350,8 @@ class API():
url = transcodePath + urlencode(xargs) + '&' + urlencode(args)
return url
def externalSubs(self, playurl):
def externalSubs(self):
externalsubs = []
mapping = {}
try:
mediastreams = self.item[0][self.part]
except (TypeError, KeyError, IndexError):
@ -2375,13 +2381,9 @@ class API():
else:
path = self.addPlexCredentialsToUrl(
"%s%s" % (self.server, key))
# map external subtitles for mapping
mapping[kodiindex] = stream.attrib['id']
externalsubs.append(path)
kodiindex += 1
mapping = dumps(mapping)
window('plex_%s.indexMapping' % playurl, value=mapping)
log.info('Found external subs: %s' % externalsubs)
log.info('Found external subs: %s', externalsubs)
return externalsubs
@staticmethod
@ -2402,13 +2404,14 @@ class API():
log.error('Could not temporarily download subtitle %s' % url)
return
else:
log.debug('Writing temp subtitle to %s' % path)
log.debug('Writing temp subtitle to %s', path)
try:
with open(path, 'wb') as f:
f.write(r.content)
except UnicodeEncodeError:
log.debug('Need to slugify the filename %s' % path)
with open(slugify(path), 'wb') as f:
log.debug('Need to slugify the filename %s', path)
path = slugify(path)
with open(path, 'wb') as f:
f.write(r.content)
return path

84
resources/lib/playback.py Normal file
View file

@ -0,0 +1,84 @@
"""
Used to kick off Kodi playback
"""
from PlexAPI import API
import playqueue as PQ
from playutils import PlayUtils
from PKC_listitem import PKC_ListItem, convert_PKC_to_listitem
from pickler import Playback_Successful
from utils import settings, dialog, language as lang
def playback_setup(plex_id, kodi_id, kodi_type, path):
"""
Get XML
For the single element, e.g. including trailers and parts
For playQueue (init by Companion or Alexa)
Set up
PKC/Kodi/Plex Playqueue
Trailers
Clips
Several parts
companion playqueue
Alexa music album
"""
trailers = False
if (api.getType() == v.PLEX_TYPE_MOVIE and
not seektime and
sizePlaylist < 2 and
settings('enableCinema') == "true"):
if settings('askCinema') == "true":
trailers = dialog('yesno', lang(29999), "Play trailers?")
trailers = True if trailers else False
else:
trailers = True
# Post to the PMS. REUSE THE PLAYQUEUE!
xml = init_plex_playqueue(plex_id,
plex_lib_UUID,
mediatype=api.getType(),
trailers=trailers)
pass
def conclude_playback_startup(playqueue_no,
pos,
plex_id=None,
kodi_id=None,
kodi_type=None,
path=None):
"""
ONLY if actually being played (e.g. at 5th position of a playqueue).
Decide on direct play, direct stream, transcoding
path to
direct paths: file itself
PMS URL
Web URL
audiostream (e.g. let user choose)
subtitle stream (e.g. let user choose)
Init Kodi Playback (depending on situation):
start playback
return PKC listitem attached to result
"""
result = Playback_Successful()
listitem = PKC_ListItem()
playqueue = PQ.PLAYQUEUES[playqueue_no]
item = playqueue.items[pos]
api = API(item.xml)
api.setPartNumber(item.part)
api.CreateListItemFromPlexItem(listitem)
if plex_id is not None:
playutils = PlayUtils(api, item)
playurl = playutils.getPlayUrl()
elif path is not None:
playurl = path
item.playmethod = 'DirectStream'
listitem.setPath(playurl)
if item.playmethod in ("DirectStream", "DirectPlay"):
listitem.setSubtitles(api.externalSubs())
else:
playutils.audio_subtitle_prefs(listitem)
listitem.setPath(playurl)
result.listitem = listitem
return result

View file

@ -189,6 +189,7 @@ class Playlist_Item(object):
uri = None [str] Weird Plex uri path involving plex_uuid. STRING!
guid = None [str] Weird Plex guid
xml = None [etree] XML from PMS, 1 lvl below <MediaContainer>
playmethod = None [str] either 'DirectPlay', 'DirectStream', 'Transcode'
"""
def __init__(self):
self.id = None
@ -201,6 +202,7 @@ class Playlist_Item(object):
self.uri = None
self.guid = None
self.xml = None
self.playmethod = None
# Yet to be implemented: handling of a movie with several parts
self.part = 0

View file

@ -4,63 +4,49 @@
from logging import getLogger
from downloadutils import DownloadUtils
from utils import window, settings, tryEncode, language as lang, dialog
from utils import window, settings, language as lang, dialog, tryEncode
import variables as v
import PlexAPI
###############################################################################
log = getLogger("PLEX."+__name__)
LOG = getLogger("PLEX." + __name__)
###############################################################################
class PlayUtils():
def __init__(self, item):
self.item = item
self.API = PlexAPI.API(item)
def __init__(self, api, playqueue_item):
self.api = api
self.item = playqueue_item
self.doUtils = DownloadUtils().downloadUrl
self.machineIdentifier = window('plex_machineIdentifier')
def getPlayUrl(self, partNumber=None):
def getPlayUrl(self):
"""
Returns the playurl for the part with number partNumber
Returns the playurl for the part
(movie might consist of several files)
playurl is utf-8 encoded!
playurl is in unicode!
"""
self.API.setPartNumber(partNumber)
self.API.getMediastreamNumber()
self.api.getMediastreamNumber()
playurl = self.isDirectPlay()
if playurl is not None:
log.info("File is direct playing.")
playurl = tryEncode(playurl)
# Set playmethod property
window('plex_%s.playmethod' % playurl, "DirectPlay")
LOG.info("File is direct playing.")
self.item.playmethod = 'DirectPlay'
elif self.isDirectStream():
log.info("File is direct streaming.")
playurl = tryEncode(
self.API.getTranscodeVideoPath('DirectStream'))
# Set playmethod property
window('plex_%s.playmethod' % playurl, "DirectStream")
LOG.info("File is direct streaming.")
playurl = self.api.getTranscodeVideoPath('DirectStream')
self.item.playmethod = 'DirectStream'
else:
log.info("File is transcoding.")
playurl = tryEncode(self.API.getTranscodeVideoPath(
LOG.info("File is transcoding.")
playurl = self.api.getTranscodeVideoPath(
'Transcode',
quality={
'maxVideoBitrate': self.get_bitrate(),
'videoResolution': self.get_resolution(),
'videoQuality': '100',
'mediaBufferSize': int(settings('kodi_video_cache'))/1024,
}))
# Set playmethod property
window('plex_%s.playmethod' % playurl, value="Transcode")
log.info("The playurl is: %s" % playurl)
})
self.item.playmethod = 'Transcode'
LOG.info("The playurl is: %s", playurl)
return playurl
def isDirectPlay(self):
@ -68,52 +54,29 @@ class PlayUtils():
Returns the path/playurl if we can direct play, None otherwise
"""
# True for e.g. plex.tv watch later
if self.API.shouldStream() is True:
log.info("Plex item optimized for direct streaming")
if self.api.shouldStream() is True:
LOG.info("Plex item optimized for direct streaming")
return
# Check whether we have a strm file that we need to throw at Kodi 1:1
path = self.API.getFilePath()
path = self.api.getFilePath()
if path is not None and path.endswith('.strm'):
log.info('.strm file detected')
playurl = self.API.validatePlayurl(path,
self.API.getType(),
LOG.info('.strm file detected')
playurl = self.api.validatePlayurl(path,
self.api.getType(),
forceCheck=True)
if playurl is None:
return
else:
return tryEncode(playurl)
return playurl
# set to either 'Direct Stream=1' or 'Transcode=2'
# and NOT to 'Direct Play=0'
if settings('playType') != "0":
# User forcing to play via HTTP
log.info("User chose to not direct play")
LOG.info("User chose to not direct play")
return
if self.mustTranscode():
return
return self.API.validatePlayurl(path,
self.API.getType(),
return self.api.validatePlayurl(path,
self.api.getType(),
forceCheck=True)
def directPlay(self):
try:
playurl = self.item['MediaSources'][0]['Path']
except (IndexError, KeyError):
playurl = self.item['Path']
if self.item.get('VideoType'):
# Specific format modification
if self.item['VideoType'] == "Dvd":
playurl = "%s/VIDEO_TS/VIDEO_TS.IFO" % playurl
elif self.item['VideoType'] == "BluRay":
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 mustTranscode(self):
"""
Returns True if we need to transcode because
@ -125,42 +88,42 @@ class PlayUtils():
- video bitrate above specified settings bitrate
if the corresponding file settings are set to 'true'
"""
if self.API.getType() in (v.PLEX_TYPE_CLIP, v.PLEX_TYPE_SONG):
log.info('Plex clip or music track, not transcoding')
if self.api.getType() in (v.PLEX_TYPE_CLIP, v.PLEX_TYPE_SONG):
LOG.info('Plex clip or music track, not transcoding')
return False
videoCodec = self.API.getVideoCodec()
log.info("videoCodec: %s" % videoCodec)
videoCodec = self.api.getVideoCodec()
LOG.info("videoCodec: %s" % videoCodec)
if window('plex_forcetranscode') == 'true':
log.info('User chose to force-transcode')
LOG.info('User chose to force-transcode')
return True
codec = videoCodec['videocodec']
if codec is None:
# e.g. trailers. Avoids TypeError with "'h265' in codec"
log.info('No codec from PMS, not transcoding.')
LOG.info('No codec from PMS, not transcoding.')
return False
if ((settings('transcodeHi10P') == 'true' and
videoCodec['bitDepth'] == '10') and
('h264' in codec)):
log.info('Option to transcode 10bit h264 video content enabled.')
LOG.info('Option to transcode 10bit h264 video content enabled.')
return True
try:
bitrate = int(videoCodec['bitrate'])
except (TypeError, ValueError):
log.info('No video bitrate from PMS, not transcoding.')
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'
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()))
return True
try:
resolution = int(videoCodec['resolution'])
except (TypeError, ValueError):
log.info('No video resolution from PMS, not transcoding.')
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 "
LOG.info("Option to transcode h265/HEVC enabled. Resolution "
"of the media: %s, transcoding limit resolution: %s"
% (str(resolution), str(self.getH265())))
return True
@ -168,12 +131,12 @@ class PlayUtils():
def isDirectStream(self):
# Never transcode Music
if self.API.getType() == 'track':
if self.api.getType() == 'track':
return True
# set to 'Transcode=2'
if settings('playType') == "2":
# User forcing to play via HTTP
log.info("User chose to transcode")
LOG.info("User chose to transcode")
return False
if self.mustTranscode():
return False
@ -255,7 +218,7 @@ class PlayUtils():
}
return res[chosen]
def audioSubsPref(self, listitem, url, part=None):
def audio_subtitle_prefs(self, listitem):
"""
For transcoding only
@ -263,15 +226,13 @@ class PlayUtils():
stream by a PUT request to the PMS
"""
# Set media and part where we're at
if self.API.mediastream is None:
self.API.getMediastreamNumber()
if part is None:
part = 0
if self.api.mediastream is None:
self.api.getMediastreamNumber()
try:
mediastreams = self.item[self.API.mediastream][part]
mediastreams = self.api.plex_media_streams()
except (TypeError, IndexError):
log.error('Could not get media %s, part %s'
% (self.API.mediastream, part))
LOG.error('Could not get media %s, part %s',
self.api.mediastream, self.api.part)
return
part_id = mediastreams.attrib['id']
audio_streams_list = []
@ -296,17 +257,17 @@ class PlayUtils():
# Audio
if typus == "2":
codec = stream.attrib.get('codec')
channelLayout = stream.attrib.get('audioChannelLayout', "")
channellayout = stream.attrib.get('audioChannelLayout', "")
try:
track = "%s %s - %s %s" % (audio_numb+1,
stream.attrib['language'],
codec,
channelLayout)
except:
channellayout)
except KeyError:
track = "%s %s - %s %s" % (audio_numb+1,
lang(39707), # unknown
codec,
channelLayout)
channellayout)
audio_streams_list.append(index)
audio_streams.append(tryEncode(track))
audio_numb += 1
@ -330,13 +291,13 @@ class PlayUtils():
if downloadable:
# We do know the language - temporarily download
if 'language' in stream.attrib:
path = self.API.download_external_subtitles(
path = self.api.download_external_subtitles(
'{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:
path = self.API.addPlexCredentialsToUrl(
path = self.api.addPlexCredentialsToUrl(
"%s%s" % (window('pms_server'),
stream.attrib['key']))
downloadable_streams.append(index)
@ -371,7 +332,7 @@ class PlayUtils():
select_subs_index = None
if (settings('pickPlexSubtitles') == 'true' and
default_sub is not None):
log.info('Using default Plex subtitle: %s' % default_sub)
LOG.info('Using default Plex subtitle: %s', default_sub)
select_subs_index = default_sub
else:
resp = dialog('select', lang(33014), subtitle_streams)
@ -381,22 +342,14 @@ class PlayUtils():
# User selected no subtitles or backed out of dialog
select_subs_index = ''
log.debug('Adding external subtitles: %s' % download_subs)
LOG.debug('Adding external subtitles: %s', download_subs)
# Enable Kodi to switch autonomously to downloadable subtitles
if download_subs:
listitem.setSubtitles(download_subs)
if select_subs_index in downloadable_streams:
for i, stream in enumerate(downloadable_streams):
if stream == select_subs_index:
# Set the correct subtitle
window('plex_%s.subtitle' % tryEncode(url), value=str(i))
break
# Don't additionally burn in subtitles
if select_subs_index in downloadable_streams:
select_subs_index = ''
else:
window('plex_%s.subtitle' % tryEncode(url), value='None')
# Now prep the PMS for our choice
args = {
'subtitleStreamID': select_subs_index,
'allParts': 1