Ejemplo n.º 1
0
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')
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
    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')
Ejemplo n.º 4
0
 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)
Ejemplo n.º 5
0
 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'))
Ejemplo n.º 6
0
 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
Ejemplo n.º 7
0
 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'])
Ejemplo n.º 9
0
    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')
Ejemplo n.º 10
0
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()