From ce29f5a60e24f50157ab2b5783cf10cafb94e749 Mon Sep 17 00:00:00 2001 From: Croneter Date: Fri, 4 May 2018 15:11:18 +0200 Subject: [PATCH 1/2] Fix ValueError for third party add-ons calling PKC --- resources/lib/playback_starter.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/resources/lib/playback_starter.py b/resources/lib/playback_starter.py index f3723b46..86433bcd 100644 --- a/resources/lib/playback_starter.py +++ b/resources/lib/playback_starter.py @@ -24,7 +24,13 @@ class PlaybackStarter(Thread): """ @staticmethod def _triage(item): - _, params = item.split('?', 1) + try: + _, params = item.split('?', 1) + except ValueError: + # E.g. other add-ons scanning for Extras folder + LOG.debug('Detected 3rd party add-on call - ignoring') + pickle_me(Playback_Successful()) + return params = dict(parse_qsl(params)) mode = params.get('mode') resolve = False if params.get('handle') == '-1' else True From 63f7d5615dbecba4ebb9af00d58d96bde2f435e3 Mon Sep 17 00:00:00 2001 From: Croneter Date: Fri, 4 May 2018 19:03:27 +0200 Subject: [PATCH 2/2] Finally support for Extras! --- default.py | 3 +++ resources/lib/PlexAPI.py | 39 ++++++++++++++++++++++++++++ resources/lib/context_entry.py | 46 +++++++++++++++++++++++++++------- resources/lib/entrypoint.py | 20 +++++++++++++++ 4 files changed, 99 insertions(+), 9 deletions(-) diff --git a/default.py b/default.py index 79b07fb3..1aaee286 100644 --- a/default.py +++ b/default.py @@ -99,6 +99,9 @@ class Main(): elif mode == 'channels': entrypoint.channels() + elif mode == 'extras': + entrypoint.extras(plex_id=params.get('plex_id')) + elif mode == 'settings': executebuiltin('Addon.OpenSettings(%s)' % v.ADDON_ID) diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index 03b9767e..6b0a36fa 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -99,6 +99,35 @@ class API(object): """ return self.item.get('ratingKey') + def path(self, force_first_media=True): + """ + Returns a "fully qualified path": add-on paths or direct paths + depending on the current settings. Will NOT valide the playurl + Returns unicode or None if something went wrong. + """ + filename = self.file_path(force_first_media=force_first_media) + if not state.DIRECT_PATHS or self.plex_type() == v.PLEX_TYPE_CLIP: + if filename and '/' in filename: + filename = filename.rsplit('/', 1) + elif filename: + filename = filename.rsplit('\\', 1) + try: + filename = filename[1] + except (TypeError, IndexError): + filename = None + # Set plugin path and media flags using real filename + path = ('plugin://%s/?plex_id=%s&plex_type=%s&mode=play&filename=%s' + % (v.ADDON_TYPE[self.plex_type()], + self.plex_id(), + self.plex_type(), + filename)) + else: + # Direct paths is set the Kodi way + path = self.validate_playurl(filename, + self.plex_type(), + omit_check=True) + return path + def path_and_plex_id(self): """ Returns the Plex key such as '/library/metadata/246922' or None @@ -655,6 +684,16 @@ class API(object): answ['bitDepth'] = None return answ + def extras(self): + """ + Returns a list of XML etree elements for each extra, e.g. a trailer. + """ + answ = [] + for extras in self.item.iterfind('Extras'): + for extra in extras: + answ.append(extra) + return answ + def trailer_id(self): """ Returns the ratingKey (plex_id) of the trailer or None diff --git a/resources/lib/context_entry.py b/resources/lib/context_entry.py index 56d2f2b1..fa5fd414 100644 --- a/resources/lib/context_entry.py +++ b/resources/lib/context_entry.py @@ -2,13 +2,16 @@ ############################################################################### from logging import getLogger -from xbmc import getInfoLabel, sleep, executebuiltin from xbmcaddon import Addon +import xbmc +import xbmcplugin +import xbmcgui import plexdb_functions as plexdb from utils import window, settings, dialog, language as lang from dialogs import context -from PlexFunctions import delete_item_from_pms +import PlexFunctions as PF +from PlexAPI import API import playqueue as PQ import variables as v import state @@ -25,7 +28,8 @@ OPTIONS = { # 'RemoveFav': lang(30406), # 'RateSong': lang(30407), 'Transcode': lang(30412), - 'PMS_Play': lang(30415) # Use PMS to start playback + 'PMS_Play': lang(30415), # Use PMS to start playback + 'Extras': lang(30235) } ############################################################################### @@ -53,17 +57,24 @@ class ContextMenu(object): self.plex_id, self.plex_type) if not self.plex_id: return + xml = PF.GetPlexMetadata(self.plex_id) + try: + xml[0].attrib + except (TypeError, IndexError, KeyError): + self.api = None + else: + self.api = API(xml[0]) if self._select_menu(): self._action_menu() if self._selected_option in (OPTIONS['Delete'], OPTIONS['Refresh']): LOG.info("refreshing container") - sleep(500) - executebuiltin('Container.Refresh') + xbmc.sleep(500) + xbmc.executebuiltin('Container.Refresh') @staticmethod def _get_plex_id(kodi_id, kodi_type): - plex_id = getInfoLabel('ListItem.Property(plexid)') or None + plex_id = xbmc.getInfoLabel('ListItem.Property(plexid)') or None if not plex_id and kodi_id and kodi_type: with plexdb.Get_Plex_DB() as plexcursor: item = plexcursor.getItem_byKodiId(kodi_id, kodi_type) @@ -79,6 +90,8 @@ class ContextMenu(object): """ options = [] # if user uses direct paths, give option to initiate playback via PMS + if self.api and self.api.extras(): + options.append(OPTIONS['Extras']) if state.DIRECT_PATHS and self.kodi_type in v.KODI_VIDEOTYPES: options.append(OPTIONS['PMS_Play']) if self.kodi_type in v.KODI_VIDEOTYPES: @@ -122,6 +135,8 @@ class ContextMenu(object): self._PMS_play() elif selected == OPTIONS['PMS_Play']: self._PMS_play() + elif selected == OPTIONS['Extras']: + self._extras() # elif selected == OPTIONS['Refresh']: # self.emby.refreshItem(self.item_id) # elif selected == OPTIONS['AddFav']: @@ -131,7 +146,8 @@ class ContextMenu(object): # elif selected == OPTIONS['RateSong']: # self._rate_song() elif selected == OPTIONS['Addon']: - executebuiltin('Addon.OpenSettings(plugin.video.plexkodiconnect)') + xbmc.executebuiltin( + 'Addon.OpenSettings(plugin.video.plexkodiconnect)') elif selected == OPTIONS['Delete']: self._delete_item() @@ -146,7 +162,7 @@ class ContextMenu(object): delete = False if delete: LOG.info("Deleting Plex item with id %s", self.plex_id) - if delete_item_from_pms(self.plex_id) is False: + if PF.delete_item_from_pms(self.plex_id) is False: dialog("ok", heading="{plex}", line1=lang(30414)) def _PMS_play(self): @@ -161,4 +177,16 @@ class ContextMenu(object): % (v.ADDON_TYPE[self.plex_type], self.plex_id, self.plex_type)) - executebuiltin('RunPlugin(%s)' % handle) + xbmc.executebuiltin('RunPlugin(%s)' % handle) + + def _extras(self): + """ + Displays a list of elements for all the extras of the Plex element + """ + handle = ('plugin://plugin.video.plexkodiconnect?mode=extras&plex_id=%s' + % self.plex_id) + if xbmcgui.getCurrentWindowId() == 10025: + # Video Window + xbmc.executebuiltin('Container.Update(\"%s\")' % handle) + else: + xbmc.executebuiltin('ActivateWindow(videos, \"%s\")' % handle) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 1bd5318e..1f47c87b 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -841,6 +841,26 @@ def __build_item(xml_element): listitem=listitem) +def extras(plex_id): + """ + Lists all extras for plex_id + """ + xbmcplugin.setContent(HANDLE, 'movies') + xml = GetPlexMetadata(plex_id) + try: + xml[0].attrib + except (TypeError, IndexError, KeyError): + xbmcplugin.endOfDirectory(HANDLE) + return + for item in API(xml[0]).extras(): + api = API(item) + listitem = api.create_listitem() + xbmcplugin.addDirectoryItem(handle=HANDLE, + url=api.path(), + listitem=listitem) + xbmcplugin.endOfDirectory(HANDLE) + + def enterPMS(): """ Opens dialogs for the user the plug in the PMS details