def get_plextype_from_xml(xml): """ Needed if PMS returns an empty playqueue. Will get the Plex type from the empty playlist playQueueSourceURI. Feed with (empty) etree xml returns None if unsuccessful """ try: plex_id = REGEX.findall(xml.attrib['playQueueSourceURI'])[0] except IndexError: LOG.error('Could not get plex_id from xml: %s', xml.attrib) return new_xml = GetPlexMetadata(plex_id) try: new_xml[0].attrib except (TypeError, IndexError, AttributeError): LOG.error('Could not get plex metadata for plex id %s', plex_id) return return new_xml[0].attrib.get('type')
def add_item_to_kodi_playlist(playlist, pos, kodi_id=None, kodi_type=None, file=None, xml_video_element=None): """ Adds an item to the KODI playlist only. WILL ALSO UPDATE OUR PLAYLISTS Returns the playlist item that was just added or raises PlaylistError file: str! """ LOG.debug( 'Adding new item kodi_id: %s, kodi_type: %s, file: %s to Kodi ' 'only at position %s for %s', kodi_id, kodi_type, file, pos, playlist) params = {'playlistid': playlist.playlistid, 'position': pos} if kodi_id is not None: params['item'] = {'%sid' % kodi_type: int(kodi_id)} else: params['item'] = {'file': file} reply = js.playlist_insert(params) if reply.get('error') is not None: raise PlaylistError('Could not add item to playlist. Kodi reply. %s', reply) if xml_video_element is not None: item = playlist_item_from_xml(playlist, xml_video_element) item.kodi_id = kodi_id item.kodi_type = kodi_type item.file = file elif kodi_id is not None: item = playlist_item_from_kodi({ 'id': kodi_id, 'type': kodi_type, 'file': file }) if item.plex_id is not None: xml = GetPlexMetadata(item.plex_id) item.xml = xml[-1] playlist.items.insert(pos, item) return item
def __init__(self): self.kodi_id = xbmc.getInfoLabel('ListItem.DBID').decode('utf-8') self.item_type = self._get_item_type() self.item_id = self._get_item_id(self.kodi_id, self.item_type) log.info("Found item_id: %s item_type: %s" % (self.item_id, self.item_type)) if not self.item_id: return self.item = GetPlexMetadata(self.item_id) self.api = PlexAPI.API(self.item) if self._select_menu(): self._action_menu() if self._selected_option in (OPTIONS['Delete'], OPTIONS['Refresh']): log.info("refreshing container") xbmc.sleep(500) xbmc.executebuiltin('Container.Refresh')
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.plex_type() == v.PLEX_TYPE_ALBUM: LOG.debug('Plex music album detected') PQ.init_playqueue_from_plex_children( api.plex_id(), transient_token=data.get('token')) elif data['containerKey'].startswith('/playQueues/'): _, container_key, _ = ParseContainerKey(data['containerKey']) xml = DownloadChunks('{server}/playQueues/%s?' % container_key) if xml is None: # "Play error" dialog('notification', lang(29999), lang(30128), icon='{error}') return playqueue = PQ.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()]) playqueue.clear() get_playlist_details_from_xml(playqueue, xml) playqueue.plex_transient_token = data.get('token') if data.get('offset') != '0': offset = float(data['offset']) / 1000.0 else: offset = None play_xml(playqueue, xml, offset) else: state.PLEX_TRANSIENT_TOKEN = data.get('token') if data.get('offset') != '0': state.RESUMABLE = True state.RESUME_PLAYBACK = True playback_triage(api.plex_id(), api.plex_type(), resolve=False)
def _process_playlist(self, data): # Get the playqueue ID _, container_key, query = ParseContainerKey(data['containerKey']) try: playqueue = PQ.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 = PQ.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()]) PQ.update_playqueue_from_PMS(playqueue, playqueue_id=container_key, repeat=query.get('repeat'), offset=data.get('offset'), transient_token=data.get('token'))
def process_play(self, plex_id, kodi_id=None): """ Processes Kodi playback init for ONE item """ log.info("Process_play called with plex_id %s, kodi_id %s" % (plex_id, kodi_id)) if window('plex_authenticated') != "true": log.error('Not yet authenticated for PMS, abort starting playback') # Todo: Warn user with dialog return xml = GetPlexMetadata(plex_id) try: xml[0].attrib except (TypeError, AttributeError): log.error('Could not get a PMS xml for plex id %s' % plex_id) return api = API(xml[0]) if api.getType() == v.PLEX_TYPE_PHOTO: # Photo result = Playback_Successful() listitem = PKC_ListItem() listitem = api.CreateListItemFromPlexItem(listitem) api.AddStreamInfo(listitem) api.set_listitem_artwork(listitem) result.listitem = listitem else: # Video and Music playqueue = self.playqueue.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.getType()]) with lock: result = PlaybackUtils(xml, playqueue).play( plex_id, kodi_id, xml.attrib.get('librarySectionUUID')) log.info('Done process_play, playqueues: %s' % self.playqueue.playqueues) return result
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 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'])
def __run(self): """ Do the work """ log.debug('Starting get metadata thread') # cache local variables because it's faster queue = self.queue out_queue = self.out_queue threadStopped = self.threadStopped while threadStopped() is False: # grabs Plex item from queue try: item = queue.get(block=False) # Empty queue except Empty: sleep(20) continue # Download Metadata xml = GetPlexMetadata(item['itemId']) if xml is None: # Did not receive a valid XML - skip that item for now log.error("Could not get metadata for %s. Skipping that item " "for now" % item['itemId']) # Increase BOTH counters - since metadata won't be processed with sync_info.LOCK: sync_info.GET_METADATA_COUNT += 1 sync_info.PROCESS_METADATA_COUNT += 1 queue.task_done() continue elif xml == 401: log.error('HTTP 401 returned by PMS. Too much strain? ' 'Cancelling sync for now') window('plex_scancrashed', value='401') # Kill remaining items in queue (for main thread to cont.) queue.task_done() break item['XML'] = xml if item.get('get_children') is True: children_xml = GetAllPlexChildren(item['itemId']) try: children_xml[0].attrib except (TypeError, IndexError, AttributeError): log.error('Could not get children for Plex id %s' % item['itemId']) else: item['children'] = [] for child in children_xml: child_xml = GetPlexMetadata(child.attrib['ratingKey']) try: child_xml[0].attrib except (TypeError, IndexError, AttributeError): log.error('Could not get child for Plex id %s' % child.attrib['ratingKey']) else: item['children'].append(child_xml[0]) # place item into out queue out_queue.put(item) # Keep track of where we are at with sync_info.LOCK: sync_info.GET_METADATA_COUNT += 1 # signals to queue job is done queue.task_done() # Empty queue in case PKC was shut down (main thread hangs otherwise) self.terminate_now() log.debug('Get metadata thread terminated')
def _playback_init(plex_id, plex_type, playqueue, pos): """ Playback setup if Kodi starts playing an item for the first time. """ LOG.info('Initializing PKC playback') xml = GetPlexMetadata(plex_id) try: xml[0].attrib except (IndexError, TypeError, AttributeError): LOG.error('Could not get a PMS xml for plex id %s', plex_id) # "Play error" dialog('notification', lang(29999), lang(30128), icon='{error}') _ensure_resolve(abort=True) return if playqueue.kodi_pl.size() > 1: # Special case - we already got a filled Kodi playqueue try: _init_existing_kodi_playlist(playqueue) except PL.PlaylistError: LOG.error('Aborting playback_init for longer Kodi playlist') _ensure_resolve(abort=True) return # Now we need to use setResolvedUrl for the item at position pos _conclude_playback(playqueue, pos) return # "Usual" case - consider trailers and parts and build both Kodi and Plex # playqueues # Fail the item we're trying to play now so we can restart the player _ensure_resolve() api = API(xml[0]) trailers = False if (plex_type == v.PLEX_TYPE_MOVIE and not api.resume_point() and settings('enableCinema') == "true"): if settings('askCinema') == "true": # "Play trailers?" trailers = dialog('yesno', lang(29999), lang(33016)) trailers = True if trailers else False else: trailers = True LOG.debug('Playing trailers: %s', trailers) playqueue.clear() if plex_type != v.PLEX_TYPE_CLIP: # Post to the PMS to create a playqueue - in any case due to Companion xml = init_plex_playqueue(plex_id, xml.attrib.get('librarySectionUUID'), mediatype=plex_type, trailers=trailers) if xml is None: LOG.error('Could not get a playqueue xml for plex id %s, UUID %s', plex_id, xml.attrib.get('librarySectionUUID')) # "Play error" dialog('notification', lang(29999), lang(30128), icon='{error}') _ensure_resolve(abort=True) return # Should already be empty, but just in case PL.get_playlist_details_from_xml(playqueue, xml) stack = _prep_playlist_stack(xml) # Sleep a bit to let setResolvedUrl do its thing - bit ugly sleep(200) _process_stack(playqueue, stack) # Always resume if playback initiated via PMS and there IS a resume # point offset = api.resume_point() * 1000 if state.CONTEXT_MENU_PLAY else None # Reset some playback variables state.CONTEXT_MENU_PLAY = False state.FORCE_TRANSCODE = False # Do NOT set offset, because Kodi player will return here to resolveURL # New thread to release this one sooner (e.g. harddisk spinning up) thread = Thread(target=threaded_playback, args=(playqueue.kodi_pl, pos, offset)) thread.setDaemon(True) LOG.info( 'Done initializing playback, starting Kodi player at pos %s and ' 'resume point %s', pos, offset) # By design, PKC will start Kodi playback using Player().play(). Kodi # caches paths like our plugin://pkc. If we use Player().play() between # 2 consecutive startups of exactly the same Kodi library item, Kodi's # cache will have been flushed for some reason. Hence the 2nd call for # plugin://pkc will be lost; Kodi will try to startup playback for an empty # path: log entry is "CGUIWindowVideoBase::OnPlayMedia <missing path>" thread.start()