def singleNode(self, indexnumber, tagname, mediatype, itemtype): tagname = try_encode(tagname) cleantagname = try_decode(normalize_nodes(tagname)) nodepath = try_decode( xbmc.translatePath("special://profile/library/video/")) nodeXML = "%splex_%s.xml" % (nodepath, cleantagname) path = "library://video/plex_%s.xml" % cleantagname if v.KODIVERSION >= 17: # Krypton windowpath = "ActivateWindow(Videos,%s,return)" % path else: windowpath = "ActivateWindow(Video,%s,return)" % path # Create the video node directory if not exists_dir(nodepath): # We need to copy over the default items copytree( src=try_decode( xbmc.translatePath("special://xbmc/system/library/video")), dst=try_decode( xbmc.translatePath("special://profile/library/video"))) labels = { 'Favorite movies': 30180, 'Favorite tvshows': 30181, 'channels': 30173 } label = lang(labels[tagname]) embynode = "Plex.nodes.%s" % indexnumber window('%s.title' % embynode, value=label) window('%s.path' % embynode, value=windowpath) window('%s.content' % embynode, value=path) window('%s.type' % embynode, value=itemtype) if exists(try_encode(nodeXML)): # Don't recreate xml if already exists return if itemtype == "channels": root = self.commonRoot(order=1, label=label, tagname=tagname, roottype=2) etree.SubElement( root, 'path' ).text = "plugin://plugin.video.plexkodiconnect/?id=0&mode=channels" else: root = self.commonRoot(order=1, label=label, tagname=tagname) etree.SubElement(root, 'order', { 'direction': "ascending" }).text = "sorttitle" etree.SubElement(root, 'content').text = mediatype try: indent(root) except: pass etree.ElementTree(root).write(nodeXML, encoding="UTF-8")
def getVideoFiles(plexId, params): """ GET VIDEO EXTRAS FOR LISTITEM returns the video files for the item as plugin listing, can be used for browsing the actual files or videoextras etc. """ if plexId is None: filename = params.get('filename') if filename is not None: filename = filename[0] import re regex = re.compile(r'''library/metadata/(\d+)''') filename = regex.findall(filename) try: plexId = filename[0] except IndexError: pass if plexId is None: log.info('No Plex ID found, abort getting Extras') return xbmcplugin.endOfDirectory(HANDLE) item = GetPlexMetadata(plexId) try: path = item[0][0][0].attrib['file'] except: log.error('Could not get file path for item %s' % plexId) return xbmcplugin.endOfDirectory(HANDLE) # Assign network protocol if path.startswith('\\\\'): path = path.replace('\\\\', 'smb://') path = path.replace('\\', '/') # Plex returns Windows paths as e.g. 'c:\slfkjelf\slfje\file.mkv' elif '\\' in path: path = path.replace('\\', '\\\\') # Directory only, get rid of filename path = path.replace(basename(path), '') if exists_dir(path): for root, dirs, files in walk(path): for directory in dirs: item_path = try_encode(join(root, directory)) li = ListItem(item_path, path=item_path) xbmcplugin.addDirectoryItem(handle=HANDLE, url=item_path, listitem=li, isFolder=True) for file in files: item_path = try_encode(join(root, file)) li = ListItem(item_path, path=item_path) xbmcplugin.addDirectoryItem(handle=HANDLE, url=file, listitem=li) break else: log.error('Kodi cannot access folder %s' % path) xbmcplugin.endOfDirectory(HANDLE)
def getExtraFanArt(plexid, plexPath): """ Get extrafanart for listitem will be called by skinhelper script to get the extrafanart for tvshows we get the plexid just from the path """ log.debug('Called with plexid: %s, plexPath: %s' % (plexid, plexPath)) if not plexid: if "plugin.video.plexkodiconnect" in plexPath: plexid = plexPath.split("/")[-2] if not plexid: log.error('Could not get a plexid, aborting') return xbmcplugin.endOfDirectory(HANDLE) # We need to store the images locally for this to work # because of the caching system in xbmc fanartDir = try_decode(translatePath( "special://thumbnails/plex/%s/" % plexid)) if not exists_dir(fanartDir): # Download the images to the cache directory makedirs(fanartDir) xml = GetPlexMetadata(plexid) if xml is None: log.error('Could not download metadata for %s' % plexid) return xbmcplugin.endOfDirectory(HANDLE) api = API(xml[0]) backdrops = api.artwork()['Backdrop'] for count, backdrop in enumerate(backdrops): # Same ordering as in artwork fanartFile = try_encode(join(fanartDir, "fanart%.3d.jpg" % count)) li = ListItem("%.3d" % count, path=fanartFile) xbmcplugin.addDirectoryItem( handle=HANDLE, url=fanartFile, listitem=li) copyfile(backdrop, try_decode(fanartFile)) else: log.info("Found cached backdrop.") # Use existing cached images for root, dirs, files in walk(fanartDir): for file in files: fanartFile = try_encode(join(root, file)) li = ListItem(file, path=fanartFile) xbmcplugin.addDirectoryItem(handle=HANDLE, url=fanartFile, listitem=li) xbmcplugin.endOfDirectory(HANDLE)
def transcode_image_path(key, AuthToken, path, width, height): """ Transcode Image support parameters: key AuthToken path - source path of current XML: path[srcXML] width height result: final path to image file """ # external address - can we get a transcoding request for external images? if key.startswith('http://') or key.startswith('https://'): path = key elif key.startswith('/'): # internal full path. path = 'http://127.0.0.1:32400' + key else: # internal path, add-on path = 'http://127.0.0.1:32400' + path + '/' + key path = try_encode(path) # This is bogus (note the extra path component) but ATV is stupid when it # comes to caching images, it doesn't use querystrings. Fortunately PMS is # lenient... transcode_path = ('/photo/:/transcode/%sx%s/%s' % (width, height, quote_plus(path))) args = { 'width': width, 'height': height, 'url': path } if AuthToken: args['X-Plex-Token'] = AuthToken return transcode_path + '?' + urlencode(args)
def _auto_pick_pms(self): """ Will try to pick PMS based on machineIdentifier saved in file settings but only once Returns server or None if unsuccessful """ https_updated = False checked_plex_tv = False server = None while True: if https_updated is False: serverlist = PF.discover_pms(self.plex_token) for item in serverlist: if item.get('machineIdentifier') == self.serverid: server = item if server is None: name = settings('plex_servername') LOG.warn( 'The PMS you have used before with a unique ' 'machineIdentifier of %s and name %s is ' 'offline', self.serverid, name) return chk = self._check_pms_connectivity(server) if chk == 504 and https_updated is False: # switch HTTPS to HTTP or vice-versa if server['scheme'] == 'https': server['scheme'] = 'http' else: server['scheme'] = 'https' https_updated = True continue if chk == 401: LOG.warn('Not yet authorized for Plex server %s', server['name']) if self.check_plex_tv_sign_in() is True: if checked_plex_tv is False: # Try again checked_plex_tv = True https_updated = False continue else: LOG.warn('Not authorized even though we are signed ' ' in to plex.tv correctly') dialog( 'ok', lang(29999), '%s %s' % (lang(39214), try_encode(server['name']))) return else: return # Problems connecting elif chk >= 400 or chk is False: LOG.warn('Problems connecting to server %s. chk is %s', server['name'], chk) return LOG.info('We found a server to automatically connect to: %s', server['name']) return server
def set_json(jsonmethod, params): '''method to set info in the kodi json api''' kodi_json = {} kodi_json["jsonrpc"] = "2.0" kodi_json["method"] = jsonmethod kodi_json["params"] = params kodi_json["id"] = 1 json_response = xbmc.executeJSONRPC(try_encode(json.dumps(kodi_json))) return json.loads(json_response.decode('utf-8', 'replace'))
def get_json(jsonmethod, sort=None, filters=None, fields=None, limits=None, returntype=None, optparam=None, filtertype=None): '''method to get details from the kodi json api''' kodi_json = {} kodi_json["jsonrpc"] = "2.0" kodi_json["method"] = jsonmethod kodi_json["params"] = {} if optparam: if isinstance(optparam, list): for param in optparam: kodi_json["params"][param[0]] = param[1] else: kodi_json["params"][optparam[0]] = optparam[1] kodi_json["id"] = 1 if sort: kodi_json["params"]["sort"] = sort if filters: if not filtertype: filtertype = "and" if len(filters) > 1: kodi_json["params"]["filter"] = {filtertype: filters} else: kodi_json["params"]["filter"] = filters[0] if fields: kodi_json["params"]["properties"] = fields if limits: kodi_json["params"]["limits"] = { "start": limits[0], "end": limits[1] } json_response = xbmc.executeJSONRPC(try_encode(json.dumps(kodi_json))) json_object = json.loads(json_response.decode('utf-8', 'replace')) # set the default returntype to prevent errors if "details" in jsonmethod.lower(): result = {} else: result = [] if 'result' in json_object: if returntype and returntype in json_object['result']: # returntype specified, return immediately result = json_object['result'][returntype] else: # no returntype specified, we'll have to look for it for key, value in json_object['result'].iteritems(): if not key == "limits" and (isinstance(value, list) or isinstance(value, dict)): result = value else: log_msg(json_response) log_msg(kodi_json) return result
def set_json(jsonmethod, params): """method to set info in the kodi json api""" kodi_json = {} kodi_json["jsonrpc"] = "2.0" kodi_json["method"] = jsonmethod kodi_json["params"] = params kodi_json["id"] = 1 json_response = xbmc.executeJSONRPC(try_encode(json.dumps(kodi_json))) if sys.version_info.major == 3: return json.loads(json_response) else: return json.loads(json_response.decode('utf-8', 'replace'))
def _conclude_playback(playqueue, pos): """ ONLY if actually being played (e.g. at 5th position of a playqueue). Decide on direct play, direct stream, transcoding path to direct paths: file itself PMS URL Web URL audiostream (e.g. let user choose) subtitle stream (e.g. let user choose) Init Kodi Playback (depending on situation): start playback return PKC listitem attached to result """ LOG.info('Concluding playback for playqueue position %s', pos) result = Playback_Successful() listitem = PKC_ListItem() item = playqueue.items[pos] if item.xml is not None: # Got a Plex element api = API(item.xml) api.set_part_number(item.part) api.create_listitem(listitem) playutils = PlayUtils(api, item) playurl = playutils.getPlayUrl() else: playurl = item.file listitem.setPath(try_encode(playurl)) if item.playmethod == 'DirectStream': listitem.setSubtitles(api.cache_external_subs()) elif item.playmethod == 'Transcode': playutils.audio_subtitle_prefs(listitem) if state.RESUME_PLAYBACK is True: state.RESUME_PLAYBACK = False if (item.offset is None and item.plex_type not in (v.PLEX_TYPE_SONG, v.PLEX_TYPE_CLIP)): with plexdb.Get_Plex_DB() as plex_db: plex_dbitem = plex_db.getItem_byId(item.plex_id) file_id = plex_dbitem[1] if plex_dbitem else None with kodidb.GetKodiDB('video') as kodi_db: item.offset = kodi_db.get_resume(file_id) LOG.info('Resuming playback at %s', item.offset) listitem.setProperty('StartOffset', str(item.offset)) listitem.setProperty('resumetime', str(item.offset)) # Reset the resumable flag result.listitem = listitem pickle_me(result) LOG.info('Done concluding playback')
def create_frame(data, opcode): """ create frame to send text, binary and other data. data: data to send. This is string value(byte array). if opcode is OPCODE_TEXT and this value is uniocde, data value is conveted into unicode string, automatically. opcode: operation code. please see OPCODE_XXX. """ if opcode == ABNF.OPCODE_TEXT and isinstance(data, unicode): data = utils.try_encode(data) # mask must be set if send data from client return ABNF(1, 0, 0, 0, opcode, 1, data)
def __repr__(self): """ Print the playlist, e.g. to log. Returns utf-8 encoded string """ answ = u'{\'%s\': {\'id\': %s, ' % (self.__class__.__name__, self.id) # For some reason, can't use dir directly for key in self.__dict__: if key in ('id', 'items', 'kodi_pl'): continue if isinstance(getattr(self, key), str): answ += '\'%s\': \'%s\', ' % (key, try_decode(getattr(self, key))) else: # e.g. int answ += '\'%s\': %s, ' % (key, unicode(getattr(self, key))) return try_encode(answ + '\'items\': %s}}') % self.items
def _prep_playlist_stack(xml): stack = [] for item in xml: api = API(item) if (state.CONTEXT_MENU_PLAY is False and api.plex_type() != 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.plex_id()) 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. # Also set kodi_id to None for playback via PMS, so that we're # using add-on paths. kodi_id = None kodi_type = None for part, _ in enumerate(item[0]): api.set_part_number(part) if kodi_id is None: # Need to redirect again to PKC to conclude playback params = { 'mode': 'play', 'plex_id': api.plex_id(), 'plex_type': api.plex_type() } path = ('plugin://%s/?%s' % (v.ADDON_TYPE[api.plex_type()], urlencode(params))) listitem = api.create_listitem() listitem.setPath(try_encode(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.viewcount(), 'offset': api.resume_point(), 'id': api.item_id() }) return stack
def __repr__(self): """ Print the playlist, e.g. to log """ answ = '{\'%s\': {' % (self.__class__.__name__) # For some reason, can't use dir directly answ += '\'id\': %s, ' % self.id for key in self.__dict__: if key in ('id', 'items', 'kodi_pl'): continue if isinstance(getattr(self, key), (str, unicode)): answ += '\'%s\': \'%s\', ' % (key, try_encode(getattr(self, key))) else: # e.g. int answ += '\'%s\': %s, ' % (key, str(getattr(self, key))) return answ + '\'items\': %s}}' % self.items
def __repr__(self): """ Print the playlist item, e.g. to log. Returns utf-8 encoded string """ answ = (u'{\'%s\': {\'id\': \'%s\', \'plex_id\': \'%s\', ' % (self.__class__.__name__, self.id, self.plex_id)) for key in self.__dict__: if key in ('id', 'plex_id', 'xml'): continue if isinstance(getattr(self, key), str): answ += '\'%s\': \'%s\', ' % (key, try_decode(getattr(self, key))) else: # e.g. int answ += '\'%s\': %s, ' % (key, unicode(getattr(self, key))) if self.xml is None: answ += '\'xml\': None}}' else: answ += '\'xml\': \'%s\'}}' % self.xml.tag return try_encode(answ)
def __repr__(self): """ Print the playlist item, e.g. to log """ answ = '{\'%s\': {' % (self.__class__.__name__) answ += '\'id\': \'%s\', ' % self.id answ += '\'plex_id\': \'%s\', ' % self.plex_id for key in self.__dict__: if key in ('id', 'plex_id', 'xml'): continue if isinstance(getattr(self, key), (str, unicode)): answ += '\'%s\': \'%s\', ' % (key, try_encode(getattr(self, key))) else: # e.g. int answ += '\'%s\': %s, ' % (key, str(getattr(self, key))) if self.xml is None: answ += '\'xml\': None}}' else: answ += '\'xml\': \'%s\'}}' % self.xml.tag return answ
def process_indirect(key, offset, resolve=True): """ Called e.g. for Plex "Play later" - Plex items where we need to fetch an additional xml for the actual playurl. In the PMS metadata, indirect="1" is set. Will release default.py with setResolvedUrl Set resolve to False if playback should be kicked off directly, not via setResolvedUrl """ LOG.info('process_indirect called with key: %s, offset: %s', key, offset) global RESOLVE RESOLVE = resolve result = Playback_Successful() if key.startswith('http') or key.startswith('{server}'): xml = DU().downloadUrl(key) elif key.startswith('/system/services'): xml = DU().downloadUrl('http://node.plexapp.com:32400%s' % key) else: xml = DU().downloadUrl('{server}%s' % key) try: xml[0].attrib except (TypeError, IndexError, AttributeError): LOG.error('Could not download PMS metadata') _ensure_resolve(abort=True) return if offset != '0': offset = int(v.PLEX_TO_KODI_TIMEFACTOR * float(offset)) # Todo: implement offset api = API(xml[0]) listitem = PKC_ListItem() api.create_listitem(listitem) playqueue = PQ.get_playqueue_from_type( v.KODI_PLAYLIST_TYPE_FROM_PLEX_TYPE[api.plex_type()]) playqueue.clear() item = PL.Playlist_Item() item.xml = xml[0] item.offset = int(offset) item.plex_type = v.PLEX_TYPE_CLIP item.playmethod = 'DirectStream' # Need to get yet another xml to get the final playback url xml = DU().downloadUrl('http://node.plexapp.com:32400%s' % xml[0][0][0].attrib['key']) try: xml[0].attrib except (TypeError, IndexError, AttributeError): LOG.error('Could not download last xml for playurl') _ensure_resolve(abort=True) return playurl = xml[0].attrib['key'] item.file = playurl listitem.setPath(try_encode(playurl)) playqueue.items.append(item) if resolve is True: result.listitem = listitem pickle_me(result) else: thread = Thread(target=Player().play, args={ 'item': try_encode(playurl), 'listitem': listitem }) thread.setDaemon(True) LOG.info('Done initializing PKC playback, starting Kodi player') thread.start()
def cacheTexture(self, url): # Cache a single image url to the texture cache if url and self.enableTextureCache: self.queue.put(double_urlencode(try_encode(url)))
def choose_home_user(token): """ Let's user choose from a list of Plex home users. Will switch to that user accordingly. Returns a dict: { 'username': Unicode 'userid': '' Plex ID of the user 'token': '' User's token 'protected': True if PIN is needed, else False } Will return False if something went wrong (wrong PIN, no connection) """ # Get list of Plex home users users = list_home_users(token) if not users: LOG.error("User download failed.") return False userlist = [] userlist_coded = [] for user in users: username = user['title'] userlist.append(username) # To take care of non-ASCII usernames userlist_coded.append(try_encode(username)) usernumber = len(userlist) username = '' usertoken = '' trials = 0 while trials < 3: if usernumber > 1: # Select user user_select = dialog('select', lang(29999) + lang(39306), userlist_coded) if user_select == -1: LOG.info("No user selected.") settings('username', value='') executebuiltin('Addon.OpenSettings(%s)' % v.ADDON_ID) return False # Only 1 user received, choose that one else: user_select = 0 selected_user = userlist[user_select] LOG.info("Selected user: %s", selected_user) user = users[user_select] # Ask for PIN, if protected: pin = None if user['protected'] == '1': LOG.debug('Asking for users PIN') pin = dialog('input', lang(39307) + selected_user, '', type='{numeric}', option='{hide}') # User chose to cancel # Plex bug: don't call url for protected user with empty PIN if not pin: trials += 1 continue # Switch to this Plex Home user, if applicable result = switch_home_user(user['id'], pin, token, settings('plex_machineIdentifier')) if result: # Successfully retrieved username: break out of while loop username = result['username'] usertoken = result['usertoken'] break # Couldn't get user auth else: trials += 1 # Could not login user, please try again if not dialog('yesno', heading='{plex}', line1=lang(39308) + selected_user, line2=lang(39309)): # User chose to cancel break if not username: LOG.error('Failed signing in a user to plex.tv') executebuiltin('Addon.OpenSettings(%s)' % v.ADDON_ID) return False return { 'username': username, 'userid': user['id'], 'protected': True if user['protected'] == '1' else False, 'token': usertoken }
def viewNode(self, indexnumber, tagname, mediatype, viewtype, viewid, delete=False): # Plex: reassign mediatype due to Kodi inner workings # How many items do we get at most? limit = state.FETCH_PMS_ITEM_NUMBER mediatypes = { 'movie': 'movies', 'show': 'tvshows', 'photo': 'photos', 'homevideo': 'homevideos', 'musicvideos': 'musicvideos' } mediatype = mediatypes[mediatype] if viewtype == "mixed": dirname = "%s-%s" % (viewid, mediatype) else: dirname = viewid # Returns strings path = try_decode( xbmc.translatePath("special://profile/library/video/")) nodepath = try_decode( xbmc.translatePath("special://profile/library/video/Plex-%s/" % dirname)) if delete: if exists_dir(nodepath): from shutil import rmtree rmtree(nodepath) log.info("Sucessfully removed videonode: %s." % tagname) return # Verify the video directory if not exists_dir(path): copytree( src=try_decode( xbmc.translatePath("special://xbmc/system/library/video")), dst=try_decode( xbmc.translatePath("special://profile/library/video"))) # Create the node directory if mediatype != "photos": if not exists_dir(nodepath): # folder does not exist yet log.debug('Creating folder %s' % nodepath) makedirs(nodepath) # Create index entry nodeXML = "%sindex.xml" % nodepath # Set windows property path = "library://video/Plex-%s/" % dirname for i in range(1, indexnumber): # Verify to make sure we don't create duplicates if window('Plex.nodes.%s.index' % i) == path: return if mediatype == "photos": path = "plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s&id=%s" % ( viewid, viewid) window('Plex.nodes.%s.index' % indexnumber, value=path) # Root if not mediatype == "photos": if viewtype == "mixed": specialtag = "%s-%s" % (tagname, mediatype) root = self.commonRoot(order=0, label=specialtag, tagname=tagname, roottype=0) else: root = self.commonRoot(order=0, label=tagname, tagname=tagname, roottype=0) try: indent(root) except: pass etree.ElementTree(root).write(nodeXML, encoding="UTF-8") nodetypes = { '1': "all", '2': "recent", '3': "recentepisodes", '4': "inprogress", '5': "inprogressepisodes", '6': "unwatched", '7': "nextepisodes", '8': "sets", '9': "genres", '10': "random", '11': "recommended", '12': "ondeck", '13': 'browsefiles' } mediatypes = { # label according to nodetype per mediatype 'movies': { '1': tagname, '2': 30174, # '4': 30177, # '6': 30189, '8': 39501, '9': 135, '10': 30227, '11': 30230, '12': 39500, '13': 39702 }, 'tvshows': { '1': tagname, # '2': 30170, '3': 30174, # '4': 30171, # '5': 30178, # '7': 30179, '9': 135, '10': 30227, # '11': 30230, '12': 39500, '13': 39702 }, 'homevideos': { '1': tagname, '2': 30251, '11': 30253, '13': 39702 }, 'photos': { '1': tagname, '2': 30252, '8': 30255, '11': 30254, '13': 39702 }, 'musicvideos': { '1': tagname, '2': 30256, '4': 30257, '6': 30258, '13': 39702 } } # Key: nodetypes, value: sort order in Kodi sortorder = { '1': '3', # "all", '2': '2', # "recent", '3': '2', # "recentepisodes", # '4': # "inprogress", # '5': # "inprogressepisodes", # '6': # "unwatched", # '7': # "nextepisodes", '8': '7', # "sets", '9': '6', # "genres", '10': '8', # "random", '11': '5', # "recommended", '12': '1', # "ondeck" '13': '9' # browse by folder } nodes = mediatypes[mediatype] for node in nodes: nodetype = nodetypes[node] nodeXML = "%s%s_%s.xml" % (nodepath, viewid, nodetype) # Get label stringid = nodes[node] if node != "1": label = lang(stringid) if not label: label = xbmc.getLocalizedString(stringid) else: label = stringid # Set window properties if (mediatype == "homevideos" or mediatype == "photos") and nodetype == "all": # Custom query path = ( "plugin://plugin.video.plexkodiconnect/?id=%s&mode=browseplex&type=%s" % (viewid, mediatype)) elif (mediatype == "homevideos" or mediatype == "photos"): # Custom query path = ( "plugin://plugin.video.plexkodiconnect/?id=%s&mode=browseplex&type=%s&folderid=%s" % (viewid, mediatype, nodetype)) elif nodetype == "nextepisodes": # Custom query path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=nextup&limit=%s" % ( tagname, limit) # elif v.KODIVERSION == 14 and nodetype == "recentepisodes": elif nodetype == "recentepisodes": # Custom query path = ( "plugin://plugin.video.plexkodiconnect/?id=%s&mode=recentepisodes&type=%s&tagname=%s&limit=%s" % (viewid, mediatype, tagname, limit)) elif v.KODIVERSION == 14 and nodetype == "inprogressepisodes": # Custom query path = "plugin://plugin.video.plexkodiconnect/?id=%s&mode=inprogressepisodes&limit=%s" % ( tagname, limit) elif nodetype == 'ondeck': # PLEX custom query if mediatype == "tvshows": path = ( "plugin://plugin.video.plexkodiconnect/?id=%s&mode=ondeck&type=%s&tagname=%s&limit=%s" % (viewid, mediatype, tagname, limit)) elif mediatype == "movies": # Reset nodetype; we got the label nodetype = 'inprogress' elif nodetype == 'browsefiles': path = 'plugin://plugin.video.plexkodiconnect?mode=browseplex&key=/library/sections/%s/folder' % viewid else: path = "library://video/Plex-%s/%s_%s.xml" % (dirname, viewid, nodetype) if mediatype == "photos": windowpath = "ActivateWindow(Pictures,%s,return)" % path else: if v.KODIVERSION >= 17: # Krypton windowpath = "ActivateWindow(Videos,%s,return)" % path else: windowpath = "ActivateWindow(Video,%s,return)" % path if nodetype == "all": if viewtype == "mixed": templabel = "%s-%s" % (tagname, mediatype) else: templabel = label embynode = "Plex.nodes.%s" % indexnumber window('%s.title' % embynode, value=templabel) window('%s.path' % embynode, value=windowpath) window('%s.content' % embynode, value=path) window('%s.type' % embynode, value=mediatype) else: embynode = "Plex.nodes.%s.%s" % (indexnumber, nodetype) window('%s.title' % embynode, value=label) window('%s.path' % embynode, value=windowpath) window('%s.content' % embynode, value=path) if mediatype == "photos": # For photos, we do not create a node in videos but we do want the window props # to be created. # To do: add our photos nodes to kodi picture sources somehow continue if exists(try_encode(nodeXML)): # Don't recreate xml if already exists continue # Create the root if (nodetype in ("nextepisodes", "ondeck", 'recentepisodes', 'browsefiles') or mediatype == "homevideos"): # Folder type with plugin path root = self.commonRoot(order=sortorder[node], label=label, tagname=tagname, roottype=2) etree.SubElement(root, 'path').text = path etree.SubElement(root, 'content').text = "episodes" else: root = self.commonRoot(order=sortorder[node], label=label, tagname=tagname) if nodetype in ('recentepisodes', 'inprogressepisodes'): etree.SubElement(root, 'content').text = "episodes" else: etree.SubElement(root, 'content').text = mediatype # Elements per nodetype if nodetype == "all": etree.SubElement(root, 'order', { 'direction': "ascending" }).text = "sorttitle" elif nodetype == "recent": etree.SubElement(root, 'order', { 'direction': "descending" }).text = "dateadded" etree.SubElement(root, 'limit').text = limit if settings('MovieShowWatched') == 'false': rule = etree.SubElement(root, 'rule', { 'field': "playcount", 'operator': "is" }) etree.SubElement(rule, 'value').text = "0" elif nodetype == "inprogress": etree.SubElement(root, 'rule', { 'field': "inprogress", 'operator': "true" }) etree.SubElement(root, 'limit').text = limit etree.SubElement(root, 'order', { 'direction': 'descending' }).text = 'lastplayed' elif nodetype == "genres": etree.SubElement(root, 'order', { 'direction': "ascending" }).text = "sorttitle" etree.SubElement(root, 'group').text = "genres" elif nodetype == "unwatched": etree.SubElement(root, 'order', { 'direction': "ascending" }).text = "sorttitle" rule = etree.SubElement(root, "rule", { 'field': "playcount", 'operator': "is" }) etree.SubElement(rule, 'value').text = "0" elif nodetype == "sets": etree.SubElement(root, 'order', { 'direction': "ascending" }).text = "sorttitle" etree.SubElement(root, 'group').text = "tags" elif nodetype == "random": etree.SubElement(root, 'order', { 'direction': "ascending" }).text = "random" etree.SubElement(root, 'limit').text = limit elif nodetype == "recommended": etree.SubElement(root, 'order', { 'direction': "descending" }).text = "rating" etree.SubElement(root, 'limit').text = limit rule = etree.SubElement(root, 'rule', { 'field': "playcount", 'operator': "is" }) etree.SubElement(rule, 'value').text = "0" rule2 = etree.SubElement(root, 'rule', attrib={ 'field': "rating", 'operator': "greaterthan" }) etree.SubElement(rule2, 'value').text = "7" elif nodetype == "recentepisodes": # Kodi Isengard, Jarvis etree.SubElement(root, 'order', { 'direction': "descending" }).text = "dateadded" etree.SubElement(root, 'limit').text = limit rule = etree.SubElement(root, 'rule', { 'field': "playcount", 'operator': "is" }) etree.SubElement(rule, 'value').text = "0" elif nodetype == "inprogressepisodes": # Kodi Isengard, Jarvis etree.SubElement(root, 'limit').text = limit rule = etree.SubElement(root, 'rule', attrib={ 'field': "inprogress", 'operator': "true" }) try: indent(root) except: pass etree.ElementTree(root).write(nodeXML, encoding="UTF-8")
def audio_subtitle_prefs(self, listitem): """ For transcoding only 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.mediastream_number() try: mediastreams = self.api.plex_media_streams() except (TypeError, IndexError): LOG.error('Could not get media %s, part %s', self.api.mediastream, self.api.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' 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') typus = stream.attrib.get('streamType') # Audio if typus == "2": codec = stream.attrib.get('codec') channellayout = stream.attrib.get('audioChannelLayout', "") try: track = "%s %s - %s %s" % (audio_numb+1, stream.attrib['language'], codec, channellayout) except KeyError: track = "%s %s - %s %s" % (audio_numb+1, lang(39707), # unknown codec, channellayout) audio_streams_list.append(index) audio_streams.append(try_encode(track)) audio_numb += 1 # Subtitles elif typus == "3": try: 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 - %s" % (track, lang(39708)) # Default if forced: track = "%s - %s" % (track, lang(39709)) # Forced if downloadable: # 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['languageCode'], stream.attrib['codec'])) # We don't know the language - no need to download else: path = self.api.attach_plex_token_to_url( "%s%s" % (window('pms_server'), stream.attrib['key'])) downloadable_streams.append(index) download_subs.append(try_encode(path)) else: 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 default_sub = index subtitle_streams_list.append(index) subtitle_streams.append(try_encode(track)) sub_num += 1 if audio_numb > 1: resp = dialog('select', lang(33013), audio_streams) if resp > -1: # User selected some audio track args = { 'audioStreamID': audio_streams_list[resp], 'allParts': 1 } DU().downloadUrl('{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: resp = dialog('select', lang(33014), subtitle_streams) if resp > 0: select_subs_index = subtitle_streams_list[resp-1] else: # User selected no subtitles or backed out of dialog select_subs_index = '' LOG.debug('Adding external subtitles: %s', download_subs) # Enable Kodi to switch autonomously to downloadable subtitles if download_subs: listitem.setSubtitles(download_subs) # Don't additionally burn in subtitles if select_subs_index in downloadable_streams: select_subs_index = '' # Now prep the PMS for our choice args = { 'subtitleStreamID': select_subs_index, 'allParts': 1 } DU().downloadUrl('{server}/library/parts/%s' % part_id, action_type='PUT', parameters=args)