def process_plex_node(self, url, viewOffset, directplay=False, node=True): """ Called for Plex directories or redirect for playback (e.g. trailers, clips, watchlater) """ log.info('process_plex_node called with url: %s, viewOffset: %s' % (url, viewOffset)) # Plex redirect, e.g. watch later. Need to get actual URLs if url.startswith('http') or url.startswith('{server}'): xml = DownloadUtils().downloadUrl(url) else: xml = DownloadUtils().downloadUrl('{server}%s' % url) try: xml[0].attrib except: log.error('Could not download PMS metadata') return if viewOffset != '0': try: viewOffset = int(v.PLEX_TO_KODI_TIMEFACTOR * float(viewOffset)) except: pass else: window('plex_customplaylist.seektime', value=str(viewOffset)) log.info('Set resume point to %s' % str(viewOffset)) api = API(xml[0]) typus = v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()] if node is True: plex_id = None kodi_id = 'plexnode' else: plex_id = api.getRatingKey() kodi_id = None with plexdb.Get_Plex_DB() as plex_db: plexdb_item = plex_db.getItem_byId(plex_id) try: kodi_id = plexdb_item[0] except TypeError: log.info('Couldnt find item %s in Kodi db' % api.getRatingKey()) playqueue = self.playqueue.get_playqueue_from_type(typus) with lock: result = PlaybackUtils(xml, playqueue).play( plex_id, kodi_id=kodi_id, plex_lib_UUID=xml.attrib.get('librarySectionUUID')) if directplay: if result.listitem: listitem = convert_PKC_to_listitem(result.listitem) Player().play(listitem.getfilename(), listitem) return Playback_Successful() else: return result
def play_all(self): """ Play all items contained in the xml passed in. Called by Plex Companion """ log.info("Playbackutils play_all called") window('plex_playbackProps', value="true") self.currentPosition = 0 for item in self.xml: api = API(item) successful = True if api.getType() == v.PLEX_TYPE_CLIP: self.add_trailer(item) else: with Get_Plex_DB() as plex_db: db_item = plex_db.getItem_byId(api.getRatingKey()) if db_item is not None: successful = add_item_to_kodi_playlist( self.playqueue, self.currentPosition, kodi_id=db_item[0], kodi_type=db_item[4]) if successful is True: self.currentPosition += 1 if len(item[0]) > 1: self.add_part(item, api, db_item[0], db_item[4]) else: # Item not in Kodi DB self.add_trailer(item) if successful is True: self.playqueue.items[self.currentPosition - 1].ID = item.get( '%sItemID' % self.playqueue.kind)
def playlist_item_from_xml(playlist, xml_video_element, kodi_id=None, kodi_type=None): """ Returns a playlist element for the playqueue using the Plex xml xml_video_element: etree xml piece 1 level underneath <MediaContainer> """ item = Playlist_Item() api = API(xml_video_element) item.plex_id = api.getRatingKey() item.plex_type = api.getType() try: item.id = xml_video_element.attrib['%sItemID' % playlist.kind] except KeyError: pass item.guid = xml_video_element.attrib.get('guid') if item.guid is not None: item.guid = escape_html(item.guid) if kodi_id is not None: item.kodi_id = kodi_id item.kodi_type = kodi_type elif item.plex_id is not None: with plexdb.Get_Plex_DB() as plex_db: db_element = plex_db.getItem_byId(item.plex_id) try: item.kodi_id, item.kodi_type = int(db_element[0]), db_element[4] except TypeError: pass item.xml = xml_video_element LOG.debug('Created new playlist item from xml: %s', item) return item
def play_all(self): """ Play all items contained in the xml passed in. Called by Plex Companion """ log.info("Playbackutils play_all called") window('plex_playbackProps', value="true") self.currentPosition = 0 for item in self.xml: api = API(item) successful = True if api.getType() == v.PLEX_TYPE_CLIP: self.add_trailer(item) else: with Get_Plex_DB() as plex_db: db_item = plex_db.getItem_byId(api.getRatingKey()) if db_item is not None: successful = add_item_to_kodi_playlist( self.playqueue, self.currentPosition, kodi_id=db_item[0], kodi_type=db_item[4]) if successful is True: self.currentPosition += 1 if len(item[0]) > 1: self.add_part(item, api, db_item[0], db_item[4]) else: # Item not in Kodi DB self.add_trailer(item) if successful is True: self.playqueue.items[self.currentPosition - 1].ID = item.get( '%sItemID' % self.playqueue.kind)
def __build_item(xml_element): api = API(xml_element) listitem = api.CreateListItemFromPlexItem() resume = api.getResume() if resume: listitem.setProperty('resumetime', str(resume)) if (api.getKey().startswith('/system/services') or api.getKey().startswith('http')): params = { 'mode': 'plex_node', 'key': xml_element.attrib.get('key'), 'offset': xml_element.attrib.get('viewOffset', '0'), } url = "plugin://%s?%s" % (v.ADDON_ID, urlencode(params)) elif api.getType() == v.PLEX_TYPE_PHOTO: url = api.get_picture_path() else: params = { 'mode': 'play', 'plex_id': api.getRatingKey(), 'plex_type': api.getType(), } url = "plugin://%s?%s" % (v.ADDON_ID, urlencode(params)) xbmcplugin.addDirectoryItem(handle=HANDLE, url=url, listitem=listitem)
def _prep_playlist_stack(xml): stack = [] for item in xml: api = API(item) if (state.CONTEXT_MENU_PLAY is False and api.getType() != v.PLEX_TYPE_CLIP): # If user chose to play via PMS or force transcode, do not # use the item path stored in the Kodi DB with plexdb.Get_Plex_DB() as plex_db: plex_dbitem = plex_db.getItem_byId(api.getRatingKey()) kodi_id = plex_dbitem[0] if plex_dbitem else None kodi_type = plex_dbitem[4] if plex_dbitem else None else: # We will never store clips (trailers) in the Kodi DB kodi_id = None kodi_type = None for part, _ in enumerate(item[0]): api.setPartNumber(part) if kodi_id is None: # Need to redirect again to PKC to conclude playback params = { 'mode': 'play', 'plex_id': api.getRatingKey(), 'plex_type': api.getType() } path = ('plugin://plugin.video.plexkodiconnect?%s' % urlencode(params)) listitem = api.CreateListItemFromPlexItem() listitem.setPath(tryEncode(path)) else: # Will add directly via the Kodi DB path = None listitem = None stack.append({ 'kodi_id': kodi_id, 'kodi_type': kodi_type, 'file': path, 'xml_video_element': item, 'listitem': listitem, 'part': part, 'playcount': api.getViewCount(), 'offset': api.getResume(), 'id': api.getItemId() }) return stack
def init_playqueue_from_plex_children(self, plex_id): """ Init a new playqueue e.g. from an album. Alexa does this """ xml = GetAllPlexChildren(plex_id) try: xml[0].attrib except (TypeError, IndexError, AttributeError): log.error('Could not download the PMS xml for %s' % plex_id) return playqueue = self.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[xml[0].attrib['type']]) playqueue.clear() for i, child in enumerate(xml): api = API(child) PL.add_item_to_playlist(playqueue, i, plex_id=api.getRatingKey()) log.debug('Firing up Kodi player') Player().play(playqueue.kodi_pl, None, False, 0)
def playlist_item_from_xml(playlist, xml_video_element): """ Returns a playlist element for the playqueue using the Plex xml """ item = Playlist_Item() api = API(xml_video_element) item.plex_id = api.getRatingKey() item.ID = xml_video_element.attrib['%sItemID' % playlist.kind] item.guid = xml_video_element.attrib.get('guid') if item.plex_id: with plexdb.Get_Plex_DB() as plex_db: db_element = plex_db.getItem_byId(item.plex_id) try: item.kodi_id, item.kodi_type = int(db_element[0]), db_element[4] except TypeError: pass log.debug('Created new playlist item from xml: %s' % item) return item
def init_playqueue_from_plex_children(self, plex_id): """ Init a new playqueue e.g. from an album. Alexa does this """ xml = GetAllPlexChildren(plex_id) try: xml[0].attrib except (TypeError, IndexError, AttributeError): log.error('Could not download the PMS xml for %s' % plex_id) return playqueue = self.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[xml[0].attrib['type']]) playqueue.clear() for i, child in enumerate(xml): api = API(child) PL.add_item_to_playlist(playqueue, i, plex_id=api.getRatingKey()) log.debug('Firing up Kodi player') Player().play(playqueue.kodi_pl, None, False, 0)
def playlist_item_from_xml(playlist, xml_video_element): """ Returns a playlist element for the playqueue using the Plex xml """ item = Playlist_Item() api = API(xml_video_element) item.plex_id = api.getRatingKey() item.ID = xml_video_element.attrib['%sItemID' % playlist.kind] item.guid = xml_video_element.attrib.get('guid') if item.plex_id: with plexdb.Get_Plex_DB() as plex_db: db_element = plex_db.getItem_byId(item.plex_id) try: item.kodi_id, item.kodi_type = int(db_element[0]), db_element[4] except TypeError: pass log.debug('Created new playlist item from xml: %s' % item) return item
def add_trailer(self, item): # Playurl needs to point back so we can get metadata! path = "plugin://plugin.video.plexkodiconnect/movies/" params = {'mode': "play", 'dbid': 'plextrailer'} introAPI = API(item) listitem = introAPI.CreateListItemFromPlexItem() params['id'] = introAPI.getRatingKey() params['filename'] = introAPI.getKey() introPlayurl = path + '?' + urlencode(params) introAPI.set_listitem_artwork(listitem) # Overwrite the Plex url listitem.setPath(introPlayurl) log.info("Adding Plex trailer: %s" % introPlayurl) add_listitem_to_Kodi_playlist(self.playqueue, self.currentPosition, listitem, introPlayurl, xml_video_element=item) self.currentPosition += 1
def add_trailer(self, item): # Playurl needs to point back so we can get metadata! path = "plugin://plugin.video.plexkodiconnect/movies/" params = { 'mode': "play", 'dbid': 'plextrailer' } introAPI = API(item) listitem = introAPI.CreateListItemFromPlexItem() params['id'] = introAPI.getRatingKey() params['filename'] = introAPI.getKey() introPlayurl = path + '?' + urlencode(params) introAPI.set_listitem_artwork(listitem) # Overwrite the Plex url listitem.setPath(introPlayurl) log.info("Adding Plex trailer: %s" % introPlayurl) add_listitem_to_Kodi_playlist( self.playqueue, self.currentPosition, listitem, introPlayurl, xml_video_element=item) self.currentPosition += 1
def _process_alexa(self, data): xml = GetPlexMetadata(data['key']) try: xml[0].attrib except (AttributeError, IndexError, TypeError): LOG.error('Could not download Plex metadata for: %s', data) return api = API(xml[0]) if api.getType() == v.PLEX_TYPE_ALBUM: LOG.debug('Plex music album detected') PQ.init_playqueue_from_plex_children( api.getRatingKey(), transient_token=data.get('token')) else: state.PLEX_TRANSIENT_TOKEN = data.get('token') params = { 'mode': 'plex_node', 'key': '{server}%s' % data.get('key'), 'view_offset': data.get('offset'), 'play_directly': 'true', 'node': 'false' } executebuiltin('RunPlugin(plugin://%s?%s)' % (v.ADDON_ID, urlencode(params)))
def __build_item(xml_element): api = API(xml_element) listitem = api.CreateListItemFromPlexItem() if (api.getKey().startswith('/system/services') or api.getKey().startswith('http')): params = { 'mode': 'plex_node', 'key': xml_element.attrib.get('key'), 'view_offset': xml_element.attrib.get('viewOffset', '0'), } url = "plugin://%s?%s" % (v.ADDON_ID, urlencode(params)) elif api.getType() == v.PLEX_TYPE_PHOTO: url = api.get_picture_path() else: params = { 'mode': 'play', 'filename': api.getKey(), 'id': api.getRatingKey(), 'dbid': listitem.getProperty('dbid') } url = "plugin://%s?%s" % (v.ADDON_ID, urlencode(params)) xbmcplugin.addDirectoryItem(handle=HANDLE, url=url, listitem=listitem)
def getOnDeck(viewid, mediatype, tagname, limit): """ Retrieves Plex On Deck items, currently only for TV shows Input: viewid: Plex id of the library section, e.g. '1' mediatype: Kodi mediatype, e.g. 'tvshows', 'movies', 'homevideos', 'photos' tagname: Name of the Plex library, e.g. "My Movies" limit: Max. number of items to retrieve, e.g. 50 """ xbmcplugin.setContent(HANDLE, 'episodes') appendShowTitle = settings('OnDeckTvAppendShow') == 'true' appendSxxExx = settings('OnDeckTvAppendSeason') == 'true' directpaths = settings('useDirectPaths') == 'true' if settings('OnDeckTVextended') == 'false': # Chances are that this view is used on Kodi startup # Wait till we've connected to a PMS. At most 30s counter = 0 while window('plex_authenticated') != 'true': counter += 1 if counter >= 300: log.error('Aborting On Deck view, we were not authenticated ' 'for the PMS') return xbmcplugin.endOfDirectory(HANDLE, False) sleep(100) xml = downloadutils.DownloadUtils().downloadUrl( '{server}/library/sections/%s/onDeck' % viewid) if xml in (None, 401): log.error('Could not download PMS xml for view %s' % viewid) return xbmcplugin.endOfDirectory(HANDLE) limitcounter = 0 for item in xml: api = API(item) listitem = api.CreateListItemFromPlexItem( appendShowTitle=appendShowTitle, appendSxxExx=appendSxxExx) if directpaths: url = api.getFilePath() else: params = { 'mode': "play", 'id': api.getRatingKey(), 'dbid': listitem.getProperty('dbid') } url = "plugin://plugin.video.plexkodiconnect/tvshows/?%s" \ % urlencode(params) xbmcplugin.addDirectoryItem( handle=HANDLE, url=url, listitem=listitem) limitcounter += 1 if limitcounter == limit: break return xbmcplugin.endOfDirectory( handle=HANDLE, cacheToDisc=settings('enableTextureCache') == 'true') # if the addon is called with nextup parameter, # we return the nextepisodes list of the given tagname # First we get a list of all the TV shows - filtered by tag params = { 'sort': {'order': "descending", 'method': "lastplayed"}, 'filter': { 'and': [ {'operator': "true", 'field': "inprogress", 'value': ""}, {'operator': "is", 'field': "tag", 'value': "%s" % tagname} ]} } result = JSONRPC('VideoLibrary.GetTVShows').execute(params) # If we found any, find the oldest unwatched show for each one. try: items = result['result'][mediatype] except (KeyError, TypeError): # Now items retrieved - empty directory xbmcplugin.endOfDirectory(handle=HANDLE) return params = { 'sort': {'method': "episode"}, 'limits': {"end": 1}, 'properties': [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "streamdetails", "firstaired", "runtime", "cast", "writer", "dateadded", "lastplayed" ], } if settings('ignoreSpecialsNextEpisodes') == "true": params['filter'] = { 'and': [ {'operator': "lessthan", 'field': "playcount", 'value': "1"}, {'operator': "greaterthan", 'field': "season", 'value': "0"} ] } else: params['filter'] = { 'or': [ {'operator': "lessthan", 'field': "playcount", 'value': "1"}, {'operator': "true", 'field': "inprogress", 'value': ""} ] } # Are there any episodes still in progress/not yet finished watching?!? # Then we should show this episode, NOT the "next up" inprog_params = { 'sort': {'method': "episode"}, 'filter': {'operator': "true", 'field': "inprogress", 'value': ""}, 'properties': params['properties'] } count = 0 for item in items: inprog_params['tvshowid'] = item['tvshowid'] result = JSONRPC('VideoLibrary.GetEpisodes').execute(inprog_params) try: episodes = result['result']['episodes'] except (KeyError, TypeError): # No, there are no episodes not yet finished. Get "next up" params['tvshowid'] = item['tvshowid'] result = JSONRPC('VideoLibrary.GetEpisodes').execute(params) try: episodes = result['result']['episodes'] except (KeyError, TypeError): # Also no episodes currently coming up continue for episode in episodes: # There will always be only 1 episode ('limit=1') li = createListItem(episode, appendShowTitle=appendShowTitle, appendSxxExx=appendSxxExx) xbmcplugin.addDirectoryItem( handle=HANDLE, url=episode['file'], listitem=li, isFolder=False) count += 1 if count >= limit: break xbmcplugin.endOfDirectory(handle=HANDLE)
def getOnDeck(viewid, mediatype, tagname, limit): """ Retrieves Plex On Deck items, currently only for TV shows Input: viewid: Plex id of the library section, e.g. '1' mediatype: Kodi mediatype, e.g. 'tvshows', 'movies', 'homevideos', 'photos' tagname: Name of the Plex library, e.g. "My Movies" limit: Max. number of items to retrieve, e.g. 50 """ xbmcplugin.setContent(HANDLE, 'episodes') appendShowTitle = settings('OnDeckTvAppendShow') == 'true' appendSxxExx = settings('OnDeckTvAppendSeason') == 'true' directpaths = settings('useDirectPaths') == 'true' if settings('OnDeckTVextended') == 'false': # Chances are that this view is used on Kodi startup # Wait till we've connected to a PMS. At most 30s counter = 0 while window('plex_authenticated') != 'true': counter += 1 if counter >= 300: log.error('Aborting On Deck view, we were not authenticated ' 'for the PMS') return xbmcplugin.endOfDirectory(HANDLE, False) sleep(100) xml = downloadutils.DownloadUtils().downloadUrl( '{server}/library/sections/%s/onDeck' % viewid) if xml in (None, 401): log.error('Could not download PMS xml for view %s' % viewid) return xbmcplugin.endOfDirectory(HANDLE) limitcounter = 0 for item in xml: api = API(item) listitem = api.CreateListItemFromPlexItem( appendShowTitle=appendShowTitle, appendSxxExx=appendSxxExx) if directpaths: url = api.getFilePath() else: params = { 'mode': "play", 'plex_id': api.getRatingKey(), 'plex_type': api.getType() } url = "plugin://plugin.video.plexkodiconnect/tvshows/?%s" \ % urlencode(params) xbmcplugin.addDirectoryItem( handle=HANDLE, url=url, listitem=listitem) limitcounter += 1 if limitcounter == limit: break return xbmcplugin.endOfDirectory( handle=HANDLE, cacheToDisc=settings('enableTextureCache') == 'true') # if the addon is called with nextup parameter, # we return the nextepisodes list of the given tagname # First we get a list of all the TV shows - filtered by tag params = { 'sort': {'order': "descending", 'method': "lastplayed"}, 'filter': { 'and': [ {'operator': "true", 'field': "inprogress", 'value': ""}, {'operator': "is", 'field': "tag", 'value': "%s" % tagname} ]} } items = js.get_tv_shows(params) if not items: # Now items retrieved - empty directory xbmcplugin.endOfDirectory(handle=HANDLE) return params = { 'sort': {'method': "episode"}, 'limits': {"end": 1}, 'properties': [ "title", "playcount", "season", "episode", "showtitle", "plot", "file", "rating", "resume", "tvshowid", "art", "streamdetails", "firstaired", "runtime", "cast", "writer", "dateadded", "lastplayed" ], } if settings('ignoreSpecialsNextEpisodes') == "true": params['filter'] = { 'and': [ {'operator': "lessthan", 'field': "playcount", 'value': "1"}, {'operator': "greaterthan", 'field': "season", 'value': "0"} ] } else: params['filter'] = { 'or': [ {'operator': "lessthan", 'field': "playcount", 'value': "1"}, {'operator': "true", 'field': "inprogress", 'value': ""} ] } # Are there any episodes still in progress/not yet finished watching?!? # Then we should show this episode, NOT the "next up" inprog_params = { 'sort': {'method': "episode"}, 'filter': {'operator': "true", 'field': "inprogress", 'value': ""}, 'properties': params['properties'] } count = 0 for item in items: inprog_params['tvshowid'] = item['tvshowid'] episodes = js.get_episodes(inprog_params) if not episodes: # No, there are no episodes not yet finished. Get "next up" params['tvshowid'] = item['tvshowid'] episodes = js.get_episodes(params) if not episodes: # Also no episodes currently coming up continue for episode in episodes: # There will always be only 1 episode ('limit=1') listitem = createListItem(episode, appendShowTitle=appendShowTitle, appendSxxExx=appendSxxExx) xbmcplugin.addDirectoryItem(handle=HANDLE, url=episode['file'], listitem=listitem, isFolder=False) count += 1 if count >= limit: break xbmcplugin.endOfDirectory(handle=HANDLE)
def processTasks(self, task): """ Processes tasks picked up e.g. by Companion listener, e.g. {'action': 'playlist', 'data': {'address': 'xyz.plex.direct', 'commandID': '7', 'containerKey': '/playQueues/6669?own=1&repeat=0&window=200', 'key': '/library/metadata/220493', 'machineIdentifier': 'xyz', 'offset': '0', 'port': '32400', 'protocol': 'https', 'token': 'transient-cd2527d1-0484-48e0-a5f7-f5caa7d591bd', 'type': 'video'}} """ log.debug('Processing: %s' % task) data = task['data'] # Get the token of the user flinging media (might be different one) token = data.get('token') if task['action'] == 'alexa': # e.g. Alexa xml = GetPlexMetadata(data['key']) try: xml[0].attrib except (AttributeError, IndexError, TypeError): log.error('Could not download Plex metadata') return api = API(xml[0]) if api.getType() == v.PLEX_TYPE_ALBUM: log.debug('Plex music album detected') queue = self.mgr.playqueue.init_playqueue_from_plex_children( api.getRatingKey()) queue.plex_transient_token = token else: state.PLEX_TRANSIENT_TOKEN = token params = { 'mode': 'plex_node', 'key': '{server}%s' % data.get('key'), 'view_offset': data.get('offset'), 'play_directly': 'true', 'node': 'false' } executebuiltin('RunPlugin(plugin://%s?%s)' % (v.ADDON_ID, urlencode(params))) elif (task['action'] == 'playlist' and data.get('address') == 'node.plexapp.com'): # E.g. watch later initiated by Companion state.PLEX_TRANSIENT_TOKEN = token params = { 'mode': 'plex_node', 'key': '{server}%s' % data.get('key'), 'view_offset': data.get('offset'), 'play_directly': 'true' } executebuiltin('RunPlugin(plugin://%s?%s)' % (v.ADDON_ID, urlencode(params))) elif task['action'] == 'playlist': # Get the playqueue ID try: typus, ID, query = ParseContainerKey(data['containerKey']) except Exception as e: log.error('Exception while processing: %s' % e) import traceback log.error("Traceback:\n%s" % traceback.format_exc()) return try: playqueue = self.mgr.playqueue.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[data['type']]) except KeyError: # E.g. Plex web does not supply the media type # Still need to figure out the type (video vs. music vs. pix) xml = GetPlexMetadata(data['key']) try: xml[0].attrib except (AttributeError, IndexError, TypeError): log.error('Could not download Plex metadata') return api = API(xml[0]) playqueue = self.mgr.playqueue.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()]) self.mgr.playqueue.update_playqueue_from_PMS( playqueue, ID, repeat=query.get('repeat'), offset=data.get('offset')) playqueue.plex_transient_token = token elif task['action'] == 'refreshPlayQueue': # example data: {'playQueueID': '8475', 'commandID': '11'} xml = get_pms_playqueue(data['playQueueID']) if xml is None: return if len(xml) == 0: log.debug('Empty playqueue received - clearing playqueue') plex_type = get_plextype_from_xml(xml) if plex_type is None: return playqueue = self.mgr.playqueue.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[plex_type]) playqueue.clear() return playqueue = self.mgr.playqueue.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[xml[0].attrib['type']]) self.mgr.playqueue.update_playqueue_from_PMS( playqueue, data['playQueueID'])