Merge pull request #1638 from croneter/streams
Tell the PMS if a video's audio stream or potentially subtitle stream has changed. For subtitles, this functionality is broken due to a Kodi bug
This commit is contained in:
commit
097fd4cfa2
5 changed files with 206 additions and 86 deletions
|
@ -421,6 +421,41 @@ def get_item(playerid):
|
|||
'properties': ['title', 'file']})['result']['item']
|
||||
|
||||
|
||||
def get_current_audio_stream_index(playerid):
|
||||
"""
|
||||
Returns the currently active audio stream index [int]
|
||||
"""
|
||||
return JsonRPC('Player.GetProperties').execute({
|
||||
'playerid': playerid,
|
||||
'properties': ['currentaudiostream']})['result']['currentaudiostream']['index']
|
||||
|
||||
|
||||
def get_current_subtitle_stream_index(playerid):
|
||||
"""
|
||||
Returns the currently active subtitle stream index [int] or None if there
|
||||
are no subs
|
||||
PICKING UP CHANGES ON SUBTITLES IS CURRENTLY BROKEN ON THE KODI SIDE! The
|
||||
JSON reply won't change even though subtitles are changed :-(
|
||||
"""
|
||||
try:
|
||||
return JsonRPC('Player.GetProperties').execute({
|
||||
'playerid': playerid,
|
||||
'properties': ['currentsubtitle', ]})['result']['currentsubtitle']['index']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
def get_subtitle_enabled(playerid):
|
||||
"""
|
||||
Returns True if a subtitle is currently enabled, False otherwise.
|
||||
PICKING UP CHANGES ON SUBTITLES IS CURRENTLY BROKEN ON THE KODI SIDE! The
|
||||
JSON reply won't change even though subtitles are changed :-(
|
||||
"""
|
||||
return JsonRPC('Player.GetProperties').execute({
|
||||
'playerid': playerid,
|
||||
'properties': ['subtitleenabled', ]})['result']['subtitleenabled']
|
||||
|
||||
|
||||
def get_player_props(playerid):
|
||||
"""
|
||||
Returns a dict for the active Kodi player with the following values:
|
||||
|
|
|
@ -375,10 +375,39 @@ class KodiMonitor(xbmc.Monitor):
|
|||
Example data as returned by Kodi:
|
||||
{'item': {'id': 5, 'type': 'movie'},
|
||||
'player': {'playerid': 1, 'speed': 1}}
|
||||
|
||||
PICKING UP CHANGES ON SUBTITLES IS CURRENTLY BROKEN ON THE KODI SIDE!
|
||||
Kodi subs will never change. Also see json_rpc.py
|
||||
"""
|
||||
playerid = data['player']['playerid']
|
||||
if not playerid == v.KODI_VIDEO_PLAYER_ID:
|
||||
# We're just messing with Kodi's videoplayer
|
||||
return
|
||||
if not self._switched_to_plex_streams:
|
||||
# We need to switch to the Plex streams ONCE upon playback start
|
||||
# after onavchange has been fired
|
||||
self.switch_to_plex_streams()
|
||||
self._switched_to_plex_streams = True
|
||||
else:
|
||||
item = app.PLAYSTATE.item
|
||||
if item is None:
|
||||
# Player might've quit
|
||||
return
|
||||
kodi_audio_stream = js.get_current_audio_stream_index(playerid)
|
||||
sub_enabled = js.get_subtitle_enabled(playerid)
|
||||
kodi_sub_stream = js.get_current_subtitle_stream_index(playerid)
|
||||
# Audio
|
||||
if kodi_audio_stream != item.current_kodi_audio_stream:
|
||||
item.on_kodi_audio_stream_change(kodi_audio_stream)
|
||||
# Subtitles - CURRENTLY BROKEN ON THE KODI SIDE!
|
||||
# current_kodi_sub_stream may also be zero
|
||||
subs_off = (None, False)
|
||||
if ((sub_enabled and item.current_kodi_sub_stream in subs_off)
|
||||
or (not sub_enabled and item.current_kodi_sub_stream not in subs_off)
|
||||
or (kodi_sub_stream is not None
|
||||
and kodi_sub_stream != item.current_kodi_sub_stream)):
|
||||
item.on_kodi_subtitle_stream_change(kodi_sub_stream,
|
||||
sub_enabled)
|
||||
|
||||
@staticmethod
|
||||
def switch_to_plex_streams():
|
||||
|
@ -393,30 +422,33 @@ class KodiMonitor(xbmc.Monitor):
|
|||
try:
|
||||
plex_index, language_tag = item.active_plex_stream_index(typus)
|
||||
except TypeError:
|
||||
if typus == 'subtitle':
|
||||
LOG.info('Deactivating Kodi subtitles because the PMS '
|
||||
'told us to not show any subtitles')
|
||||
app.APP.player.showSubtitles(False)
|
||||
LOG.debug('Deactivating Kodi subtitles because the PMS '
|
||||
'told us to not show any subtitles')
|
||||
app.APP.player.showSubtitles(False)
|
||||
item.current_kodi_sub_stream = False
|
||||
continue
|
||||
LOG.info('The PMS wants to display %s stream with Plex id %s and '
|
||||
'languageTag %s',
|
||||
typus, plex_index, language_tag)
|
||||
kodi_index = item.kodi_stream_index(plex_index,
|
||||
typus)
|
||||
LOG.debug('The PMS wants to display %s stream with Plex id %s and '
|
||||
'languageTag %s',
|
||||
typus, plex_index, language_tag)
|
||||
kodi_index = item.kodi_stream_index(plex_index, typus)
|
||||
if kodi_index is None:
|
||||
LOG.info('Leaving Kodi %s stream settings untouched since we '
|
||||
'could not parse Plex %s stream with id %s to a Kodi '
|
||||
'index', typus, typus, plex_index)
|
||||
LOG.debug('Leaving Kodi %s stream settings untouched since we '
|
||||
'could not parse Plex %s stream with id %s to a Kodi'
|
||||
' index', typus, typus, plex_index)
|
||||
else:
|
||||
LOG.info('Switching to Kodi %s stream number %s because the '
|
||||
'PMS told us to show stream with Plex id %s',
|
||||
typus, kodi_index, plex_index)
|
||||
LOG.debug('Switching to Kodi %s stream number %s because the '
|
||||
'PMS told us to show stream with Plex id %s',
|
||||
typus, kodi_index, plex_index)
|
||||
# If we're choosing an "illegal" index, this function does
|
||||
# need seem to fail nor log any errors
|
||||
if typus == 'subtitle':
|
||||
app.APP.player.setSubtitleStream(kodi_index)
|
||||
else:
|
||||
if typus == 'audio':
|
||||
app.APP.player.setAudioStream(kodi_index)
|
||||
else:
|
||||
app.APP.player.setSubtitleStream(kodi_index)
|
||||
if typus == 'audio':
|
||||
item.current_kodi_audio_stream = kodi_index
|
||||
else:
|
||||
item.current_kodi_sub_stream = kodi_index
|
||||
|
||||
|
||||
def _playback_cleanup(ended=False):
|
||||
|
|
|
@ -12,6 +12,7 @@ from . import plex_functions as PF
|
|||
from .kodi_db import kodiid_from_filename
|
||||
from .downloadutils import DownloadUtils as DU
|
||||
from . import utils
|
||||
from .utils import cast
|
||||
from . import json_rpc as js
|
||||
from . import variables as v
|
||||
from . import app
|
||||
|
@ -180,6 +181,16 @@ class PlaylistItem(object):
|
|||
# False: do NOT resume, don't ask user
|
||||
# True: do resume, don't ask user
|
||||
self.resume = None
|
||||
# Get the Plex audio and subtitle streams in the same order as Kodi
|
||||
# uses them (Kodi uses indexes to activate them, not ids like Plex)
|
||||
self._streams_have_been_processed = False
|
||||
self._audio_streams = None
|
||||
self._subtitle_streams = None
|
||||
# Which Kodi streams are active?
|
||||
self.current_kodi_audio_stream = None
|
||||
# False means "deactivated", None means "we do not have a Kodi
|
||||
# equivalent for this Plex subtitle"
|
||||
self.current_kodi_sub_stream = None
|
||||
|
||||
@property
|
||||
def plex_id(self):
|
||||
|
@ -195,6 +206,18 @@ class PlaylistItem(object):
|
|||
def uri(self):
|
||||
return self._uri
|
||||
|
||||
@property
|
||||
def audio_streams(self):
|
||||
if not self._streams_have_been_processed:
|
||||
self._process_streams()
|
||||
return self._audio_streams
|
||||
|
||||
@property
|
||||
def subtitle_streams(self):
|
||||
if not self._streams_have_been_processed:
|
||||
self._process_streams()
|
||||
return self._subtitle_streams
|
||||
|
||||
def __unicode__(self):
|
||||
return ("{{"
|
||||
"'id': {self.id}, "
|
||||
|
@ -214,85 +237,100 @@ class PlaylistItem(object):
|
|||
def __repr__(self):
|
||||
return self.__unicode__().encode('utf-8')
|
||||
|
||||
def _process_streams(self):
|
||||
"""
|
||||
Builds audio and subtitle streams and enables matching between Plex
|
||||
and Kodi using self.audio_streams and self.subtitle_streams
|
||||
"""
|
||||
# The playqueue response from the PMS does not contain a stream filename
|
||||
# thanks Plex
|
||||
self._subtitle_streams = accessible_plex_subtitles(
|
||||
self.playmethod,
|
||||
self.file,
|
||||
self.api.plex_media_streams())
|
||||
# Audio streams are much easier - they're always available and sorted
|
||||
# the same in Kodi and Plex
|
||||
self._audio_streams = [x for x in self.api.plex_media_streams()
|
||||
if x.get('streamType') == '2']
|
||||
self._streams_have_been_processed = True
|
||||
|
||||
def _get_iterator(self, stream_type):
|
||||
if stream_type == 'audio':
|
||||
return self.audio_streams
|
||||
elif stream_type == 'subtitle':
|
||||
return self.subtitle_streams
|
||||
|
||||
def plex_stream_index(self, kodi_stream_index, stream_type):
|
||||
"""
|
||||
Pass in the kodi_stream_index [int] in order to receive the Plex stream
|
||||
index.
|
||||
|
||||
index [int].
|
||||
stream_type: 'video', 'audio', 'subtitle'
|
||||
|
||||
Returns None if unsuccessful
|
||||
"""
|
||||
stream_type = v.PLEX_STREAM_TYPE_FROM_STREAM_TYPE[stream_type]
|
||||
count = 0
|
||||
if kodi_stream_index == -1:
|
||||
# Kodi telling us "it's the last one"
|
||||
iterator = list(reversed(self.api.plex_media_streams()))
|
||||
kodi_stream_index = 0
|
||||
else:
|
||||
iterator = self.api.plex_media_streams()
|
||||
# Kodi indexes differently than Plex
|
||||
for stream in iterator:
|
||||
if (stream.get('streamType') == stream_type and
|
||||
'key' in stream.attrib):
|
||||
if count == kodi_stream_index:
|
||||
return stream.get('id')
|
||||
count += 1
|
||||
for stream in iterator:
|
||||
if (stream.get('streamType') == stream_type and
|
||||
'key' not in stream.attrib):
|
||||
if count == kodi_stream_index:
|
||||
return stream.get('id')
|
||||
count += 1
|
||||
if stream_type == 'audio':
|
||||
return int(self.audio_streams[kodi_stream_index].get('id'))
|
||||
elif stream_type == 'subtitle':
|
||||
try:
|
||||
return int(self.subtitle_streams[kodi_stream_index].get('id'))
|
||||
except (IndexError, TypeError):
|
||||
pass
|
||||
|
||||
def kodi_stream_index(self, plex_stream_index, stream_type):
|
||||
"""
|
||||
Pass in the kodi_stream_index [int] in order to receive the Plex stream
|
||||
index.
|
||||
|
||||
Pass in the plex_stream_index [int] in order to receive the Kodi stream
|
||||
index [int].
|
||||
stream_type: 'video', 'audio', 'subtitle'
|
||||
|
||||
Returns None if unsuccessful
|
||||
"""
|
||||
if plex_stream_index is None:
|
||||
return
|
||||
stream_type = v.PLEX_STREAM_TYPE_FROM_STREAM_TYPE[stream_type]
|
||||
count = 0
|
||||
streams = self.sorted_accessible_plex_subtitles(stream_type)
|
||||
for stream in streams:
|
||||
if utils.cast(int, stream.get('id')) == plex_stream_index:
|
||||
return count
|
||||
count += 1
|
||||
for i, stream in enumerate(self._get_iterator(stream_type)):
|
||||
if cast(int, stream.get('id')) == plex_stream_index:
|
||||
return i
|
||||
|
||||
def active_plex_stream_index(self, stream_type):
|
||||
"""
|
||||
Returns the following tuple for the active stream on the Plex side:
|
||||
(id [int], languageTag [str])
|
||||
(Plex stream id [int], languageTag [str] or None)
|
||||
Returns None if no stream has been selected
|
||||
"""
|
||||
stream_type = v.PLEX_STREAM_TYPE_FROM_STREAM_TYPE[stream_type]
|
||||
for stream in self.api.plex_media_streams():
|
||||
if stream.get('streamType') == stream_type \
|
||||
and stream.get('selected') == '1':
|
||||
return (utils.cast(int, stream.get('id')),
|
||||
stream.get('languageTag'))
|
||||
for i, stream in enumerate(self._get_iterator(stream_type)):
|
||||
if stream.get('selected') == '1':
|
||||
return (int(stream.get('id')), stream.get('languageTag'))
|
||||
|
||||
def sorted_accessible_plex_subtitles(self, stream_type):
|
||||
def on_kodi_subtitle_stream_change(self, kodi_stream_index, subs_enabled):
|
||||
"""
|
||||
Returns only the subtitles that Kodi can access when PKC Direct Paths
|
||||
are used; i.e. Kodi has access to a video's directory.
|
||||
NOT supported: additional subtitles downloaded using the Plex interface
|
||||
Call this method if Kodi changed its subtitle and you want Plex to
|
||||
know.
|
||||
"""
|
||||
# The playqueue response from the PMS does not contain a stream filename
|
||||
# thanks Plex
|
||||
if stream_type == '3':
|
||||
streams = accessible_plex_subtitles(self.playmethod,
|
||||
self.file,
|
||||
self.api.plex_media_streams())
|
||||
if subs_enabled:
|
||||
try:
|
||||
plex_stream_index = int(self.subtitle_streams[kodi_stream_index].get('id'))
|
||||
except (IndexError, TypeError):
|
||||
LOG.debug('Kodi subtitle change detected to a sub %s that is '
|
||||
'NOT available on the Plex side', kodi_stream_index)
|
||||
self.current_kodi_sub_stream = None
|
||||
return
|
||||
LOG.debug('Kodi subtitle change detected: telling Plex about '
|
||||
'switch to index %s, Plex stream id %s',
|
||||
kodi_stream_index, plex_stream_index)
|
||||
self.current_kodi_sub_stream = kodi_stream_index
|
||||
else:
|
||||
streams = [x for x in self.api.plex_media_streams()
|
||||
if x.get('streamType') == stream_type]
|
||||
return streams
|
||||
plex_stream_index = 0
|
||||
LOG.debug('Kodi subtitle has been deactivated, telling Plex')
|
||||
self.current_kodi_sub_stream = False
|
||||
PF.change_subtitle(plex_stream_index, self.api.part_id())
|
||||
|
||||
def on_kodi_audio_stream_change(self, kodi_stream_index):
|
||||
"""
|
||||
Call this method if Kodi changed its audio stream and you want Plex to
|
||||
know. kodi_stream_index [int]
|
||||
"""
|
||||
plex_stream_index = int(self.audio_streams[kodi_stream_index].get('id'))
|
||||
LOG.debug('Changing Plex audio stream to %s, Kodi index %s',
|
||||
plex_stream_index, kodi_stream_index)
|
||||
PF.change_audio_stream(plex_stream_index, self.api.part_id())
|
||||
self.current_kodi_audio_stream = kodi_stream_index
|
||||
|
||||
|
||||
def playlist_item_from_kodi(kodi_item):
|
||||
|
@ -318,7 +356,7 @@ def playlist_item_from_kodi(kodi_item):
|
|||
except IndexError:
|
||||
query = ''
|
||||
query = dict(utils.parse_qsl(query))
|
||||
item.plex_id = utils.cast(int, query.get('plex_id'))
|
||||
item.plex_id = cast(int, query.get('plex_id'))
|
||||
item.plex_type = query.get('itemType')
|
||||
LOG.debug('Made playlist item from Kodi: %s', item)
|
||||
return item
|
||||
|
@ -445,18 +483,13 @@ def get_playlist_details_from_xml(playlist, xml):
|
|||
"""
|
||||
if xml is None:
|
||||
raise PlaylistError('No playlist received for playlist %s' % playlist)
|
||||
playlist.id = utils.cast(int,
|
||||
xml.get('%sID' % playlist.kind))
|
||||
playlist.version = utils.cast(int,
|
||||
xml.get('%sVersion' % playlist.kind))
|
||||
playlist.shuffled = utils.cast(int,
|
||||
xml.get('%sShuffled' % playlist.kind))
|
||||
playlist.selectedItemID = utils.cast(int,
|
||||
xml.get('%sSelectedItemID'
|
||||
% playlist.kind))
|
||||
playlist.selectedItemOffset = utils.cast(int,
|
||||
xml.get('%sSelectedItemOffset'
|
||||
% playlist.kind))
|
||||
playlist.id = cast(int, xml.get('%sID' % playlist.kind))
|
||||
playlist.version = cast(int, xml.get('%sVersion' % playlist.kind))
|
||||
playlist.shuffled = cast(int, xml.get('%sShuffled' % playlist.kind))
|
||||
playlist.selectedItemID = cast(int, xml.get('%sSelectedItemID'
|
||||
% playlist.kind))
|
||||
playlist.selectedItemOffset = cast(int, xml.get('%sSelectedItemOffset'
|
||||
% playlist.kind))
|
||||
LOG.debug('Updated playlist from xml: %s', playlist)
|
||||
|
||||
|
||||
|
|
|
@ -1133,3 +1133,19 @@ def change_subtitle(plex_stream_id, part_id):
|
|||
url = '{server}/library/parts/%s' % part_id
|
||||
return DU().downloadUrl(utils.extend_url(url, arguments),
|
||||
action_type='PUT')
|
||||
|
||||
|
||||
def change_audio_stream(plex_stream_id, part_id):
|
||||
"""
|
||||
Tell the PMS to display/burn-in the subtitle stream with id plex_stream_id
|
||||
for the Part (PMS XML etree tag "Part") with unique id part_id.
|
||||
- plex_stream_id = 0 will deactivate the subtitle
|
||||
- We always do this for ALL parts of a video
|
||||
"""
|
||||
arguments = {
|
||||
'audioStreamID': plex_stream_id,
|
||||
'allParts': 1
|
||||
}
|
||||
url = '{server}/library/parts/%s' % part_id
|
||||
return DU().downloadUrl(utils.extend_url(url, arguments),
|
||||
action_type='PUT')
|
||||
|
|
|
@ -45,6 +45,10 @@ ADDON_PATH = try_decode(_ADDON.getAddonInfo('path'))
|
|||
ADDON_FOLDER = try_decode(xbmc.translatePath('special://home'))
|
||||
ADDON_PROFILE = try_decode(xbmc.translatePath(_ADDON.getAddonInfo('profile')))
|
||||
|
||||
# Used e.g. for json_rpc
|
||||
KODI_VIDEO_PLAYER_ID = 1
|
||||
KODI_AUDIO_PLAYER_ID = 0
|
||||
|
||||
KODILANGUAGE = xbmc.getLanguage(xbmc.ISO_639_1)
|
||||
KODIVERSION = int(xbmc.getInfoLabel("System.BuildVersion")[:2])
|
||||
KODILONGVERSION = xbmc.getInfoLabel('System.BuildVersion')
|
||||
|
|
Loading…
Reference in a new issue