Revamp playback start, part 1
This commit is contained in:
parent
671424ecbe
commit
f0a2955b83
4 changed files with 157 additions and 115 deletions
|
@ -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
84
resources/lib/playback.py
Normal 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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
# Don't additionally burn in subtitles
|
||||
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
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue