From 72ed20e47f0d025ffc3d5176add909998fc125c8 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Mon, 1 May 2017 19:51:10 +0200 Subject: [PATCH] Big transcoding overhaul - Fixes #278 --- .../resource.language.en_gb/strings.po | 27 ++- resources/lib/PlexAPI.py | 5 +- resources/lib/kodimonitor.py | 38 ++-- resources/lib/playbackutils.py | 17 +- resources/lib/player.py | 3 +- resources/lib/playutils.py | 204 ++++++++++-------- 6 files changed, 169 insertions(+), 125 deletions(-) diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 5bbcc7a3..07db049c 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -1851,7 +1851,6 @@ msgctxt "#39702" msgid "Browse by folder" msgstr "" - # For use with addon.xml (PKC metadata for Kodi, e.g. description) # Addon Summary msgctxt "#39703" @@ -1869,3 +1868,29 @@ msgstr "" msgctxt "#39705" msgid "Use at your own risk" msgstr "" + + +# If user gets prompted to choose between several subtitles. Leave the number one at the beginning of the string! +msgctxt "#39706" +msgid "1 No subtitles" +msgstr "" + +# If user gets prompted to choose between several audio/subtitle tracks and language is unknown +msgctxt "#39707" +msgid "unknown" +msgstr "" + +# If user gets prompted to choose between several subtitles and Plex adds the "default" flag +msgctxt "#39708" +msgid "Default" +msgstr "" + +# If user gets prompted to choose between several subtitles and Plex adds the "forced" flag +msgctxt "#39709" +msgid "Forced" +msgstr "" + +# If user gets prompted to choose between several subtitles the subtitle cannot be downloaded (has no 'key' attribute from the PMS), the subtitle needs to be burned in +msgctxt "#39710" +msgid "burn-in" +msgstr "" diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index 7abc874b..7067fd08 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -2282,6 +2282,7 @@ class API(): 'hasMDE': 1, 'location': 'lan', 'mediaBufferSize': '16384', + 'subtitleSize': settings('subtitleSize') # 'copyts': 1, # 'offset': 0, # Resume point } @@ -2313,7 +2314,7 @@ class API(): if key: # We do know the language - temporarily download if stream.attrib.get('languageCode') is not None: - path = self.__download_external_subtitles( + path = self.download_external_subtitles( "{server}%s" % key, "subtitle.%s.%s" % (stream.attrib['languageCode'], stream.attrib['codec'])) @@ -2331,7 +2332,7 @@ class API(): return externalsubs @staticmethod - def __download_external_subtitles(url, filename): + def download_external_subtitles(url, filename): """ One cannot pass the subtitle language for ListItems. Workaround; will download the subtitle at url to the Kodi PKC directory in a temp dir diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index 0dc17443..4e19721e 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -106,7 +106,8 @@ class KodiMonitor(Monitor): log.error("Could not find itemid in plex database for a " "video library update") else: - # Stop from manually marking as watched unwatched, with actual playback. + # Stop from manually marking as watched unwatched, with + # actual playback. if window('plex_skipWatched%s' % itemid) == "true": # property is set in player.py window('plex_skipWatched%s' % itemid, clear=True) @@ -171,11 +172,11 @@ class KodiMonitor(Monitor): # Try to get a Kodi ID # If PKC was used - native paths, not direct paths - plexid = window('plex_%s.itemid' % tryEncode(currentFile)) + plex_id = window('plex_%s.itemid' % tryEncode(currentFile)) # Get rid of the '' if the window property was not set - plexid = None if not plexid else plexid + plex_id = None if not plex_id else plex_id kodiid = None - if plexid is None: + if plex_id is None: log.debug('Did not get Plex id from window properties') try: kodiid = data['item']['id'] @@ -183,30 +184,39 @@ class KodiMonitor(Monitor): log.debug('Did not get a Kodi id from Kodi, darn') # For direct paths, if we're not streaming something # When using Widgets, Kodi doesn't tell us shit so we need this hack - if (kodiid is None and plexid is None and typus != 'song' + if (kodiid is None and plex_id is None and typus != 'song' and not currentFile.startswith('http')): (kodiid, typus) = get_kodiid_from_filename(currentFile) if kodiid is None: return - if plexid is None: + if plex_id is None: # Get Plex' item id with plexdb.Get_Plex_DB() as plexcursor: plex_dbitem = plexcursor.getItem_byKodiId(kodiid, typus) try: - plexid = plex_dbitem[0] + plex_id = plex_dbitem[0] except TypeError: log.info("No Plex id returned for kodiid %s. Aborting playback" " report" % kodiid) return log.debug("Found Plex id %s for Kodi id %s for type %s" - % (plexid, kodiid, typus)) + % (plex_id, kodiid, typus)) + + # Switch subtitle tracks if applicable + subtitle = window('plex_%s.subtitle' % tryEncode(currentFile)) + if window(tryEncode('plex_%s.playmethod' % currentFile)) \ + == 'Transcode' and subtitle: + if window('plex_%s.subtitle' % currentFile) == 'None': + self.xbmcplayer.showSubtitles(False) + else: + self.xbmcplayer.setSubtitleStream(int(subtitle)) # Set some stuff if Kodi initiated playback if ((settings('useDirectPaths') == "1" and not typus == "song") or (typus == "song" and settings('enableMusic') == "true")): - if self.StartDirectPath(plexid, + if self.StartDirectPath(plex_id, typus, tryEncode(currentFile)) is False: log.error('Could not initiate monitoring; aborting') @@ -214,19 +224,19 @@ class KodiMonitor(Monitor): # Save currentFile for cleanup later and to be able to access refs window('plex_lastPlayedFiled', value=currentFile) - window('plex_currently_playing_itemid', value=plexid) - window("plex_%s.itemid" % tryEncode(currentFile), value=plexid) + window('plex_currently_playing_itemid', value=plex_id) + window("plex_%s.itemid" % tryEncode(currentFile), value=plex_id) log.info('Finish playback startup') - def StartDirectPath(self, plexid, type, currentFile): + def StartDirectPath(self, plex_id, type, currentFile): """ Set some additional stuff if playback was initiated by Kodi, not PKC """ - xml = self.doUtils('{server}/library/metadata/%s' % plexid) + xml = self.doUtils('{server}/library/metadata/%s' % plex_id) try: xml[0].attrib except: - log.error('Did not receive a valid XML for plexid %s.' % plexid) + log.error('Did not receive a valid XML for plex_id %s.' % plex_id) return False # Setup stuff, because playback was started by Kodi, not PKC api = API(xml[0]) diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py index fabbda83..56a45695 100644 --- a/resources/lib/playbackutils.py +++ b/resources/lib/playbackutils.py @@ -75,10 +75,7 @@ class PlaybackUtils(): playmethod = window('plex_%s.playmethod' % playurl) if playmethod == "Transcode": - window('plex_%s.playmethod' % playurl, clear=True) - playurl = tryEncode(playutils.audioSubsPref( - listitem, tryDecode(playurl))) - window('plex_%s.playmethod' % playurl, "Transcode") + playutils.audioSubsPref(listitem, tryDecode(playurl)) listitem.setPath(playurl) api.set_playback_win_props(playurl, listitem) result.listitem = listitem @@ -195,12 +192,7 @@ class PlaybackUtils(): # Would be using the direct path log.debug("Adding contextmenu item for direct paths") if window('plex_%s.playmethod' % playurl) == "Transcode": - window('plex_%s.playmethod' % playurl, - clear=True) - playurl = tryEncode(playutils.audioSubsPref( - listitem, tryDecode(playurl))) - window('plex_%s.playmethod' % playurl, - value="Transcode") + playutils.audioSubsPref(listitem, tryDecode(playurl)) api.CreateListItemFromPlexItem(listitem) api.set_playback_win_props(playurl, listitem) api.set_listitem_artwork(listitem) @@ -246,10 +238,7 @@ class PlaybackUtils(): # For transcoding only, ask for audio/subs pref if (window('plex_%s.playmethod' % playurl) == "Transcode" and not contextmenu_play): - window('plex_%s.playmethod' % playurl, clear=True) - playurl = tryEncode(playutils.audioSubsPref( - listitem, tryDecode(playurl))) - window('plex_%s.playmethod' % playurl, value="Transcode") + playutils.audioSubsPref(listitem, tryDecode(playurl)) listitem.setPath(playurl) api.set_playback_win_props(playurl, listitem) diff --git a/resources/lib/player.py b/resources/lib/player.py index bba39909..b5f2389f 100644 --- a/resources/lib/player.py +++ b/resources/lib/player.py @@ -396,7 +396,8 @@ class Player(xbmc.Player): '%s.type' % plex_item, '%s.runtime' % plex_item, '%s.playcount' % plex_item, - '%s.playlistPosition' % plex_item + '%s.playlistPosition' % plex_item, + '%s.subtitle' % plex_item, ) for item in cleanup: window(item, clear=True) diff --git a/resources/lib/playutils.py b/resources/lib/playutils.py index d047d269..046e5a4f 100644 --- a/resources/lib/playutils.py +++ b/resources/lib/playutils.py @@ -3,11 +3,9 @@ ############################################################################### import logging -from urllib import urlencode +from downloadutils import DownloadUtils -import xbmcgui - -from utils import window, settings, tryEncode, language as lang +from utils import window, settings, tryEncode, language as lang, dialog import variables as v import PlexAPI @@ -24,6 +22,7 @@ class PlayUtils(): self.item = item self.API = PlexAPI.API(item) + self.doUtils = DownloadUtils().downloadUrl self.userid = window('currUserId') self.server = window('pms_server') @@ -254,131 +253,150 @@ class PlayUtils(): return res[chosen] def audioSubsPref(self, listitem, url, part=None): - dialog = xbmcgui.Dialog() - # For transcoding only - # Present the list of audio to select from - audioStreamsList = [] - audioStreams = [] - # audioStreamsChannelsList = [] - subtitleStreamsList = [] - subtitleStreams = ['1 No subtitles'] - downloadableStreams = [] - # selectAudioIndex = "" - selectSubsIndex = "" - playurlprefs = {} + """ + For transcoding only - # Set part where we're at - self.API.setPartNumber(part) + Called at the very beginning of play; used to change audio and subtitle + 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 try: - mediastreams = self.item[0][part] - except (TypeError, KeyError, IndexError): - return url - - audioNum = 0 + mediastreams = self.item[self.API.mediastream][part] + except (TypeError, IndexError): + log.error('Could not get media %s, part %s' + % (self.API.mediastream, part)) + return + part_id = mediastreams.attrib['id'] + audio_streams_list = [] + audio_streams = [] + subtitle_streams_list = [] + # No subtitles as an option + subtitle_streams = [lang(39706)] + downloadable_streams = [] + download_subs = [] + # selectAudioIndex = "" + select_subs_index = "" + audio_numb = 0 # Remember 'no subtitles' - subNum = 1 - defaultSub = None + sub_num = 1 + default_sub = None + for stream in mediastreams: # Since Plex returns all possible tracks together, have to sort # them. index = stream.attrib.get('id') - type = stream.attrib.get('streamType') - + typus = stream.attrib.get('streamType') # Audio - if type == "2": + if typus == "2": codec = stream.attrib.get('codec') channelLayout = stream.attrib.get('audioChannelLayout', "") try: - track = "%s %s - %s %s" % (audioNum+1, + track = "%s %s - %s %s" % (audio_numb+1, stream.attrib['language'], codec, channelLayout) except: - track = "%s 'unknown' - %s %s" % (audioNum+1, - codec, - channelLayout) - audioStreamsList.append(index) - audioStreams.append(tryEncode(track)) - audioNum += 1 + track = "%s %s - %s %s" % (audio_numb+1, + lang(39707), # unknown + codec, + channelLayout) + audio_streams_list.append(index) + audio_streams.append(tryEncode(track)) + audio_numb += 1 # Subtitles - elif type == "3": + elif typus == "3": try: - track = "%s %s" % (subNum+1, stream.attrib['language']) - except: - track = "%s 'unknown' (%s)" % (subNum+1, - stream.attrib.get('codec')) + track = "%s %s" % (sub_num+1, stream.attrib['language']) + except KeyError: + track = "%s %s (%s)" % (sub_num+1, + lang(39707), # unknown + stream.attrib.get('codec')) default = stream.attrib.get('default') forced = stream.attrib.get('forced') downloadable = stream.attrib.get('key') if default: - track = "%s - Default" % track + track = "%s - %s" % (track, lang(39708)) # Default if forced: - track = "%s - Forced" % track + track = "%s - %s" % (track, lang(39709)) # Forced if downloadable: - downloadableStreams.append(index) + # We do know the language - temporarily download + if 'language' in stream.attrib: + 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( + "%s%s" % (self.server, stream.attrib['key'])) + downloadable_streams.append(index) + download_subs.append(tryEncode(path)) else: - track = "%s (burn-in)" % track + track = "%s (%s)" % (track, lang(39710)) # burn-in if stream.attrib.get('selected') == '1' and downloadable: # Only show subs without asking user if they can be # turned off - defaultSub = index + default_sub = index - subtitleStreamsList.append(index) - subtitleStreams.append(tryEncode(track)) - subNum += 1 + subtitle_streams_list.append(index) + subtitle_streams.append(tryEncode(track)) + sub_num += 1 - if audioNum > 1: - resp = dialog.select(lang(33013), audioStreams) + if audio_numb > 1: + resp = dialog('select', lang(33013), audio_streams) if resp > -1: - # User selected audio - playurlprefs['audioStreamID'] = audioStreamsList[resp] - else: - # User backed out of selection - let PMS decide - pass + # User selected some audio track + args = { + 'audioStreamID': audio_streams_list[resp], + 'allParts': 1 + } + self.doUtils('{server}/library/parts/%s' % part_id, + action_type='PUT', + parameters=args) + + if sub_num == 1: + # No subtitles + return + + select_subs_index = None + if (settings('pickPlexSubtitles') == 'true' and + default_sub is not None): + log.info('Using default Plex subtitle: %s' % default_sub) + select_subs_index = default_sub else: - # There's only one audiotrack. - playurlprefs['audioStreamID'] = audioStreamsList[0] - - selectSubsIndex = None - if subNum > 1: - if (settings('pickPlexSubtitles') == 'true' and - defaultSub is not None): - log.info('Using default Plex subtitle: %s' % defaultSub) - selectSubsIndex = defaultSub + resp = dialog('select', lang(33014), subtitle_streams) + if resp > 0: + select_subs_index = subtitle_streams_list[resp-1] else: - resp = dialog.select(lang(33014), subtitleStreams) - if resp > 0: - selectSubsIndex = subtitleStreamsList[resp-1] - else: - # User selected no subtitles or backed out of dialog - playurlprefs["skipSubtitles"] = 1 - if selectSubsIndex is not None: - # Load subtitles in the listitem if downloadable - if selectSubsIndex in downloadableStreams: - sub_url = self.API.addPlexHeadersToUrl( - "%s/library/streams/%s" - % (self.server, selectSubsIndex)) - log.info("Downloadable sub: %s: %s" - % (selectSubsIndex, sub_url)) - listitem.setSubtitles([tryEncode(sub_url)]) - # Don't additionally burn in subtitles - playurlprefs["skipSubtitles"] = 1 - else: - log.info('Need to burn in subtitle %s' % selectSubsIndex) - playurlprefs["subtitleStreamID"] = selectSubsIndex - playurlprefs["subtitleSize"] = settings('subtitleSize') + # User selected no subtitles or backed out of dialog + select_subs_index = '' - url += '&' + urlencode(playurlprefs) + log.debug('Adding external subtitles: %s' % download_subs) + # Enable Kodi to switch autonomously to downloadable subtitles + if download_subs: + listitem.setSubtitles(download_subs) - # Get number of channels for selected audio track - # audioChannels = audioStreamsChannelsList.get(selectAudioIndex, 0) - # if audioChannels > 2: - # playurlprefs += "&AudioBitrate=384000" - # else: - # playurlprefs += "&AudioBitrate=192000" + 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') - return url + args = { + 'subtitleStreamID': select_subs_index, + 'allParts': 1 + } + self.doUtils('{server}/library/parts/%s' % part_id, + action_type='PUT', + parameters=args)