Merge pull request #1131 from croneter/fix-playback

Rewire the set-up of audio and subtitle streams, esp. before starting a transcoding session. Fixes playback not starting at all
This commit is contained in:
croneter 2020-02-25 18:21:20 +01:00 committed by GitHub
commit 5aceb223ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 104 additions and 98 deletions

View file

@ -291,8 +291,8 @@ class DownloadUtils():
return return
else: else:
r.encoding = 'utf-8' r.encoding = 'utf-8'
LOG.warn('Unknown answer from PMS %s with status code %s. ', LOG.warn('Unknown answer from PMS %s with status code %s: %s',
url, r.status_code) url, r.status_code, r.text)
return True return True
finally: finally:

View file

@ -12,16 +12,10 @@ import xbmc
from .plex_api import API from .plex_api import API
from .plex_db import PlexDB from .plex_db import PlexDB
from . import plex_functions as PF
from . import utils
from .kodi_db import KodiVideoDB from .kodi_db import KodiVideoDB
from . import playlist_func as PL from . import plex_functions as PF, playlist_func as PL, playqueue as PQ
from . import playqueue as PQ from . import json_rpc as js, variables as v, utils, transfer
from . import json_rpc as js from . import playback_decision, app
from . import transfer
from .playback_decision import set_playurl, audio_subtitle_prefs
from . import variables as v
from . import app
############################################################################### ###############################################################################
LOG = getLogger('PLEX.playback') LOG = getLogger('PLEX.playback')
@ -457,21 +451,20 @@ def _conclude_playback(playqueue, pos):
_ensure_resolve() _ensure_resolve()
return return
api.part = item.part or 0 api.part = item.part or 0
listitem = api.listitem(listitem=transfer.PKCListItem, resume=False) playback_decision.set_pkc_playmethod(api, item)
set_playurl(api, item) if not playback_decision.audio_subtitle_prefs(api, item):
if not item.file: LOG.info('Did not set audio subtitle prefs, aborting silently')
LOG.debug('Did not get a playurl, aborting playback silently')
_ensure_resolve() _ensure_resolve()
return return
playback_decision.set_playurl(api, item)
if not item.file:
LOG.info('Did not get a playurl, aborting playback silently')
_ensure_resolve()
return
listitem = api.listitem(listitem=transfer.PKCListItem, resume=False)
listitem.setPath(item.file.encode('utf-8')) listitem.setPath(item.file.encode('utf-8'))
if item.playmethod == v.PLAYBACK_METHOD_DIRECT_PLAY: if item.playmethod != v.PLAYBACK_METHOD_DIRECT_PATH:
listitem.setSubtitles(api.cache_external_subs()) listitem.setSubtitles(api.cache_external_subs())
elif item.playmethod in (v.PLAYBACK_METHOD_DIRECT_STREAM,
v.PLAYBACK_METHOD_TRANSCODE):
audio_subtitle_prefs(api, listitem)
# Need to hit the PMS api again in order to get the selected
# burn-in subtitles set-up correctly
set_playurl(api, item)
transfer.send(listitem) transfer.send(listitem)
LOG.debug('Done concluding playback') LOG.debug('Done concluding playback')

View file

@ -18,30 +18,36 @@ DIRECT_PLAY_OK = 1000
CONVERSION_OK = 1001 # PMS can either direct stream or transcode CONVERSION_OK = 1001 # PMS can either direct stream or transcode
def set_playurl(api, item): def set_pkc_playmethod(api, item):
item.playmethod = int(utils.settings('playType')) item.playmethod = int(utils.settings('playType'))
LOG.info('User chose playback method %s in PKC settings', LOG.info('User chose playback method %s in PKC settings',
v.EXPLICIT_PLAYBACK_METHOD[item.playmethod]) v.EXPLICIT_PLAYBACK_METHOD[item.playmethod])
_initial_best_playback_method(api, item) _initial_best_playback_method(api, item)
LOG.info('PKC decided on playback method %s', LOG.info('PKC decided on playback method %s',
v.EXPLICIT_PLAYBACK_METHOD[item.playmethod]) v.EXPLICIT_PLAYBACK_METHOD[item.playmethod])
if item.playmethod == v.PLAYBACK_METHOD_DIRECT_PATH:
# No need to ask the PMS whether we can play - we circumvent
# the PMS entirely def set_playurl(api, item):
LOG.info('The playurl for %s is: %s',
v.EXPLICIT_PLAYBACK_METHOD[item.playmethod], item.file)
return
LOG.info('Lets ask the PMS next')
try: try:
_pms_playback_decision(api, item) if item.playmethod == v.PLAYBACK_METHOD_DIRECT_PATH:
except (exceptions.RequestException, AttributeError, IndexError, SystemExit) as err: # No need to ask the PMS whether we can play - we circumvent
LOG.warn('Could not find suitable settings for playback, aborting') # the PMS entirely
LOG.warn('Error received: %s', err) return
item.playmethod = None LOG.info('Lets ask the PMS next')
item.file = None try:
else: _pms_playback_decision(api, item)
item.file = api.transcode_video_path(item.playmethod, except (exceptions.RequestException,
quality=item.quality) AttributeError,
IndexError,
SystemExit):
LOG.warn('Could not find suitable settings for playback, aborting')
utils.ERROR(notify=True)
item.playmethod = None
item.file = None
else:
item.file = api.transcode_video_path(item.playmethod,
quality=item.quality)
finally:
LOG.info('The playurl for %s is: %s', LOG.info('The playurl for %s is: %s',
v.EXPLICIT_PLAYBACK_METHOD[item.playmethod], item.file) v.EXPLICIT_PLAYBACK_METHOD[item.playmethod], item.file)
@ -313,12 +319,16 @@ def _getH265():
return H265[utils.settings('transcodeH265')] return H265[utils.settings('transcodeH265')]
def audio_subtitle_prefs(api, listitem): def audio_subtitle_prefs(api, item):
""" """
For transcoding only Sets the stage for transcoding, letting the user potentially choose both
audio and subtitle streams; subtitle streams to burn-into the video file.
Called at the very beginning of play; used to change audio and subtitle Uses a PUT request to the PMS, simulating e.g. the user using Plex Web,
stream by a PUT request to the PMS choosing a different stream in the video's metadata and THEN initiating
playback.
Returns None if user cancelled or we need to abort, True otherwise
""" """
# Set media and part where we're at # Set media and part where we're at
if (api.mediastream is None and if (api.mediastream is None and
@ -331,13 +341,21 @@ def audio_subtitle_prefs(api, listitem):
api.mediastream, api.part) api.mediastream, api.part)
return return
part_id = mediastreams.attrib['id'] part_id = mediastreams.attrib['id']
if item.playmethod != v.PLAYBACK_METHOD_TRANSCODE:
LOG.debug('Telling PMS we are not burning in any subtitles')
args = {
'subtitleStreamID': 0,
'allParts': 1
}
DU().downloadUrl('{server}/library/parts/%s' % part_id,
action_type='PUT',
parameters=args)
return True
audio_streams_list = [] audio_streams_list = []
audio_streams = [] audio_streams = []
subtitle_streams_list = [] subtitle_streams_list = []
# "Don't burn-in any subtitle" # "Don't burn-in any subtitle"
subtitle_streams = ['1 %s' % utils.lang(39706)] subtitle_streams = ['1 %s' % utils.lang(39706)]
downloadable_streams = []
download_subs = []
# selectAudioIndex = "" # selectAudioIndex = ""
select_subs_index = "" select_subs_index = ""
audio_numb = 0 audio_numb = 0
@ -369,66 +387,58 @@ def audio_subtitle_prefs(api, listitem):
# Subtitles # Subtitles
elif typus == "3": elif typus == "3":
downloadable = stream.get('key') if stream.get('key'):
if downloadable: # Subtitle can and will be downloaded - don't let user choose
# Download the subtitle to Kodi - the user will need to # this subtitle to burn-in
# manually select the subtitle on the Kodi side continue
# Hence do NOT show dialog for this sub # Subtitle is available within the video file
path = api.download_external_subtitles( # Burn in the subtitle, if user chooses to do so
'{{server}}{}'.format(stream.get('key')), default = stream.get('default')
stream.get('displayTitle'), forced = stream.get('forced')
stream.get('codec')) try:
if path: track = '{} {}'.format(sub_num + 1,
downloadable_streams.append(index) stream.attrib['displayTitle'])
download_subs.append(path.encode('utf-8')) except KeyError:
else: track = '{} {} ({})'.format(sub_num + 1,
# Burn in the subtitle, if user chooses to do so utils.lang(39707), # unknown
default = stream.get('default') stream.get('codec'))
forced = stream.get('forced') if default:
try: track = "%s - %s" % (track, utils.lang(39708)) # Default
track = '{} {}'.format(sub_num + 1, if forced:
stream.attrib['displayTitle']) track = "%s - %s" % (track, utils.lang(39709)) # Forced
except KeyError: track = "%s (%s)" % (track, utils.lang(39710)) # burn-in
track = '{} {} ({})'.format(sub_num + 1, subtitle_streams_list.append(index)
utils.lang(39707), # unknown subtitle_streams.append(track.encode('utf-8'))
stream.get('codec')) sub_num += 1
if default:
track = "%s - %s" % (track, utils.lang(39708)) # Default
if forced:
track = "%s - %s" % (track, utils.lang(39709)) # Forced
track = "%s (%s)" % (track, utils.lang(39710)) # burn-in
subtitle_streams_list.append(index)
subtitle_streams.append(track.encode('utf-8'))
sub_num += 1
if audio_numb > 1: if audio_numb > 1:
resp = utils.dialog('select', utils.lang(33013), audio_streams) resp = utils.dialog('select', utils.lang(33013), audio_streams)
if resp > -1: if resp == -1:
# User selected some audio track LOG.info('User aborted dialog to select audio stream')
args = { return
'audioStreamID': audio_streams_list[resp], args = {
'allParts': 1 'audioStreamID': audio_streams_list[resp],
} 'allParts': 1
DU().downloadUrl('{server}/library/parts/%s' % part_id, }
action_type='PUT', DU().downloadUrl('{server}/library/parts/%s' % part_id,
parameters=args) action_type='PUT',
parameters=args)
LOG.debug('Adding downloadable subtitles: %s', download_subs)
# Enable Kodi to switch autonomously to downloadable subtitles
if download_subs:
listitem.setSubtitles(download_subs)
select_subs_index = '' select_subs_index = ''
if sub_num == 1: if sub_num == 1:
# Note: we DO need to tell the PMS that we DONT want any sub # Note: we DO need to tell the PMS that we DONT want any sub
# Otherwise, the PMS might pick-up the last one # Otherwise, the PMS might pick-up the last one
LOG.debug('No subtitles to burn-in') LOG.info('No subtitles to burn-in')
else: else:
resp = utils.dialog('select', utils.lang(33014), subtitle_streams) resp = utils.dialog('select', utils.lang(33014), subtitle_streams)
if resp < 1: if resp == -1:
LOG.info('User aborted dialog to select subtitle stream')
return
elif resp == 0:
# User did not select a subtitle or backed out of the dialog # User did not select a subtitle or backed out of the dialog
LOG.debug('User chose to not burn-in any subtitles') LOG.info('User chose to not burn-in any subtitles')
else: else:
LOG.debug('User chose to burn-in subtitle %s: %s', LOG.info('User chose to burn-in subtitle %s: %s',
select_subs_index, select_subs_index,
subtitle_streams[resp].decode('utf-8')) subtitle_streams[resp].decode('utf-8'))
select_subs_index = subtitle_streams_list[resp - 1] select_subs_index = subtitle_streams_list[resp - 1]
@ -440,3 +450,4 @@ def audio_subtitle_prefs(api, listitem):
DU().downloadUrl('{server}/library/parts/%s' % part_id, DU().downloadUrl('{server}/library/parts/%s' % part_id,
action_type='PUT', action_type='PUT',
parameters=args) parameters=args)
return True

View file

@ -1085,13 +1085,15 @@ def transcoding_arguments(path, media, part, playmethod, args=None):
'protocol': 'hls', # seen in the wild: 'http', 'dash', 'http', 'hls' 'protocol': 'hls', # seen in the wild: 'http', 'dash', 'http', 'hls'
'session': v.PKC_MACHINE_IDENTIFIER, # TODO: create new unique id 'session': v.PKC_MACHINE_IDENTIFIER, # TODO: create new unique id
'fastSeek': 1, 'fastSeek': 1,
# none, embedded, sidecar
# Essentially indicating what you want to do with subtitles and state
# you arent want it to burn them into the video (requires transcoding)
# 'subtitles': 'none',
'subtitleSize': utils.settings('subtitleSize'),
'copyts': 1 'copyts': 1
} }
if playmethod != v.PLAYBACK_METHOD_TRANSCODE:
# Essentially indicating what you want to do with subtitles and state
# you arent want it to burn them into the video (requires transcoding)
# none, embedded, sidecar
args['subtitles'] = 'none'
else:
args['subtitleSize'] = utils.settings('subtitleSize')
if args: if args:
arguments.update(args) arguments.update(args)
return arguments return arguments