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', '') 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): def getFilePath(self, forceFirstMediaStream=False):
""" """
Returns the direct path to this item, e.g. '\\NAS\movies\movie.mkv' 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) url = transcodePath + urlencode(xargs) + '&' + urlencode(args)
return url return url
def externalSubs(self, playurl): def externalSubs(self):
externalsubs = [] externalsubs = []
mapping = {}
try: try:
mediastreams = self.item[0][self.part] mediastreams = self.item[0][self.part]
except (TypeError, KeyError, IndexError): except (TypeError, KeyError, IndexError):
@ -2375,13 +2381,9 @@ class API():
else: else:
path = self.addPlexCredentialsToUrl( path = self.addPlexCredentialsToUrl(
"%s%s" % (self.server, key)) "%s%s" % (self.server, key))
# map external subtitles for mapping
mapping[kodiindex] = stream.attrib['id']
externalsubs.append(path) externalsubs.append(path)
kodiindex += 1 kodiindex += 1
mapping = dumps(mapping) log.info('Found external subs: %s', externalsubs)
window('plex_%s.indexMapping' % playurl, value=mapping)
log.info('Found external subs: %s' % externalsubs)
return externalsubs return externalsubs
@staticmethod @staticmethod
@ -2402,13 +2404,14 @@ class API():
log.error('Could not temporarily download subtitle %s' % url) log.error('Could not temporarily download subtitle %s' % url)
return return
else: else:
log.debug('Writing temp subtitle to %s' % path) log.debug('Writing temp subtitle to %s', path)
try: try:
with open(path, 'wb') as f: with open(path, 'wb') as f:
f.write(r.content) f.write(r.content)
except UnicodeEncodeError: except UnicodeEncodeError:
log.debug('Need to slugify the filename %s' % path) log.debug('Need to slugify the filename %s', path)
with open(slugify(path), 'wb') as f: path = slugify(path)
with open(path, 'wb') as f:
f.write(r.content) f.write(r.content)
return path 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! uri = None [str] Weird Plex uri path involving plex_uuid. STRING!
guid = None [str] Weird Plex guid guid = None [str] Weird Plex guid
xml = None [etree] XML from PMS, 1 lvl below <MediaContainer> xml = None [etree] XML from PMS, 1 lvl below <MediaContainer>
playmethod = None [str] either 'DirectPlay', 'DirectStream', 'Transcode'
""" """
def __init__(self): def __init__(self):
self.id = None self.id = None
@ -201,6 +202,7 @@ class Playlist_Item(object):
self.uri = None self.uri = None
self.guid = None self.guid = None
self.xml = None self.xml = None
self.playmethod = None
# Yet to be implemented: handling of a movie with several parts # Yet to be implemented: handling of a movie with several parts
self.part = 0 self.part = 0

View file

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