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 (!! exists() needs / or \ at end) path = path.replace(os_path.basename(path), '') # Only proceed if we can access this folder import xbmcvfs if xbmcvfs.exists(path): # Careful, returns encoded strings! dirs, files = xbmcvfs.listdir(path) for file in files: file = path + tryDecode(file) li = ListItem(file, path=file) xbmcplugin.addDirectoryItem(handle=HANDLE, url=tryEncode(file), listitem=li) for dir in dirs: dir = path + tryDecode(dir) li = ListItem(dir, path=dir) xbmcplugin.addDirectoryItem(handle=HANDLE, url=tryEncode(dir), listitem=li, isFolder=True) else: log.warn('Kodi cannot access folder %s' % path) xbmcplugin.endOfDirectory(HANDLE)
def singleNode(self, indexnumber, tagname, mediatype, itemtype): window = utils.window tagname = utils.tryEncode(tagname) cleantagname = utils.normalize_nodes(tagname) nodepath = utils.tryDecode( xbmc.translatePath("special://profile/library/video/")) nodeXML = "%splex_%s.xml" % (nodepath, cleantagname) path = "library://video/plex_%s.xml" % cleantagname windowpath = "ActivateWindow(Video,%s,return)" % path # Create the video node directory if not xbmcvfs.exists(nodepath): # We need to copy over the default items shutil.copytree( src=utils.tryDecode( xbmc.translatePath("special://xbmc/system/library/video")), dst=utils.tryDecode( xbmc.translatePath("special://profile/library/video"))) xbmcvfs.exists(path) labels = { 'Favorite movies': 30180, 'Favorite tvshows': 30181, 'channels': 30173 } label = utils.language(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 xbmcvfs.exists(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: utils.indent(root) except: pass etree.ElementTree(root).write(nodeXML)
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 """ import xbmcvfs 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 = tryDecode(translatePath( "special://thumbnails/plex/%s/" % plexid)) if not xbmcvfs.exists(fanartDir): # Download the images to the cache directory xbmcvfs.mkdirs(tryEncode(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.getAllArtwork()['Backdrop'] for count, backdrop in enumerate(backdrops): # Same ordering as in artwork if os_path.supports_unicode_filenames: fanartFile = os_path.join(fanartDir, "fanart%.3d.jpg" % count) else: fanartFile = os_path.join( tryEncode(fanartDir), tryEncode("fanart%.3d.jpg" % count)) li = ListItem("%.3d" % count, path=fanartFile) xbmcplugin.addDirectoryItem( handle=HANDLE, url=fanartFile, listitem=li) xbmcvfs.copy(backdrop, fanartFile) else: log.info("Found cached backdrop.") # Use existing cached images dirs, files = xbmcvfs.listdir(fanartDir) for file in files: fanartFile = os_path.join(fanartDir, tryDecode(file)) li = ListItem(file, path=fanartFile) xbmcplugin.addDirectoryItem(handle=HANDLE, url=fanartFile, listitem=li) xbmcplugin.endOfDirectory(HANDLE)
def singleNode(self, indexnumber, tagname, mediatype, itemtype): tagname = tryEncode(tagname) cleantagname = normalize_nodes(tagname) nodepath = tryDecode(xbmc.translatePath( "special://profile/library/video/")) nodeXML = "%splex_%s.xml" % (nodepath, cleantagname) path = "library://video/plex_%s.xml" % cleantagname if self.kodiversion >= 17: # Krypton windowpath = "ActivateWindow(Videos,%s,return)" % path else: windowpath = "ActivateWindow(Video,%s,return)" % path # Create the video node directory if not xbmcvfs.exists(nodepath): # We need to copy over the default items shutil.copytree( src=tryDecode(xbmc.translatePath( "special://xbmc/system/library/video")), dst=tryDecode(xbmc.translatePath( "special://profile/library/video"))) xbmcvfs.exists(path) 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 xbmcvfs.exists(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)
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 """ import xbmcvfs 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 = tryDecode( translatePath("special://thumbnails/plex/%s/" % plexid)) if not xbmcvfs.exists(fanartDir): # Download the images to the cache directory xbmcvfs.mkdirs(tryEncode(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.getAllArtwork()['Backdrop'] for count, backdrop in enumerate(backdrops): # Same ordering as in artwork if os_path.supports_unicode_filenames: fanartFile = os_path.join(fanartDir, "fanart%.3d.jpg" % count) else: fanartFile = os_path.join(tryEncode(fanartDir), tryEncode("fanart%.3d.jpg" % count)) li = ListItem("%.3d" % count, path=fanartFile) xbmcplugin.addDirectoryItem(handle=HANDLE, url=fanartFile, listitem=li) xbmcvfs.copy(backdrop, fanartFile) else: log.info("Found cached backdrop.") # Use existing cached images dirs, files = xbmcvfs.listdir(fanartDir) for file in files: fanartFile = os_path.join(fanartDir, tryDecode(file)) li = ListItem(file, path=fanartFile) xbmcplugin.addDirectoryItem(handle=HANDLE, url=fanartFile, listitem=li) xbmcplugin.endOfDirectory(HANDLE)
def deleteCachedArtwork(self, url): # Only necessary to remove and apply a new backdrop or poster connection = utils.kodiSQL('texture') cursor = connection.cursor() try: cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,)) cachedurl = cursor.fetchone()[0] except TypeError: self.logMsg("Could not find cached url.", 1) except OperationalError: self.logMsg("Database is locked. Skip deletion process.", 1) else: # Delete thumbnail as well as the entry thumbnails = utils.tryDecode( xbmc.translatePath("special://thumbnails/%s" % cachedurl)) self.logMsg("Deleting cached thumbnail: %s" % thumbnails, 1) xbmcvfs.delete(thumbnails) try: cursor.execute("DELETE FROM texture WHERE url = ?", (url,)) connection.commit() except OperationalError: self.logMsg("Issue deleting url from cache. Skipping.", 2) finally: cursor.close()
def deleteCachedArtwork(self, url): # Only necessary to remove and apply a new backdrop or poster connection = utils.kodiSQL('texture') cursor = connection.cursor() try: cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url, )) cachedurl = cursor.fetchone()[0] except TypeError: self.logMsg("Could not find cached url.", 1) except OperationalError: self.logMsg("Database is locked. Skip deletion process.", 1) else: # Delete thumbnail as well as the entry thumbnails = utils.tryDecode( xbmc.translatePath("special://thumbnails/%s" % cachedurl)) self.logMsg("Deleting cached thumbnail: %s" % thumbnails, 1) xbmcvfs.delete(thumbnails) try: cursor.execute("DELETE FROM texture WHERE url = ?", (url, )) connection.commit() except OperationalError: self.logMsg("Issue deleting url from cache. Skipping.", 2) finally: cursor.close()
def single_urlencode(self, text): text = urllib.urlencode({'blahblahblah': utils.tryEncode(text) }) #urlencode needs a utf- string text = text[13:] return utils.tryDecode(text) #return the result again as unicode
def getRealFileName(filename, isTemp=False): #get the filename path accessible by python if possible... if not xbmcvfs.exists(filename): logMsg("File does not exist! %s" % (filename), 0) return (False, "") #if we use os.path method on older python versions (sunch as some android builds), we need to pass arguments as string if os.path.supports_unicode_filenames: checkfile = filename else: checkfile = utils.tryEncode(filename) # determine if our python module is able to access the file directly... if os.path.exists(checkfile): filename = filename elif os.path.exists( checkfile.replace("smb://", "\\\\").replace("/", "\\")): filename = filename.replace("smb://", "\\\\").replace("/", "\\") else: #file can not be accessed by python directly, we copy it for processing... isTemp = True if "/" in filename: filepart = filename.split("/")[-1] else: filepart = filename.split("\\")[-1] tempfile = "special://temp/" + filepart xbmcvfs.copy(filename, tempfile) filename = utils.tryDecode(xbmc.translatePath(tempfile)) return (isTemp, filename)
def getRealFileName(filename, isTemp=False): #get the filename path accessible by python if possible... if not xbmcvfs.exists(filename): logMsg( "File does not exist! %s" %(filename), 0) return (False, "") #if we use os.path method on older python versions (sunch as some android builds), we need to pass arguments as string if os.path.supports_unicode_filenames: checkfile = filename else: checkfile = utils.tryEncode(filename) # determine if our python module is able to access the file directly... if os.path.exists(checkfile): filename = filename elif os.path.exists(checkfile.replace("smb://","\\\\").replace("/","\\")): filename = filename.replace("smb://","\\\\").replace("/","\\") else: #file can not be accessed by python directly, we copy it for processing... isTemp = True if "/" in filename: filepart = filename.split("/")[-1] else: filepart = filename.split("\\")[-1] tempfile = "special://temp/"+filepart xbmcvfs.copy(filename, tempfile) filename = utils.tryDecode(xbmc.translatePath(tempfile)) return (isTemp,filename)
def onPlayBackResumed(self): currentFile = self.currentFile self.logMsg("PLAYBACK_RESUMED: %s" % utils.tryDecode(currentFile), 2) if self.played_info.get(currentFile): self.played_info[currentFile]['paused'] = False self.reportPlayback()
def onPlayBackPaused(self): currentFile = self.currentFile self.logMsg("PLAYBACK_PAUSED: %s" % utils.tryDecode(currentFile), 2) if self.played_info.get(currentFile): self.played_info[currentFile]['paused'] = True self.reportPlayback()
def getDeviceName(self): if settings('deviceNameOpt') == "false": # Use Kodi's deviceName deviceName = tryDecode(xbmc.getInfoLabel('System.FriendlyName')) else: deviceName = settings('deviceName') deviceName = deviceName.replace("\"", "_") deviceName = deviceName.replace("/", "_") return deviceName
def updateRatingToFile(rating, file): #update the rating from Emby to the file f = xbmcvfs.File(file) org_size = f.size() f.close() #create tempfile if "/" in file: filepart = file.split("/")[-1] else: filepart = file.split("\\")[-1] tempfile = "special://temp/" + filepart xbmcvfs.copy(file, tempfile) tempfile = utils.tryDecode(xbmc.translatePath(tempfile)) logMsg("setting song rating: %s for filename: %s - using tempfile: %s" % (rating, file, tempfile)) if not tempfile: return try: if tempfile.lower().endswith(".flac"): audio = FLAC(tempfile) calcrating = int(round((float(rating) / 5) * 100, 0)) audio["rating"] = str(calcrating) audio.save() elif tempfile.lower().endswith(".mp3"): audio = ID3(tempfile) calcrating = int(round((float(rating) / 5) * 255, 0)) audio.add( id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1)) audio.save() else: logMsg("Not supported fileformat: %s" % (tempfile)) #once we have succesfully written the flags we move the temp file to destination, otherwise not proceeding and just delete the temp #safety check: we check the file size of the temp file before proceeding with overwite of original file f = xbmcvfs.File(tempfile) checksum_size = f.size() f.close() if checksum_size >= org_size: xbmcvfs.delete(file) xbmcvfs.copy(tempfile, file) else: logMsg( "Checksum mismatch for filename: %s - using tempfile: %s - not proceeding with file overwite!" % (rating, file, tempfile)) #always delete the tempfile xbmcvfs.delete(tempfile) except Exception as e: #file in use ? logMsg("Exception in updateRatingToFile %s" % e, 0)
def onPlayBackSeek(self, time, seekOffset): # Make position when seeking a bit more accurate currentFile = self.currentFile self.logMsg("PLAYBACK_SEEK: %s" % utils.tryDecode(currentFile), 2) if self.played_info.get(currentFile): position = self.xbmcplayer.getTime() self.played_info[currentFile]['currentPosition'] = position self.reportPlayback()
def fullTextureCacheSync(self): """ This method will sync all Kodi artwork to textures13.db and cache them locally. This takes diskspace! """ if not dialog('yesno', "Image Texture Cache", lang(39250)): return LOG.info("Doing Image Cache Sync") # ask to rest all existing or not if dialog('yesno', "Image Texture Cache", lang(39251)): LOG.info("Resetting all cache data first") # Remove all existing textures first path = tryDecode(translatePath("special://thumbnails/")) if exists_dir(path): rmtree(path, ignore_errors=True) self.restoreCacheDirectories() # remove all existing data from texture DB connection = kodiSQL('texture') cursor = connection.cursor() query = 'SELECT tbl_name FROM sqlite_master WHERE type=?' cursor.execute(query, ('table', )) rows = cursor.fetchall() for row in rows: tableName = row[0] if tableName != "version": cursor.execute("DELETE FROM %s" % tableName) connection.commit() connection.close() # Cache all entries in video DB connection = kodiSQL('video') cursor = connection.cursor() # dont include actors query = "SELECT url FROM art WHERE media_type != ?" cursor.execute(query, ('actor', )) result = cursor.fetchall() total = len(result) LOG.info("Image cache sync about to process %s video images" % total) connection.close() for url in result: self.cacheTexture(url[0]) # Cache all entries in music DB connection = kodiSQL('music') cursor = connection.cursor() cursor.execute("SELECT url FROM art") result = cursor.fetchall() total = len(result) LOG.info("Image cache sync about to process %s music images" % total) connection.close() for url in result: self.cacheTexture(url[0])
def __init__(self): # Initial logging LOG.info("======== START %s ========", v.ADDON_NAME) LOG.info("Platform: %s", v.PLATFORM) LOG.info("KODI Version: %s", v.KODILONGVERSION) LOG.info("%s Version: %s", v.ADDON_NAME, v.ADDON_VERSION) LOG.info("PKC Direct Paths: %s", settings('useDirectPaths') == "true") LOG.info("Number of sync threads: %s", settings('syncThreadNumber')) LOG.info("Full sys.argv received: %s", argv) # Reset window props for profile switch properties = [ "plex_online", "plex_serverStatus", "plex_onWake", "plex_kodiScan", "plex_shouldStop", "plex_dbScan", "plex_initialScan", "plex_customplayqueue", "plex_playbackProps", "pms_token", "plex_token", "pms_server", "plex_machineIdentifier", "plex_servername", "plex_authenticated", "PlexUserImage", "useDirectPaths", "countError", "countUnauthorized", "plex_restricteduser", "plex_allows_mediaDeletion", "plex_command", "plex_result", "plex_force_transcode_pix" ] for prop in properties: window(prop, clear=True) # Clear video nodes properties videonodes.VideoNodes().clearProperties() # Init some stuff state.VERIFY_SSL_CERT = settings('sslverify') == 'true' state.SSL_CERT_PATH = settings('sslcert') \ if settings('sslcert') != 'None' else None state.FULL_SYNC_INTERVALL = int(settings('fullSyncInterval')) * 60 state.SYNC_THREAD_NUMBER = int(settings('syncThreadNumber')) state.SYNC_DIALOG = settings('dbSyncIndicator') == 'true' state.ENABLE_MUSIC = settings('enableMusic') == 'true' state.BACKGROUND_SYNC = settings( 'enableBackgroundSync') == 'true' state.BACKGROUNDSYNC_SAFTYMARGIN = int( settings('backgroundsync_saftyMargin')) state.REPLACE_SMB_PATH = settings('replaceSMB') == 'true' state.REMAP_PATH = settings('remapSMB') == 'true' set_replace_paths() state.KODI_PLEX_TIME_OFFSET = float(settings('kodiplextimeoffset')) window('plex_minDBVersion', value="2.0.0") set_webserver() self.monitor = Monitor() window('plex_kodiProfile', value=tryDecode(translatePath("special://profile"))) window('fetch_pms_item_number', value=settings('fetch_pms_item_number')) clientinfo.getDeviceId()
def onPlayBackSeek(self, time, seekOffset): # Make position when seeking a bit more accurate currentFile = self.currentFile self.logMsg("PLAYBACK_SEEK: %s" % utils.tryDecode(currentFile), 2) if self.played_info.get(currentFile): try: position = self.xbmcplayer.getTime() except RuntimeError: # When Kodi is not playing return self.played_info[currentFile]['currentPosition'] = position * 1000 self.reportPlayback()
def updateRatingToFile(rating, file): #update the rating from Emby to the file f = xbmcvfs.File(file) org_size = f.size() f.close() #create tempfile if "/" in file: filepart = file.split("/")[-1] else: filepart = file.split("\\")[-1] tempfile = "special://temp/"+filepart xbmcvfs.copy(file, tempfile) tempfile = utils.tryDecode(xbmc.translatePath(tempfile)) logMsg( "setting song rating: %s for filename: %s - using tempfile: %s" %(rating,file,tempfile)) if not tempfile: return try: if tempfile.lower().endswith(".flac"): audio = FLAC(tempfile) calcrating = int(round((float(rating) / 5) * 100, 0)) audio["rating"] = str(calcrating) audio.save() elif tempfile.lower().endswith(".mp3"): audio = ID3(tempfile) calcrating = int(round((float(rating) / 5) * 255, 0)) audio.add(id3.POPM(email="Windows Media Player 9 Series", rating=calcrating, count=1)) audio.save() else: logMsg( "Not supported fileformat: %s" %(tempfile)) #once we have succesfully written the flags we move the temp file to destination, otherwise not proceeding and just delete the temp #safety check: we check the file size of the temp file before proceeding with overwite of original file f = xbmcvfs.File(tempfile) checksum_size = f.size() f.close() if checksum_size >= org_size: xbmcvfs.delete(file) xbmcvfs.copy(tempfile,file) else: logMsg( "Checksum mismatch for filename: %s - using tempfile: %s - not proceeding with file overwite!" %(rating,file,tempfile)) #always delete the tempfile xbmcvfs.delete(tempfile) except Exception as e: #file in use ? logMsg("Exception in updateRatingToFile %s" %e,0)
def __init__(self): logLevel = self.getLogLevel() self.monitor = Monitor() window('plex_logLevel', value=str(logLevel)) window('plex_kodiProfile', value=tryDecode(translatePath("special://profile"))) window('plex_context', value='true' if settings('enableContext') == "true" else "") window('fetch_pms_item_number', value=settings('fetch_pms_item_number')) # Initial logging log.warn("======== START %s ========" % v.ADDON_NAME) log.warn("Platform: %s" % v.PLATFORM) log.warn("KODI Version: %s" % v.KODILONGVERSION) log.warn("%s Version: %s" % (v.ADDON_NAME, v.ADDON_VERSION)) log.warn("Using plugin paths: %s" % (settings('useDirectPaths') != "true")) log.warn("Number of sync threads: %s" % settings('syncThreadNumber')) log.warn("Log Level: %s" % logLevel) log.warn("Full sys.argv received: %s" % argv) # Reset window props for profile switch properties = [ "plex_online", "plex_serverStatus", "plex_onWake", "plex_dbCheck", "plex_kodiScan", "plex_shouldStop", "plex_dbScan", "plex_initialScan", "plex_customplayqueue", "plex_playbackProps", "plex_runLibScan", "pms_token", "plex_token", "pms_server", "plex_machineIdentifier", "plex_servername", "plex_authenticated", "PlexUserImage", "useDirectPaths", "kodiplextimeoffset", "countError", "countUnauthorized", "plex_restricteduser", "plex_allows_mediaDeletion", "plex_command", "plex_result", "plex_force_transcode_pix" ] for prop in properties: window(prop, clear=True) # Clear video nodes properties videonodes.VideoNodes().clearProperties() # Set the minimum database version window('plex_minDBVersion', value="1.5.10")
def deleteCachedArtwork(self, url): # Only necessary to remove and apply a new backdrop or poster connection = kodiSQL('texture') cursor = connection.cursor() try: cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,)) cachedurl = cursor.fetchone()[0] except TypeError: log.info("Could not find cached url.") else: # Delete thumbnail as well as the entry path = translatePath("special://thumbnails/%s" % cachedurl) log.debug("Deleting cached thumbnail: %s" % path) if exists(path): rmtree(tryDecode(path), ignore_errors=True) cursor.execute("DELETE FROM texture WHERE url = ?", (url,)) connection.commit() finally: connection.close()
def deleteCachedArtwork(self, url): # Only necessary to remove and apply a new backdrop or poster connection = kodiSQL('texture') cursor = connection.cursor() try: cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url,)) cachedurl = cursor.fetchone()[0] except TypeError: log.info("Could not find cached url.") else: # Delete thumbnail as well as the entry thumbnails = tryDecode( translatePath("special://thumbnails/%s" % cachedurl)) log.debug("Deleting cached thumbnail: %s" % thumbnails) try: delete(thumbnails) except Exception as e: log.error('Could not delete cached artwork %s. Error: %s' % (thumbnails, e)) cursor.execute("DELETE FROM texture WHERE url = ?", (url,)) connection.commit() finally: connection.close()
def deleteCachedArtwork(self, url): # Only necessary to remove and apply a new backdrop or poster connection = kodiSQL('texture') cursor = connection.cursor() try: cursor.execute("SELECT cachedurl FROM texture WHERE url = ?", (url, )) cachedurl = cursor.fetchone()[0] except TypeError: log.info("Could not find cached url.") else: # Delete thumbnail as well as the entry thumbnails = tryDecode( xbmc.translatePath("special://thumbnails/%s" % cachedurl)) log.debug("Deleting cached thumbnail: %s" % thumbnails) try: xbmcvfs.delete(thumbnails) except Exception as e: log.error('Could not delete cached artwork %s. Error: %s' % (thumbnails, e)) cursor.execute("DELETE FROM texture WHERE url = ?", (url, )) connection.commit() finally: connection.close()
def PlayBackStart(self, data): """ Called whenever a playback is started """ log = self.logMsg window = utils.window # Get currently playing file - can take a while. Will be utf-8! try: currentFile = self.xbmcplayer.getPlayingFile() except: currentFile = None count = 0 while currentFile is None: xbmc.sleep(100) try: currentFile = self.xbmcplayer.getPlayingFile() except: pass if count == 50: log("No current File - Cancelling OnPlayBackStart...", -1) return else: count += 1 log("Currently playing file is: %s" % utils.tryDecode(currentFile), 1) # Try to get a Kodi ID item = data.get('item') try: type = item['type'] except: log("Item is invalid for PMS playstate update.", 0) return try: kodiid = item['id'] except (KeyError, TypeError): itemType = window("emby_%s.type" % currentFile) log("No kodi id passed. Playing itemtype is: %s" % itemType, 1) if itemType in ('movie', 'episode'): # Window was setup by PKC and is NOT a trailer ('clip') with kodidb.GetKodiDB('video') as kodi_db: kodiid = kodi_db.getIdFromTitle(data.get('item')) if kodiid is None: log( "Skip playstate update. No unique Kodi title found" " for %s" % data.get('item'), 0) return else: log("Item is invalid for PMS playstate update.", 0) return # Get Plex' item id with embydb.GetEmbyDB() as emby_db: emby_dbitem = emby_db.getItem_byKodiId(kodiid, type) try: plexid = emby_dbitem[0] except TypeError: log("No Plex id returned for kodiid %s" % kodiid, 0) return log("Found Plex id %s for Kodi id %s" % (plexid, kodiid), 1) # Set some stuff if Kodi initiated playback if ((utils.settings('useDirectPaths') == "1" and not type == "song") or (type == "song" and utils.settings('enableMusic') == "true")): if self.StartDirectPath(plexid, type, currentFile) is False: log('Could not initiate monitoring; aborting', -1) return # Save currentFile for cleanup later and to be able to access refs window('plex_lastPlayedFiled', value=utils.tryDecode(currentFile)) window('Plex_currently_playing_itemid', value=plexid) window("emby_%s.itemid" % currentFile, value=plexid) log('Finish playback startup', 1)
def play(self, plex_id, kodi_id=None, plex_lib_UUID=None): """ plex_lib_UUID: xml attribute 'librarySectionUUID', needed for posting to the PMS """ log.info("Playbackutils called") item = self.xml[0] api = API(item) playqueue = self.playqueue xml = None result = Playback_Successful() listitem = ListItem() playutils = putils.PlayUtils(item) playurl = playutils.getPlayUrl() if not playurl: log.error('No playurl found, aborting') return if kodi_id in (None, 'plextrailer', 'plexnode'): # Item is not in Kodi database, is a trailer/clip or plex redirect # e.g. plex.tv watch later api.CreateListItemFromPlexItem(listitem) api.set_listitem_artwork(listitem) if kodi_id == 'plexnode': # Need to get yet another xml to get final url window('plex_%s.playmethod' % playurl, clear=True) xml = downloadutils.DownloadUtils().downloadUrl( '{server}%s' % item[0][0].attrib.get('key')) try: xml[0].attrib except (TypeError, AttributeError): log.error('Could not download %s' % item[0][0].attrib.get('key')) return playurl = tryEncode(xml[0].attrib.get('key')) window('plex_%s.playmethod' % playurl, value='DirectStream') playmethod = window('plex_%s.playmethod' % playurl) if playmethod == "Transcode": playutils.audioSubsPref(listitem, tryDecode(playurl)) listitem.setPath(playurl) api.set_playback_win_props(playurl, listitem) result.listitem = listitem return result kodi_type = v.KODITYPE_FROM_PLEXTYPE[api.getType()] kodi_id = int(kodi_id) # ORGANIZE CURRENT PLAYLIST ################ contextmenu_play = window('plex_contextplay') == 'true' window('plex_contextplay', clear=True) homeScreen = getCondVisibility('Window.IsActive(home)') sizePlaylist = len(playqueue.items) if contextmenu_play: # Need to start with the items we're inserting here startPos = sizePlaylist else: # Can return -1 startPos = max(playqueue.kodi_pl.getposition(), 0) self.currentPosition = startPos propertiesPlayback = window('plex_playbackProps') == "true" introsPlaylist = False dummyPlaylist = False log.info("Playing from contextmenu: %s" % contextmenu_play) log.info("Playlist start position: %s" % startPos) log.info("Playlist plugin position: %s" % self.currentPosition) log.info("Playlist size: %s" % sizePlaylist) # RESUME POINT ################ seektime, runtime = api.getRuntime() if window('plex_customplaylist.seektime'): # Already got seektime, e.g. from playqueue & Plex companion seektime = int(window('plex_customplaylist.seektime')) # We need to ensure we add the intro and additional parts only once. # Otherwise we get a loop. if not propertiesPlayback: window('plex_playbackProps', value="true") log.info("Setting up properties in playlist.") # Where will the player need to start? # Do we need to get trailers? trailers = False if (api.getType() == v.PLEX_TYPE_MOVIE and not seektime and sizePlaylist < 2 and settings('enableCinema') == "true"): if settings('askCinema') == "true": trailers = xbmcgui.Dialog().yesno(lang(29999), "Play trailers?") else: trailers = True # Post to the PMS. REUSE THE PLAYQUEUE! xml = init_plex_playqueue(plex_id, plex_lib_UUID, mediatype=api.getType(), trailers=trailers) try: get_playlist_details_from_xml(playqueue, xml=xml) except KeyError: return if (not homeScreen and not seektime and sizePlaylist < 2 and window('plex_customplaylist') != "true" and not contextmenu_play): # Need to add a dummy file because the first item will fail log.debug("Adding dummy file to playlist.") dummyPlaylist = True add_listitem_to_Kodi_playlist(playqueue, startPos, xbmcgui.ListItem(), playurl, xml[0]) # Remove the original item from playlist remove_from_Kodi_playlist(playqueue, startPos + 1) # Readd the original item to playlist - via jsonrpc so we have # full metadata add_item_to_kodi_playlist(playqueue, self.currentPosition + 1, kodi_id=kodi_id, kodi_type=kodi_type, file=playurl) self.currentPosition += 1 # -- ADD TRAILERS ################ if trailers: for i, item in enumerate(xml): if i == len(xml) - 1: # Don't add the main movie itself break self.add_trailer(item) introsPlaylist = True # -- ADD MAIN ITEM ONLY FOR HOMESCREEN ############## if homeScreen and not seektime and not sizePlaylist: # Extend our current playlist with the actual item to play # only if there's no playlist first log.info("Adding main item to playlist.") add_item_to_kodi_playlist(playqueue, self.currentPosition, kodi_id, kodi_type) elif contextmenu_play: if state.DIRECT_PATHS: # Cannot add via JSON with full metadata because then we # Would be using the direct path log.debug("Adding contextmenu item for direct paths") if window('plex_%s.playmethod' % playurl) == "Transcode": playutils.audioSubsPref(listitem, tryDecode(playurl)) api.CreateListItemFromPlexItem(listitem) api.set_playback_win_props(playurl, listitem) api.set_listitem_artwork(listitem) add_listitem_to_Kodi_playlist( playqueue, self.currentPosition + 1, convert_PKC_to_listitem(listitem), file=playurl, kodi_item={ 'id': kodi_id, 'type': kodi_type }) else: # Full metadata$ add_item_to_kodi_playlist(playqueue, self.currentPosition + 1, kodi_id, kodi_type) self.currentPosition += 1 if seektime: window('plex_customplaylist.seektime', value=str(seektime)) # Ensure that additional parts are played after the main item self.currentPosition += 1 # -- CHECK FOR ADDITIONAL PARTS ################ if len(item[0]) > 1: self.add_part(item, api, kodi_id, kodi_type) if dummyPlaylist: # Added a dummy file to the playlist, # because the first item is going to fail automatically. log.info("Processed as a playlist. First item is skipped.") # Delete the item that's gonna fail! del playqueue.items[startPos] # Don't attach listitem return result # We just skipped adding properties. Reset flag for next time. elif propertiesPlayback: log.debug("Resetting properties playback flag.") window('plex_playbackProps', clear=True) # SETUP MAIN ITEM ########## # For transcoding only, ask for audio/subs pref if (window('plex_%s.playmethod' % playurl) == "Transcode" and not contextmenu_play): playutils.audioSubsPref(listitem, tryDecode(playurl)) listitem.setPath(playurl) api.set_playback_win_props(playurl, listitem) api.set_listitem_artwork(listitem) # PLAYBACK ################ if (homeScreen and seektime and window('plex_customplaylist') != "true" and not contextmenu_play): log.info("Play as a widget item") api.CreateListItemFromPlexItem(listitem) result.listitem = listitem return result elif ((introsPlaylist and window('plex_customplaylist') == "true") or (homeScreen and not sizePlaylist) or contextmenu_play): # Playlist was created just now, play it. # Contextmenu plays always need this log.info("Play playlist from starting position %s" % startPos) # Need a separate thread because Player won't return in time thread = Thread(target=Player().play, args=(playqueue.kodi_pl, None, False, startPos)) thread.setDaemon(True) thread.start() # Don't attach listitem return result else: log.info("Play as a regular item") result.listitem = listitem return result
def authenticate(self): log.info('Authenticating user') dialog = xbmcgui.Dialog() # Give attempts at entering password / selecting user if self.retry >= 2: log.error("Too many retries to login.") window('plex_serverStatus', value="Stop") dialog.ok(lang(33001), lang(39023)) xbmc.executebuiltin( 'Addon.OpenSettings(plugin.video.plexkodiconnect)') return False # Get /profile/addon_data addondir = tryDecode( xbmc.translatePath(self.addon.getAddonInfo('profile'))) hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir) # If there's no settings.xml if not hasSettings: log.error("Error, no settings.xml found.") self.auth = False return False server = self.getServer() # If there is no server we can connect to if not server: log.info("Missing server information.") self.auth = False return False # If there is a username in the settings, try authenticating username = settings('username') userId = settings('userid') usertoken = settings('accessToken') enforceLogin = settings('enforceUserLogin') # Found a user in the settings, try to authenticate if username and enforceLogin == 'false': log.info('Trying to authenticate with old settings') if self.loadCurrUser(username, userId, usertoken, authenticated=False): # SUCCESS: loaded a user from the settings return True else: # Failed to use the settings - delete them! log.info("Failed to use settings credentials. Deleting them") settings('username', value='') settings('userid', value='') settings('accessToken', value='') plx = PlexAPI.PlexAPI() # Could not use settings - try to get Plex user list from plex.tv plextoken = settings('plexToken') if plextoken: log.info("Trying to connect to plex.tv to get a user list") userInfo = plx.ChoosePlexHomeUser(plextoken) if userInfo is False: # FAILURE: Something went wrong, try again self.auth = True self.retry += 1 return False username = userInfo['username'] userId = userInfo['userid'] usertoken = userInfo['token'] else: log.info("Trying to authenticate without a token") username = '' userId = '' usertoken = '' if self.loadCurrUser(username, userId, usertoken, authenticated=False): # SUCCESS: loaded a user from the settings return True else: # FAILUR: Something went wrong, try again self.auth = True self.retry += 1 return False
def play(self, itemid, dbid=None): window = utils.window settings = utils.settings item = self.item # Hack to get only existing entry in PMS response for THIS instance of # playbackutils :-) self.API = PlexAPI.API(item[0]) API = self.API listitem = xbmcgui.ListItem() playutils = putils.PlayUtils(item[0]) self.logMsg("Play called.", 1) playurl = playutils.getPlayUrl() if not playurl: return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) if dbid in (None, '999999999', 'plexnode'): # Item is not in Kodi database, is a trailer or plex redirect # e.g. plex.tv watch later API.CreateListItemFromPlexItem(listitem) self.setArtwork(listitem) if dbid == 'plexnode': # Need to get yet another xml to get final url window('emby_%s.playmethod' % playurl, clear=True) xml = downloadutils.DownloadUtils().downloadUrl( '{server}%s' % item[0][0][0].attrib.get('key')) if xml in (None, 401): self.logMsg('Could not download %s' % item[0][0][0].attrib.get('key'), -1) return xbmcplugin.setResolvedUrl( int(sys.argv[1]), False, listitem) playurl = utils.tryEncode(xml[0].attrib.get('key')) window('emby_%s.playmethod' % playurl, value='DirectStream') playmethod = window('emby_%s.playmethod' % playurl) if playmethod == "Transcode": window('emby_%s.playmethod' % playurl, clear=True) playurl = utils.tryEncode(playutils.audioSubsPref( listitem, utils.tryDecode(playurl))) window('emby_%s.playmethod' % playurl, "Transcode") listitem.setPath(playurl) self.setProperties(playurl, listitem) return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem) ############### ORGANIZE CURRENT PLAYLIST ################ homeScreen = xbmc.getCondVisibility('Window.IsActive(home)') playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) startPos = max(playlist.getposition(), 0) # Can return -1 sizePlaylist = playlist.size() self.currentPosition = startPos propertiesPlayback = window('plex_playbackProps') == "true" introsPlaylist = False dummyPlaylist = False self.logMsg("Playlist start position: %s" % startPos, 2) self.logMsg("Playlist plugin position: %s" % self.currentPosition, 2) self.logMsg("Playlist size: %s" % sizePlaylist, 2) ############### RESUME POINT ################ seektime, runtime = API.getRuntime() # We need to ensure we add the intro and additional parts only once. # Otherwise we get a loop. if not propertiesPlayback: window('plex_playbackProps', value="true") self.logMsg("Setting up properties in playlist.", 1) if (not homeScreen and not seektime and window('plex_customplaylist') != "true"): self.logMsg("Adding dummy file to playlist.", 2) dummyPlaylist = True playlist.add(playurl, listitem, index=startPos) # Remove the original item from playlist self.pl.removefromPlaylist(startPos+1) # Readd the original item to playlist - via jsonrpc so we have full metadata self.pl.insertintoPlaylist( self.currentPosition+1, dbid, PF.GetKodiTypeFromPlex(API.getType())) self.currentPosition += 1 ############### -- CHECK FOR INTROS ################ if (settings('enableCinema') == "true" and not seektime): # if we have any play them when the movie/show is not being resumed xml = PF.GetPlexPlaylist( itemid, item.attrib.get('librarySectionUUID'), mediatype=API.getType()) introsPlaylist = self.AddTrailers(xml) ############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ############### if homeScreen and not seektime and not sizePlaylist: # Extend our current playlist with the actual item to play # only if there's no playlist first self.logMsg("Adding main item to playlist.", 1) self.pl.addtoPlaylist( dbid, PF.GetKodiTypeFromPlex(API.getType())) # Ensure that additional parts are played after the main item self.currentPosition += 1 ############### -- CHECK FOR ADDITIONAL PARTS ################ if len(item[0][0]) > 1: # Only add to the playlist after intros have played for counter, part in enumerate(item[0][0]): # Never add first part if counter == 0: continue # Set listitem and properties for each additional parts API.setPartNumber(counter) additionalListItem = xbmcgui.ListItem() additionalPlayurl = playutils.getPlayUrl( partNumber=counter) self.logMsg("Adding additional part: %s" % counter, 1) self.setProperties(additionalPlayurl, additionalListItem) self.setArtwork(additionalListItem) # NEW to Plex API.CreateListItemFromPlexItem(additionalListItem) playlist.add(additionalPlayurl, additionalListItem, index=self.currentPosition) self.pl.verifyPlaylist() self.currentPosition += 1 API.setPartNumber = 0 if dummyPlaylist: # Added a dummy file to the playlist, # because the first item is going to fail automatically. self.logMsg("Processed as a playlist. First item is skipped.", 1) return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) # We just skipped adding properties. Reset flag for next time. elif propertiesPlayback: self.logMsg("Resetting properties playback flag.", 2) window('plex_playbackProps', clear=True) #self.pl.verifyPlaylist() ########## SETUP MAIN ITEM ########## # For transcoding only, ask for audio/subs pref if window('emby_%s.playmethod' % playurl) == "Transcode": window('emby_%s.playmethod' % playurl, clear=True) playurl = utils.tryEncode(playutils.audioSubsPref( listitem, utils.tryDecode(playurl))) window('emby_%s.playmethod' % playurl, value="Transcode") listitem.setPath(playurl) self.setProperties(playurl, listitem) ############### PLAYBACK ################ if homeScreen and seektime and window('plex_customplaylist') != "true": self.logMsg("Play as a widget item.", 1) API.CreateListItemFromPlexItem(listitem) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem) elif ((introsPlaylist and window('plex_customplaylist') == "true") or (homeScreen and not sizePlaylist)): # Playlist was created just now, play it. self.logMsg("Play playlist.", 1) xbmc.Player().play(playlist, startpos=startPos) else: self.logMsg("Play as a regular item.", 1) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
def PlayBackStart(self, data): """ Called whenever a playback is started """ # Get currently playing file - can take a while. Will be utf-8! try: currentFile = self.xbmcplayer.getPlayingFile() except: currentFile = None count = 0 while currentFile is None: sleep(100) try: currentFile = self.xbmcplayer.getPlayingFile() except: pass if count == 50: log.info("No current File, cancel OnPlayBackStart...") return else: count += 1 # Just to be on the safe side currentFile = tryDecode(currentFile) log.debug("Currently playing file is: %s" % currentFile) # Get the type of media we're playing try: typus = data['item']['type'] except (TypeError, KeyError): log.info("Item is invalid for PMS playstate update.") return log.debug("Playing itemtype is (or appears to be): %s" % typus) # Try to get a Kodi ID # If PKC was used - native paths, not direct paths plex_id = window('plex_%s.itemid' % tryEncode(currentFile)) # Get rid of the '' if the window property was not set plex_id = None if not plex_id else plex_id kodiid = None if plex_id is None: log.debug('Did not get Plex id from window properties') try: kodiid = data['item']['id'] except (TypeError, KeyError): log.debug('Did not get a Kodi id from Kodi, darn') # For direct paths, if we're not streaming something # When using Widgets, Kodi doesn't tell us shit so we need this hack if (kodiid is None and plex_id is None and typus != 'song' and not currentFile.startswith('http')): (kodiid, typus) = get_kodiid_from_filename(currentFile) if kodiid is None: return if plex_id is None: # Get Plex' item id with plexdb.Get_Plex_DB() as plexcursor: plex_dbitem = plexcursor.getItem_byKodiId(kodiid, typus) try: plex_id = plex_dbitem[0] except TypeError: log.info("No Plex id returned for kodiid %s. Aborting playback" " report" % kodiid) return log.debug("Found Plex id %s for Kodi id %s for type %s" % (plex_id, kodiid, typus)) # Switch subtitle tracks if applicable subtitle = window('plex_%s.subtitle' % tryEncode(currentFile)) if window(tryEncode('plex_%s.playmethod' % currentFile)) \ == 'Transcode' and subtitle: if window('plex_%s.subtitle' % currentFile) == 'None': self.xbmcplayer.showSubtitles(False) else: self.xbmcplayer.setSubtitleStream(int(subtitle)) # Set some stuff if Kodi initiated playback if ((settings('useDirectPaths') == "1" and not typus == "song") or (typus == "song" and settings('enableMusic') == "true")): if self.StartDirectPath(plex_id, typus, tryEncode(currentFile)) is False: log.error('Could not initiate monitoring; aborting') return # Save currentFile for cleanup later and to be able to access refs window('plex_lastPlayedFiled', value=currentFile) window('plex_currently_playing_itemid', value=plex_id) window("plex_%s.itemid" % tryEncode(currentFile), value=plex_id) log.info('Finish playback startup')
def fullTextureCacheSync(self): """ This method will sync all Kodi artwork to textures13.db and cache them locally. This takes diskspace! """ if not xbmcgui.Dialog().yesno("Image Texture Cache", lang(39250)): return log.info("Doing Image Cache Sync") # ask to rest all existing or not if xbmcgui.Dialog().yesno("Image Texture Cache", lang(39251), ""): log.info("Resetting all cache data first") # Remove all existing textures first path = tryDecode(xbmc.translatePath("special://thumbnails/")) if IfExists(path): allDirs, allFiles = xbmcvfs.listdir(path) for dir in allDirs: allDirs, allFiles = xbmcvfs.listdir(path + dir) for file in allFiles: if os.path.supports_unicode_filenames: xbmcvfs.delete( os.path.join(path + tryDecode(dir), tryDecode(file))) else: xbmcvfs.delete( os.path.join(tryEncode(path) + dir, file)) # remove all existing data from texture DB connection = kodiSQL('texture') cursor = connection.cursor() cursor.execute( 'SELECT tbl_name FROM sqlite_master WHERE type="table"') rows = cursor.fetchall() for row in rows: tableName = row[0] if tableName != "version": cursor.execute("DELETE FROM " + tableName) connection.commit() connection.close() # Cache all entries in video DB connection = kodiSQL('video') cursor = connection.cursor() # dont include actors cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") result = cursor.fetchall() total = len(result) log.info("Image cache sync about to process %s video images" % total) connection.close() for url in result: self.cacheTexture(url[0]) # Cache all entries in music DB connection = kodiSQL('music') cursor = connection.cursor() cursor.execute("SELECT url FROM art") result = cursor.fetchall() total = len(result) log.info("Image cache sync about to process %s music images" % total) connection.close() for url in result: self.cacheTexture(url[0])
def onPlayBackStarted(self): """ Will be called when xbmc starts playing a file. Window values need to have been set in Kodimonitor.py """ self.stopAll() # Get current file (in utf-8!) try: currentFile = tryDecode(self.getPlayingFile()) xbmc.sleep(300) except: currentFile = "" count = 0 while not currentFile: xbmc.sleep(100) try: currentFile = tryDecode(self.getPlayingFile()) except: pass if count == 20: break else: count += 1 if not currentFile: log.warn('Error getting currently playing file; abort reporting') return # Save currentFile for cleanup later and for references self.currentFile = currentFile window('plex_lastPlayedFiled', value=currentFile) # We may need to wait for info to be set in kodi monitor itemId = window("plex_%s.itemid" % tryEncode(currentFile)) count = 0 while not itemId: xbmc.sleep(200) itemId = window("plex_%s.itemid" % tryEncode(currentFile)) if count == 5: log.warn("Could not find itemId, cancelling playback report!") return count += 1 log.info("ONPLAYBACK_STARTED: %s itemid: %s" % (currentFile, itemId)) plexitem = "plex_%s" % tryEncode(currentFile) runtime = window("%s.runtime" % plexitem) refresh_id = window("%s.refreshid" % plexitem) playMethod = window("%s.playmethod" % plexitem) itemType = window("%s.type" % plexitem) try: playcount = int(window("%s.playcount" % plexitem)) except ValueError: playcount = 0 window('plex_skipWatched%s' % itemId, value="true") log.debug("Playing itemtype is: %s" % itemType) customseek = window('plex_customplaylist.seektime') if customseek: # Start at, when using custom playlist (play to Kodi from # webclient) log.info("Seeking to: %s" % customseek) try: self.seekTime(int(customseek)) except: log.error('Could not seek!') window('plex_customplaylist.seektime', clear=True) try: seekTime = self.getTime() except RuntimeError: log.error('Could not get current seektime from xbmc player') seekTime = 0 # Get playback volume volume_query = { "jsonrpc": "2.0", "id": 1, "method": "Application.GetProperties", "params": { "properties": ["volume", "muted"] } } result = xbmc.executeJSONRPC(json.dumps(volume_query)) result = json.loads(result) result = result.get('result') volume = result.get('volume') muted = result.get('muted') # Postdata structure to send to plex server url = "{server}/:/timeline?" postdata = { 'QueueableMediaTypes': "Video", 'CanSeek': True, 'ItemId': itemId, 'MediaSourceId': itemId, 'PlayMethod': playMethod, 'VolumeLevel': volume, 'PositionTicks': int(seekTime * 10000000), 'IsMuted': muted } # Get the current audio track and subtitles if playMethod == "Transcode": # property set in PlayUtils.py postdata['AudioStreamIndex'] = window("%sAudioStreamIndex" % tryEncode(currentFile)) postdata['SubtitleStreamIndex'] = window("%sSubtitleStreamIndex" % tryEncode(currentFile)) else: # Get the current kodi audio and subtitles and convert to plex equivalent tracks_query = { "jsonrpc": "2.0", "id": 1, "method": "Player.GetProperties", "params": { "playerid": 1, "properties": [ "currentsubtitle", "currentaudiostream", "subtitleenabled" ] } } result = xbmc.executeJSONRPC(json.dumps(tracks_query)) result = json.loads(result) result = result.get('result') try: # Audio tracks indexAudio = result['currentaudiostream']['index'] except (KeyError, TypeError): indexAudio = 0 try: # Subtitles tracks indexSubs = result['currentsubtitle']['index'] except (KeyError, TypeError): indexSubs = 0 try: # If subtitles are enabled subsEnabled = result['subtitleenabled'] except (KeyError, TypeError): subsEnabled = "" # Postdata for the audio postdata['AudioStreamIndex'] = indexAudio + 1 # Postdata for the subtitles if subsEnabled and len( xbmc.Player().getAvailableSubtitleStreams()) > 0: # Number of audiotracks to help get plex Index audioTracks = len(xbmc.Player().getAvailableAudioStreams()) mapping = window("%s.indexMapping" % plexitem) if mapping: # Set in playbackutils.py log.debug("Mapping for external subtitles index: %s" % mapping) externalIndex = json.loads(mapping) if externalIndex.get(str(indexSubs)): # If the current subtitle is in the mapping postdata['SubtitleStreamIndex'] = externalIndex[str( indexSubs)] else: # Internal subtitle currently selected subindex = indexSubs - len( externalIndex) + audioTracks + 1 postdata['SubtitleStreamIndex'] = subindex else: # Direct paths enabled scenario or no external subtitles set postdata[ 'SubtitleStreamIndex'] = indexSubs + audioTracks + 1 else: postdata['SubtitleStreamIndex'] = "" # Post playback to server # log("Sending POST play started: %s." % postdata, 2) # self.doUtils(url, postBody=postdata, type="POST") # Ensure we do have a runtime try: runtime = int(runtime) except ValueError: try: runtime = self.getTotalTime() log.error("Runtime is missing, Kodi runtime: %s" % runtime) except: log.error('Could not get kodi runtime, setting to zero') runtime = 0 with plexdb.Get_Plex_DB() as plex_db: plex_dbitem = plex_db.getItem_byId(itemId) try: fileid = plex_dbitem[1] except TypeError: log.info("Could not find fileid in plex db.") fileid = None # Save data map for updates and position calls data = { 'runtime': runtime, 'item_id': itemId, 'refresh_id': refresh_id, 'currentfile': currentFile, 'AudioStreamIndex': postdata['AudioStreamIndex'], 'SubtitleStreamIndex': postdata['SubtitleStreamIndex'], 'playmethod': playMethod, 'Type': itemType, 'currentPosition': int(seekTime), 'fileid': fileid, 'itemType': itemType, 'playcount': playcount } self.played_info[currentFile] = data log.info("ADDING_FILE: %s" % data) # log some playback stats '''if(itemType != None):
def PlayBackStart(self, data): """ Called whenever a playback is started """ log = self.logMsg window = utils.window # Get currently playing file - can take a while. Will be utf-8! try: currentFile = self.xbmcplayer.getPlayingFile() except: currentFile = None count = 0 while currentFile is None: xbmc.sleep(100) try: currentFile = self.xbmcplayer.getPlayingFile() except: pass if count == 50: log("No current File - Cancelling OnPlayBackStart...", -1) return else: count += 1 # Just to be on the safe side currentFile = utils.tryDecode(currentFile) log("Currently playing file is: %s" % currentFile, 1) # Get the type of media we're playing try: typus = data['item']['type'] except (TypeError, KeyError): log("Item is invalid for PMS playstate update.", 0) return log("Playing itemtype is (or appears to be): %s" % typus, 1) # Try to get a Kodi ID # If PKC was used - native paths, not direct paths plexid = utils.window('emby_%s.itemid' % utils.tryEncode(currentFile)) # Get rid of the '' if the window property was not set plexid = None if not plexid else plexid kodiid = None if plexid is None: log('Did not get Plex id from window properties', 1) try: kodiid = data['item']['id'] except (TypeError, KeyError): log('Did not get a Kodi id from Kodi, darn', 1) # For direct paths, if we're not streaming something # When using Widgets, Kodi doesn't tell us shit so we need this hack if (kodiid is None and plexid is None and typus != 'song' and not currentFile.startswith('http')): try: filename = currentFile.rsplit('/', 1)[1] path = currentFile.rsplit('/', 1)[0] + '/' except IndexError: filename = currentFile.rsplit('\\', 1)[1] path = currentFile.rsplit('\\', 1)[0] + '\\' log('Trying to figure out playing item from filename: %s and ' 'path: %s' % (filename, path), 1) with kodidb.GetKodiDB('video') as kodi_db: try: kodiid, typus = kodi_db.getIdFromFilename(filename, path) except TypeError: log('Aborting playback report', 1) return if plexid is None: # Get Plex' item id with embydb.GetEmbyDB() as emby_db: emby_dbitem = emby_db.getItem_byKodiId(kodiid, typus) try: plexid = emby_dbitem[0] except TypeError: log("No Plex id returned for kodiid %s" % kodiid, 1) log('Aborting playback report', 1) return log("Found Plex id %s for Kodi id %s for type %s" % (plexid, kodiid, typus), 1) # Set some stuff if Kodi initiated playback if ((utils.settings('useDirectPaths') == "1" and not typus == "song") or (typus == "song" and utils.settings('enableMusic') == "true")): if self.StartDirectPath(plexid, typus, utils.tryEncode(currentFile)) is False: log('Could not initiate monitoring; aborting', -1) return # Save currentFile for cleanup later and to be able to access refs window('plex_lastPlayedFiled', value=currentFile) window('Plex_currently_playing_itemid', value=plexid) window("emby_%s.itemid" % utils.tryEncode(currentFile), value=plexid) log('Finish playback startup', 1)
def single_urlencode(self, text): text = urllib.urlencode({'blahblahblah': utils.tryEncode(text)}) #urlencode needs a utf- string text = text[13:] return utils.tryDecode(text) #return the result again as unicode
import embydb_functions as embydb import kodidb_functions as kodidb import musicutils as musicutils import PlexFunctions as PF import PlexAPI def logMsg(msg, lvl=1): utils.logMsg("%s %s" % ("PlexKodiConnect", "Contextmenu"), msg, lvl) #Kodi contextmenu item to configure the emby settings #for now used to set ratings but can later be used to sync individual items etc. if __name__ == '__main__': itemid = utils.tryDecode(xbmc.getInfoLabel("ListItem.DBID")) itemtype = utils.tryDecode(xbmc.getInfoLabel("ListItem.DBTYPE")) emby = embyserver.Read_EmbyServer() plexid = "" if not itemtype and xbmc.getCondVisibility("Container.Content(albums)"): itemtype = "album" if not itemtype and xbmc.getCondVisibility("Container.Content(artists)"): itemtype = "artist" if not itemtype and xbmc.getCondVisibility("Container.Content(songs)"): itemtype = "song" if not itemtype and xbmc.getCondVisibility("Container.Content(pictures)"): itemtype = "picture" if (not itemid or itemid
def play(self, itemid, dbid=None): item = self.item # Hack to get only existing entry in PMS response for THIS instance of # playbackutils :-) self.API = PlexAPI.API(item[0]) API = self.API listitem = xbmcgui.ListItem() playutils = putils.PlayUtils(item[0]) log.info("Play called.") playurl = playutils.getPlayUrl() if not playurl: return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) if dbid in (None, '999999999', 'plexnode'): # Item is not in Kodi database, is a trailer or plex redirect # e.g. plex.tv watch later API.CreateListItemFromPlexItem(listitem) self.setArtwork(listitem) if dbid == 'plexnode': # Need to get yet another xml to get final url window('emby_%s.playmethod' % playurl, clear=True) xml = downloadutils.DownloadUtils().downloadUrl( '{server}%s' % item[0][0][0].attrib.get('key')) if xml in (None, 401): log.error('Could not download %s' % item[0][0][0].attrib.get('key')) return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) playurl = tryEncode(xml[0].attrib.get('key')) window('emby_%s.playmethod' % playurl, value='DirectStream') playmethod = window('emby_%s.playmethod' % playurl) if playmethod == "Transcode": window('emby_%s.playmethod' % playurl, clear=True) playurl = tryEncode( playutils.audioSubsPref(listitem, tryDecode(playurl))) window('emby_%s.playmethod' % playurl, "Transcode") listitem.setPath(playurl) self.setProperties(playurl, listitem) return xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem) ############### ORGANIZE CURRENT PLAYLIST ################ homeScreen = xbmc.getCondVisibility('Window.IsActive(home)') kodiPl = self.pl.playlist # Can return -1 startPos = max(kodiPl.getposition(), 0) sizePlaylist = kodiPl.size() self.currentPosition = startPos propertiesPlayback = window('plex_playbackProps') == "true" introsPlaylist = False dummyPlaylist = False log.info("Playlist start position: %s" % startPos) log.info("Playlist plugin position: %s" % self.currentPosition) log.info("Playlist size: %s" % sizePlaylist) ############### RESUME POINT ################ seektime, runtime = API.getRuntime() # We need to ensure we add the intro and additional parts only once. # Otherwise we get a loop. if not propertiesPlayback: window('plex_playbackProps', value="true") log.info("Setting up properties in playlist.") if (not homeScreen and not seektime and window('plex_customplaylist') != "true"): log.debug("Adding dummy file to playlist.") dummyPlaylist = True kodiPl.add(playurl, listitem, index=startPos) # Remove the original item from playlist self.pl.removefromPlaylist(startPos + 1) # Readd the original item to playlist - via jsonrpc so we have full metadata self.pl.insertintoPlaylist( self.currentPosition + 1, dbid, PF.GetKodiTypeFromPlex(API.getType())) self.currentPosition += 1 ############### -- CHECK FOR INTROS ################ if (settings('enableCinema') == "true" and not seektime): # if we have any play them when the movie/show is not being resumed xml = PF.GetPlexPlaylist(itemid, item.attrib.get('librarySectionUUID'), mediatype=API.getType()) introsPlaylist = self.AddTrailers(xml) ############### -- ADD MAIN ITEM ONLY FOR HOMESCREEN ############## if homeScreen and not seektime and not sizePlaylist: # Extend our current playlist with the actual item to play # only if there's no playlist first log.info("Adding main item to playlist.") self.pl.addtoPlaylist(dbid, PF.GetKodiTypeFromPlex(API.getType())) # Ensure that additional parts are played after the main item self.currentPosition += 1 ############### -- CHECK FOR ADDITIONAL PARTS ################ if len(item[0][0]) > 1: # Only add to the playlist after intros have played for counter, part in enumerate(item[0][0]): # Never add first part if counter == 0: continue # Set listitem and properties for each additional parts API.setPartNumber(counter) additionalListItem = xbmcgui.ListItem() additionalPlayurl = playutils.getPlayUrl( partNumber=counter) log.debug("Adding additional part: %s" % counter) self.setProperties(additionalPlayurl, additionalListItem) self.setArtwork(additionalListItem) # NEW to Plex API.CreateListItemFromPlexItem(additionalListItem) kodiPl.add(additionalPlayurl, additionalListItem, index=self.currentPosition) self.pl.verifyPlaylist() self.currentPosition += 1 API.setPartNumber = 0 if dummyPlaylist: # Added a dummy file to the playlist, # because the first item is going to fail automatically. log.info("Processed as a playlist. First item is skipped.") return xbmcplugin.setResolvedUrl(int(sys.argv[1]), False, listitem) # We just skipped adding properties. Reset flag for next time. elif propertiesPlayback: log.debug("Resetting properties playback flag.") window('plex_playbackProps', clear=True) #self.pl.verifyPlaylist() ########## SETUP MAIN ITEM ########## # For transcoding only, ask for audio/subs pref if window('emby_%s.playmethod' % playurl) == "Transcode": window('emby_%s.playmethod' % playurl, clear=True) playurl = tryEncode( playutils.audioSubsPref(listitem, tryDecode(playurl))) window('emby_%s.playmethod' % playurl, value="Transcode") listitem.setPath(playurl) self.setProperties(playurl, listitem) ############### PLAYBACK ################ if homeScreen and seektime and window('plex_customplaylist') != "true": log.info("Play as a widget item.") API.CreateListItemFromPlexItem(listitem) xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem) elif ((introsPlaylist and window('plex_customplaylist') == "true") or (homeScreen and not sizePlaylist)): # Playlist was created just now, play it. log.info("Play playlist.") xbmcplugin.endOfDirectory(int(sys.argv[1]), True, False, False) xbmc.Player().play(kodiPl, startpos=startPos) else: log.info("Play as a regular item.") xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem)
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 = "100" mediatypes = { 'movie': 'movies', 'show': 'tvshows', 'photo': 'photos', 'homevideo': 'homevideos', 'musicvideos': 'musicvideos' } mediatype = mediatypes[mediatype] window = utils.window if viewtype == "mixed": dirname = "%s-%s" % (viewid, mediatype) else: dirname = viewid path = utils.tryDecode( xbmc.translatePath("special://profile/library/video/")) nodepath = utils.tryDecode( xbmc.translatePath("special://profile/library/video/Plex-%s/" % dirname)) # Verify the video directory # KODI BUG # Kodi caches the result of exists for directories # so try creating a file if utils.IfExists(path) is False: shutil.copytree( src=utils.tryDecode( xbmc.translatePath("special://xbmc/system/library/video")), dst=utils.tryDecode( xbmc.translatePath("special://profile/library/video"))) # Create the node directory if mediatype != "photos": if utils.IfExists(nodepath) is False: # folder does not exist yet self.logMsg('Creating folder %s' % nodepath, 1) xbmcvfs.mkdirs(utils.tryEncode(nodepath)) if delete: dirs, files = xbmcvfs.listdir(utils.tryEncode(nodepath)) for file in files: xbmcvfs.delete( utils.tryEncode( (nodepath + utils.tryDecode(file)))) self.logMsg("Sucessfully removed videonode: %s." % tagname, 1) return # 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/?id=%s&mode=getsubfolders" % indexnumber 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: utils.indent(root) except: pass etree.ElementTree(root).write(nodeXML) 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" } 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, }, 'tvshows': { '1': tagname, # '2': 30170, '3': 30174, # '4': 30171, # '5': 30178, # '7': 30179, '9': 135, '10': 30227, # '11': 30230, '12': 39500, }, 'homevideos': { '1': tagname, '2': 30251, '11': 30253 }, 'photos': { '1': tagname, '2': 30252, '8': 30255, '11': 30254 }, 'musicvideos': { '1': tagname, '2': 30256, '4': 30257, '6': 30258 } } # 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" } 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 = utils.language(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 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 self.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' else: path = "library://video/Plex-%s/%s_%s.xml" % (dirname, viewid, nodetype) if mediatype == "photos": windowpath = "ActivateWindow(Pictures,%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 xbmcvfs.exists(utils.tryEncode(nodeXML)): # Don't recreate xml if already exists continue # Create the root if (nodetype in ("nextepisodes", "ondeck", 'recentepisodes') 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 utils.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: utils.indent(root) except: pass etree.ElementTree(root).write(nodeXML)
def PlayBackStart(self, data): """ Called whenever a playback is started """ # Get currently playing file - can take a while. Will be utf-8! try: currentFile = self.xbmcplayer.getPlayingFile() except: currentFile = None count = 0 while currentFile is None: xbmc.sleep(100) try: currentFile = self.xbmcplayer.getPlayingFile() except: pass if count == 50: log.info("No current File, cancel OnPlayBackStart...") return else: count += 1 # Just to be on the safe side currentFile = tryDecode(currentFile) log.debug("Currently playing file is: %s" % currentFile) # Get the type of media we're playing try: typus = data['item']['type'] except (TypeError, KeyError): log.info("Item is invalid for PMS playstate update.") return log.debug("Playing itemtype is (or appears to be): %s" % typus) # Try to get a Kodi ID # If PKC was used - native paths, not direct paths plexid = window('emby_%s.itemid' % tryEncode(currentFile)) # Get rid of the '' if the window property was not set plexid = None if not plexid else plexid kodiid = None if plexid is None: log.debug('Did not get Plex id from window properties') try: kodiid = data['item']['id'] except (TypeError, KeyError): log.debug('Did not get a Kodi id from Kodi, darn') # For direct paths, if we're not streaming something # When using Widgets, Kodi doesn't tell us shit so we need this hack if (kodiid is None and plexid is None and typus != 'song' and not currentFile.startswith('http')): try: filename = currentFile.rsplit('/', 1)[1] path = currentFile.rsplit('/', 1)[0] + '/' except IndexError: filename = currentFile.rsplit('\\', 1)[1] path = currentFile.rsplit('\\', 1)[0] + '\\' log.debug('Trying to figure out playing item from filename: %s ' 'and path: %s' % (filename, path)) with kodidb.GetKodiDB('video') as kodi_db: try: kodiid, typus = kodi_db.getIdFromFilename(filename, path) except TypeError: log.info('Abort playback report, could not id kodi item') return if plexid is None: # Get Plex' item id with embydb.GetEmbyDB() as emby_db: emby_dbitem = emby_db.getItem_byKodiId(kodiid, typus) try: plexid = emby_dbitem[0] except TypeError: log.info("No Plex id returned for kodiid %s. Aborting playback" " report" % kodiid) return log.debug("Found Plex id %s for Kodi id %s for type %s" % (plexid, kodiid, typus)) # Set some stuff if Kodi initiated playback if ((settings('useDirectPaths') == "1" and not typus == "song") or (typus == "song" and settings('enableMusic') == "true")): if self.StartDirectPath(plexid, typus, tryEncode(currentFile)) is False: log.error('Could not initiate monitoring; aborting') return # Save currentFile for cleanup later and to be able to access refs window('plex_lastPlayedFiled', value=currentFile) window('plex_currently_playing_itemid', value=plexid) window("emby_%s.itemid" % tryEncode(currentFile), value=plexid) log.info('Finish playback startup')
def PlayBackStart(self, data): """ Called whenever a playback is started """ log = self.logMsg window = utils.window # Get currently playing file - can take a while. Will be utf-8! try: currentFile = self.xbmcplayer.getPlayingFile() except: currentFile = None count = 0 while currentFile is None: xbmc.sleep(100) try: currentFile = self.xbmcplayer.getPlayingFile() except: pass if count == 50: log("No current File - Cancelling OnPlayBackStart...", -1) return else: count += 1 log("Currently playing file is: %s" % utils.tryDecode(currentFile), 1) # Try to get a Kodi ID item = data.get('item') try: type = item['type'] except: log("Item is invalid for PMS playstate update.", 0) return try: kodiid = item['id'] except (KeyError, TypeError): itemType = window("emby_%s.type" % currentFile) log("No kodi id passed. Playing itemtype is: %s" % itemType, 1) if itemType in ('movie', 'episode'): # Window was setup by PKC and is NOT a trailer ('clip') with kodidb.GetKodiDB('video') as kodi_db: kodiid = kodi_db.getIdFromTitle(data.get('item')) if kodiid is None: log("Skip playstate update. No unique Kodi title found" " for %s" % data.get('item'), 0) return else: log("Item is invalid for PMS playstate update.", 0) return # Get Plex' item id with embydb.GetEmbyDB() as emby_db: emby_dbitem = emby_db.getItem_byKodiId(kodiid, type) try: plexid = emby_dbitem[0] except TypeError: log("No Plex id returned for kodiid %s" % kodiid, 0) return log("Found Plex id %s for Kodi id %s" % (plexid, kodiid), 1) # Set some stuff if Kodi initiated playback if ((utils.settings('useDirectPaths') == "1" and not type == "song") or (type == "song" and utils.settings('enableMusic') == "true")): if self.StartDirectPath(plexid, type, currentFile) is False: log('Could not initiate monitoring; aborting', -1) return # Save currentFile for cleanup later and to be able to access refs window('plex_lastPlayedFiled', value=utils.tryDecode(currentFile)) window('Plex_currently_playing_itemid', value=plexid) window("emby_%s.itemid" % currentFile, value=plexid) log('Finish playback startup', 1)
def onPlayBackStarted(self): """ Window values need to have been set in Kodimonitor.py """ window = utils.window # Will be called when xbmc starts playing a file self.stopAll() # Get current file (in utf-8!) try: currentFile = self.xbmcplayer.getPlayingFile() xbmc.sleep(300) except: currentFile = "" count = 0 while not currentFile: xbmc.sleep(100) try: currentFile = self.xbmcplayer.getPlayingFile() except: pass if count == 20: self.logMsg("Cancelling playback report...", 1) break else: count += 1 if not currentFile: self.logMsg('Error getting a currently playing file; abort ' 'reporting', -1) return # Save currentFile for cleanup later and for references self.currentFile = currentFile window('plex_lastPlayedFiled', value=utils.tryDecode(currentFile)) # We may need to wait for info to be set in kodi monitor itemId = window("emby_%s.itemid" % currentFile) count = 0 while not itemId: xbmc.sleep(200) itemId = window("emby_%s.itemid" % currentFile) # try 20 times or about 10 seconds if count == 20: self.logMsg("Could not find itemId, cancelling playback " "report!", -1) return count += 1 self.logMsg("ONPLAYBACK_STARTED: %s itemid: %s" % (utils.tryDecode(currentFile), itemId), 0) embyitem = "emby_%s" % currentFile runtime = window("%s.runtime" % embyitem) refresh_id = window("%s.refreshid" % embyitem) playMethod = window("%s.playmethod" % embyitem) itemType = window("%s.type" % embyitem) try: playcount = int(window("%s.playcount" % embyitem)) except ValueError: playcount = 0 window('emby_skipWatched%s' % itemId, value="true") self.logMsg("Playing itemtype is: %s" % itemType, 1) customseek = window('plex_customplaylist.seektime') if (window('plex_customplaylist') == "true" and customseek): # Start at, when using custom playlist (play to Kodi from webclient) self.logMsg("Seeking to: %s" % customseek, 1) try: self.xbmcplayer.seekTime(int(customseek)) except: self.logMsg('Could not seek!', -1) window('plex_customplaylist.seektime', clear=True) try: seekTime = self.xbmcplayer.getTime() except RuntimeError: self.logMsg('Could not get current seektime from xbmc player', -1) seekTime = 0 # Get playback volume volume_query = { "jsonrpc": "2.0", "id": 1, "method": "Application.GetProperties", "params": { "properties": ["volume", "muted"] } } result = xbmc.executeJSONRPC(json.dumps(volume_query)) result = json.loads(result) result = result.get('result') volume = result.get('volume') muted = result.get('muted') # Postdata structure to send to Emby server url = "{server}/:/timeline?" postdata = { 'QueueableMediaTypes': "Video", 'CanSeek': True, 'ItemId': itemId, 'MediaSourceId': itemId, 'PlayMethod': playMethod, 'VolumeLevel': volume, 'PositionTicks': int(seekTime * 10000000), 'IsMuted': muted } # Get the current audio track and subtitles if playMethod == "Transcode": # property set in PlayUtils.py postdata['AudioStreamIndex'] = window("%sAudioStreamIndex" % currentFile) postdata['SubtitleStreamIndex'] = window("%sSubtitleStreamIndex" % currentFile) else: # Get the current kodi audio and subtitles and convert to Emby equivalent tracks_query = { "jsonrpc": "2.0", "id": 1, "method": "Player.GetProperties", "params": { "playerid": 1, "properties": ["currentsubtitle","currentaudiostream","subtitleenabled"] } } result = xbmc.executeJSONRPC(json.dumps(tracks_query)) result = json.loads(result) result = result.get('result') try: # Audio tracks indexAudio = result['currentaudiostream']['index'] except (KeyError, TypeError): indexAudio = 0 try: # Subtitles tracks indexSubs = result['currentsubtitle']['index'] except (KeyError, TypeError): indexSubs = 0 try: # If subtitles are enabled subsEnabled = result['subtitleenabled'] except (KeyError, TypeError): subsEnabled = "" # Postdata for the audio postdata['AudioStreamIndex'] = indexAudio + 1 # Postdata for the subtitles if subsEnabled and len(xbmc.Player().getAvailableSubtitleStreams()) > 0: # Number of audiotracks to help get Emby Index audioTracks = len(xbmc.Player().getAvailableAudioStreams()) mapping = window("%s.indexMapping" % embyitem) if mapping: # Set in playbackutils.py self.logMsg("Mapping for external subtitles index: %s" % mapping, 2) externalIndex = json.loads(mapping) if externalIndex.get(str(indexSubs)): # If the current subtitle is in the mapping postdata['SubtitleStreamIndex'] = externalIndex[str(indexSubs)] else: # Internal subtitle currently selected subindex = indexSubs - len(externalIndex) + audioTracks + 1 postdata['SubtitleStreamIndex'] = subindex else: # Direct paths enabled scenario or no external subtitles set postdata['SubtitleStreamIndex'] = indexSubs + audioTracks + 1 else: postdata['SubtitleStreamIndex'] = "" # Post playback to server # log("Sending POST play started: %s." % postdata, 2) # self.doUtils(url, postBody=postdata, type="POST") # Ensure we do have a runtime try: runtime = int(runtime) except ValueError: try: runtime = self.xbmcplayer.getTotalTime() self.logMsg("Runtime is missing, Kodi runtime: %s" % runtime, 1) except: self.logMsg('Could not get kodi runtime, setting to zero', -1) runtime = 0 playQueueVersion = window('playQueueVersion') playQueueID = window('playQueueID') playQueueItemID = window('plex_%s.playQueueItemID' % currentFile) with embydb.GetEmbyDB() as emby_db: emby_dbitem = emby_db.getItem_byId(itemId) try: fileid = emby_dbitem[1] except TypeError: self.logMsg("Could not find fileid in plex db.", 1) fileid = None # Save data map for updates and position calls data = { 'playQueueVersion': playQueueVersion, 'playQueueID': playQueueID, 'playQueueItemID': playQueueItemID, 'runtime': runtime, 'item_id': itemId, 'refresh_id': refresh_id, 'currentfile': currentFile, 'AudioStreamIndex': postdata['AudioStreamIndex'], 'SubtitleStreamIndex': postdata['SubtitleStreamIndex'], 'playmethod': playMethod, 'Type': itemType, 'currentPosition': int(seekTime) * 1000, 'fileid': fileid, 'itemType': itemType, 'playcount': playcount } self.played_info[currentFile] = data self.logMsg("ADDING_FILE: %s" % data, 1) # log some playback stats '''if(itemType != None):
elif mode == "texturecache": import artwork artwork.Artwork().FullTextureCacheSync() else: entrypoint.doMainListing() if ( __name__ == "__main__" ): xbmc.log('plugin.video.plexkodiconnect started') if enableProfiling: import cProfile import pstats import random from time import gmtime, strftime addonid = utils.tryDecode(addon_.getAddonInfo('id')) datapath = os.path.join(utils.tryDecode(xbmc.translatePath( "special://profile/" )), "addon_data", addonid ) filename = os.path.join( datapath, strftime( "%Y%m%d%H%M%S",gmtime() ) + "-" + str( random.randrange(0,100000) ) + ".log" ) cProfile.run( 'Main()', filename ) stream = open( filename + ".txt", 'w') p = pstats.Stats( filename, stream = stream ) p.sort_stats( "cumulative" ) p.print_stats() else: Main() xbmc.log('plugin.video.plexkodiconnect stopped')
import embydb_functions as embydb import kodidb_functions as kodidb import musicutils as musicutils import PlexFunctions as PF import PlexAPI def logMsg(msg, lvl=1): utils.logMsg("%s %s" % ("PlexKodiConnect", "Contextmenu"), msg, lvl) #Kodi contextmenu item to configure the emby settings #for now used to set ratings but can later be used to sync individual items etc. if __name__ == '__main__': itemid = utils.tryDecode(xbmc.getInfoLabel("ListItem.DBID")) itemtype = utils.tryDecode(xbmc.getInfoLabel("ListItem.DBTYPE")) emby = embyserver.Read_EmbyServer() plexid = "" if not itemtype and xbmc.getCondVisibility("Container.Content(albums)"): itemtype = "album" if not itemtype and xbmc.getCondVisibility("Container.Content(artists)"): itemtype = "artist" if not itemtype and xbmc.getCondVisibility("Container.Content(songs)"): itemtype = "song" if not itemtype and xbmc.getCondVisibility("Container.Content(pictures)"): itemtype = "picture" if (not itemid or itemid == "-1") and xbmc.getInfoLabel("ListItem.Property(plexid)"): plexid = xbmc.getInfoLabel("ListItem.Property(plexid)") else: with embydb.GetEmbyDB() as emby_db: item = emby_db.getItem_byKodiId(itemid, itemtype)
def FullTextureCacheSync(self): # This method will sync all Kodi artwork to textures13.db # and cache them locally. This takes diskspace! import xbmcaddon string = xbmcaddon.Addon().getLocalizedString if not xbmcgui.Dialog().yesno( "Image Texture Cache", string(39250)): return self.logMsg("Doing Image Cache Sync", 1) dialog = xbmcgui.DialogProgress() dialog.create("Emby for Kodi", "Image Cache Sync") # ask to rest all existing or not if xbmcgui.Dialog().yesno( "Image Texture Cache", string(39251), ""): self.logMsg("Resetting all cache data first", 1) # Remove all existing textures first path = utils.tryDecode(xbmc.translatePath("special://thumbnails/")) if utils.IfExists(path): allDirs, allFiles = xbmcvfs.listdir(path) for dir in allDirs: allDirs, allFiles = xbmcvfs.listdir(path+dir) for file in allFiles: if os.path.supports_unicode_filenames: xbmcvfs.delete(os.path.join( path + utils.tryDecode(dir), utils.tryDecode(file))) else: xbmcvfs.delete(os.path.join( utils.tryEncode(path) + dir, file)) # remove all existing data from texture DB textureconnection = utils.kodiSQL('texture') texturecursor = textureconnection.cursor() texturecursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') rows = texturecursor.fetchall() for row in rows: tableName = row[0] if(tableName != "version"): texturecursor.execute("DELETE FROM " + tableName) textureconnection.commit() texturecursor.close() # Cache all entries in video DB connection = utils.kodiSQL('video') cursor = connection.cursor() cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") # dont include actors result = cursor.fetchall() total = len(result) count = 1 percentage = 0 self.logMsg("Image cache sync about to process " + str(total) + " images", 1) for url in result: if dialog.iscanceled(): break percentage = int((float(count) / float(total))*100) textMessage = str(count) + " of " + str(total) + " (" + str(len(self.imageCacheThreads)) + ")" dialog.update(percentage, "Updating Image Cache: " + textMessage) self.CacheTexture(url[0]) count += 1 cursor.close() # Cache all entries in music DB connection = utils.kodiSQL('music') cursor = connection.cursor() cursor.execute("SELECT url FROM art") result = cursor.fetchall() total = len(result) count = 1 percentage = 0 self.logMsg("Image cache sync about to process " + str(total) + " images", 1) for url in result: if dialog.iscanceled(): break percentage = int((float(count) / float(total))*100) textMessage = str(count) + " of " + str(total) dialog.update(percentage, "Updating Image Cache: " + textMessage) self.CacheTexture(url[0]) count += 1 cursor.close() dialog.update(100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads))) self.logMsg("Waiting for all threads to exit", 1) while len(self.imageCacheThreads) > 0: for thread in self.imageCacheThreads: if thread.isFinished: self.imageCacheThreads.remove(thread) dialog.update(100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads))) self.logMsg("Waiting for all threads to exit: " + str(len(self.imageCacheThreads)), 1) xbmc.sleep(500) dialog.close()
def PlayBackStart(self, data): """ Called whenever a playback is started """ # Get currently playing file - can take a while. Will be utf-8! try: currentFile = self.xbmcplayer.getPlayingFile() except: currentFile = None count = 0 while currentFile is None: sleep(100) try: currentFile = self.xbmcplayer.getPlayingFile() except: pass if count == 50: log.info("No current File, cancel OnPlayBackStart...") return else: count += 1 # Just to be on the safe side currentFile = tryDecode(currentFile) log.debug("Currently playing file is: %s" % currentFile) # Get the type of media we're playing try: typus = data['item']['type'] except (TypeError, KeyError): log.info("Item is invalid for PMS playstate update.") return log.debug("Playing itemtype is (or appears to be): %s" % typus) # Try to get a Kodi ID # If PKC was used - native paths, not direct paths plexid = window('plex_%s.itemid' % tryEncode(currentFile)) # Get rid of the '' if the window property was not set plexid = None if not plexid else plexid kodiid = None if plexid is None: log.debug('Did not get Plex id from window properties') try: kodiid = data['item']['id'] except (TypeError, KeyError): log.debug('Did not get a Kodi id from Kodi, darn') # For direct paths, if we're not streaming something # When using Widgets, Kodi doesn't tell us shit so we need this hack if (kodiid is None and plexid is None and typus != 'song' and not currentFile.startswith('http')): (kodiid, typus) = get_kodiid_from_filename(currentFile) if kodiid is None: return if plexid is None: # Get Plex' item id with plexdb.Get_Plex_DB() as plexcursor: plex_dbitem = plexcursor.getItem_byKodiId(kodiid, typus) try: plexid = plex_dbitem[0] except TypeError: log.info("No Plex id returned for kodiid %s. Aborting playback" " report" % kodiid) return log.debug("Found Plex id %s for Kodi id %s for type %s" % (plexid, kodiid, typus)) # Set some stuff if Kodi initiated playback if ((settings('useDirectPaths') == "1" and not typus == "song") or (typus == "song" and settings('enableMusic') == "true")): if self.StartDirectPath(plexid, typus, tryEncode(currentFile)) is False: log.error('Could not initiate monitoring; aborting') return # Save currentFile for cleanup later and to be able to access refs window('plex_lastPlayedFiled', value=currentFile) window('plex_currently_playing_itemid', value=plexid) window("plex_%s.itemid" % tryEncode(currentFile), value=plexid) log.info('Finish playback startup')
def restoreCacheDirectories(): LOG.info("Restoring cache directories...") paths = ("","0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f","Video","plex") for p in paths: makedirs(tryDecode(translatePath("special://thumbnails/%s" % p)))
def FullTextureCacheSync(self): # This method will sync all Kodi artwork to textures13.db # and cache them locally. This takes diskspace! import xbmcaddon string = xbmcaddon.Addon().getLocalizedString if not xbmcgui.Dialog().yesno("Image Texture Cache", string(39250)): return self.logMsg("Doing Image Cache Sync", 1) dialog = xbmcgui.DialogProgress() dialog.create("PlexKodiConnect", "Image Cache Sync") # ask to rest all existing or not if xbmcgui.Dialog().yesno("Image Texture Cache", string(39251), ""): self.logMsg("Resetting all cache data first", 1) # Remove all existing textures first path = utils.tryDecode(xbmc.translatePath("special://thumbnails/")) if utils.IfExists(path): allDirs, allFiles = xbmcvfs.listdir(path) for dir in allDirs: allDirs, allFiles = xbmcvfs.listdir(path + dir) for file in allFiles: if os.path.supports_unicode_filenames: xbmcvfs.delete( os.path.join(path + utils.tryDecode(dir), utils.tryDecode(file))) else: xbmcvfs.delete( os.path.join( utils.tryEncode(path) + dir, file)) # remove all existing data from texture DB textureconnection = utils.kodiSQL('texture') texturecursor = textureconnection.cursor() texturecursor.execute( 'SELECT tbl_name FROM sqlite_master WHERE type="table"') rows = texturecursor.fetchall() for row in rows: tableName = row[0] if (tableName != "version"): texturecursor.execute("DELETE FROM " + tableName) textureconnection.commit() texturecursor.close() # Cache all entries in video DB connection = utils.kodiSQL('video') cursor = connection.cursor() cursor.execute("SELECT url FROM art WHERE media_type != 'actor'" ) # dont include actors result = cursor.fetchall() total = len(result) count = 1 percentage = 0 self.logMsg( "Image cache sync about to process " + str(total) + " images", 1) for url in result: if dialog.iscanceled(): break percentage = int((float(count) / float(total)) * 100) textMessage = str(count) + " of " + str(total) + " (" + str( len(self.imageCacheThreads)) + ")" dialog.update(percentage, "Updating Image Cache: " + textMessage) self.CacheTexture(url[0]) count += 1 cursor.close() # Cache all entries in music DB connection = utils.kodiSQL('music') cursor = connection.cursor() cursor.execute("SELECT url FROM art") result = cursor.fetchall() total = len(result) count = 1 percentage = 0 self.logMsg( "Image cache sync about to process " + str(total) + " images", 1) for url in result: if dialog.iscanceled(): break percentage = int((float(count) / float(total)) * 100) textMessage = str(count) + " of " + str(total) dialog.update(percentage, "Updating Image Cache: " + textMessage) self.CacheTexture(url[0]) count += 1 cursor.close() dialog.update( 100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads))) self.logMsg("Waiting for all threads to exit", 1) while len(self.imageCacheThreads) > 0: for thread in self.imageCacheThreads: if thread.isFinished: self.imageCacheThreads.remove(thread) dialog.update( 100, "Waiting for all threads to exit: " + str(len(self.imageCacheThreads))) self.logMsg( "Waiting for all threads to exit: " + str(len(self.imageCacheThreads)), 1) xbmc.sleep(500) dialog.close()
def fullTextureCacheSync(self): """ This method will sync all Kodi artwork to textures13.db and cache them locally. This takes diskspace! """ if not dialog('yesno', "Image Texture Cache", lang(39250)): return log.info("Doing Image Cache Sync") # ask to rest all existing or not if dialog('yesno', "Image Texture Cache", lang(39251)): log.info("Resetting all cache data first") # Remove all existing textures first path = tryDecode(translatePath("special://thumbnails/")) if IfExists(path): allDirs, allFiles = listdir(path) for dir in allDirs: allDirs, allFiles = listdir(path+dir) for file in allFiles: if os_path.supports_unicode_filenames: delete(os_path.join( path + tryDecode(dir), tryDecode(file))) else: delete(os_path.join( tryEncode(path) + dir, file)) # remove all existing data from texture DB connection = kodiSQL('texture') cursor = connection.cursor() cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type="table"') rows = cursor.fetchall() for row in rows: tableName = row[0] if tableName != "version": cursor.execute("DELETE FROM " + tableName) connection.commit() connection.close() # Cache all entries in video DB connection = kodiSQL('video') cursor = connection.cursor() # dont include actors cursor.execute("SELECT url FROM art WHERE media_type != 'actor'") result = cursor.fetchall() total = len(result) log.info("Image cache sync about to process %s video images" % total) connection.close() for url in result: self.cacheTexture(url[0]) # Cache all entries in music DB connection = kodiSQL('music') cursor = connection.cursor() cursor.execute("SELECT url FROM art") result = cursor.fetchall() total = len(result) log.info("Image cache sync about to process %s music images" % total) connection.close() for url in result: self.cacheTexture(url[0])
def authenticate(self): log = self.logMsg log('Authenticating user', 1) lang = utils.language window = utils.window settings = utils.settings dialog = xbmcgui.Dialog() # Give attempts at entering password / selecting user if self.retry >= 2: log("Too many retries to login.", -1) window('plex_serverStatus', value="Stop") dialog.ok(lang(33001), lang(39023)) xbmc.executebuiltin( 'Addon.OpenSettings(plugin.video.plexkodiconnect)') return False # Get /profile/addon_data addondir = utils.tryDecode(xbmc.translatePath( self.addon.getAddonInfo('profile'))) hasSettings = xbmcvfs.exists("%ssettings.xml" % addondir) # If there's no settings.xml if not hasSettings: log("Error, no settings.xml found.", -1) self.auth = False return False server = self.getServer() # If there is no server we can connect to if not server: log("Missing server information.", 0) self.auth = False return False # If there is a username in the settings, try authenticating username = settings('username') userId = settings('userid') usertoken = settings('accessToken') enforceLogin = settings('enforceUserLogin') # Found a user in the settings, try to authenticate if username and enforceLogin == 'false': log('Trying to authenticate with old settings', 0) if self.loadCurrUser(username, userId, usertoken, authenticated=False): # SUCCESS: loaded a user from the settings return True else: # Failed to use the settings - delete them! log("Failed to use the settings credentials. Deleting them", 1) settings('username', value='') settings('userid', value='') settings('accessToken', value='') plx = PlexAPI.PlexAPI() # Could not use settings - try to get Plex user list from plex.tv plextoken = settings('plexToken') if plextoken: log("Trying to connect to plex.tv to get a user list", 0) userInfo = plx.ChoosePlexHomeUser(plextoken) if userInfo is False: # FAILURE: Something went wrong, try again self.auth = True self.retry += 1 return False username = userInfo['username'] userId = userInfo['userid'] usertoken = userInfo['token'] else: log("Trying to authenticate without a token", 0) username = '' userId = '' usertoken = '' if self.loadCurrUser(username, userId, usertoken, authenticated=False): # SUCCESS: loaded a user from the settings return True else: # FAILUR: Something went wrong, try again self.auth = True self.retry += 1 return False
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 = "100" 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 path = tryDecode(xbmc.translatePath( "special://profile/library/video/")) nodepath = tryDecode(xbmc.translatePath( "special://profile/library/video/Plex-%s/" % dirname)) # Verify the video directory # KODI BUG # Kodi caches the result of exists for directories # so try creating a file if IfExists(path) is False: shutil.copytree( src=tryDecode(xbmc.translatePath( "special://xbmc/system/library/video")), dst=tryDecode(xbmc.translatePath( "special://profile/library/video"))) # Create the node directory if mediatype != "photos": if IfExists(nodepath) is False: # folder does not exist yet log.debug('Creating folder %s' % nodepath) xbmcvfs.mkdirs(tryEncode(nodepath)) if delete: dirs, files = xbmcvfs.listdir(tryEncode(nodepath)) for file in files: xbmcvfs.delete(tryEncode( (nodepath + tryDecode(file)))) log.info("Sucessfully removed videonode: %s." % tagname) return # 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/?id=%s&mode=getsubfolders" % indexnumber 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) 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" } 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, }, 'tvshows': { '1': tagname, # '2': 30170, '3': 30174, # '4': 30171, # '5': 30178, # '7': 30179, '9': 135, '10': 30227, # '11': 30230, '12': 39500, }, 'homevideos': { '1': tagname, '2': 30251, '11': 30253 }, 'photos': { '1': tagname, '2': 30252, '8': 30255, '11': 30254 }, 'musicvideos': { '1': tagname, '2': 30256, '4': 30257, '6': 30258 } } # 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" } 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 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 self.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' else: path = "library://video/Plex-%s/%s_%s.xml" % (dirname, viewid, nodetype) if mediatype == "photos": windowpath = "ActivateWindow(Pictures,%s,return)" % path else: if self.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 xbmcvfs.exists(tryEncode(nodeXML)): # Don't recreate xml if already exists continue # Create the root if (nodetype in ("nextepisodes", "ondeck", 'recentepisodes') 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)
def play(self, plex_id, kodi_id=None, plex_lib_UUID=None): """ plex_lib_UUID: xml attribute 'librarySectionUUID', needed for posting to the PMS """ log.info("Playbackutils called") item = self.xml[0] api = API(item) playqueue = self.playqueue xml = None result = Playback_Successful() listitem = ListItem() playutils = putils.PlayUtils(item) playurl = playutils.getPlayUrl() if not playurl: log.error('No playurl found, aborting') return if kodi_id in (None, 'plextrailer', 'plexnode'): # Item is not in Kodi database, is a trailer/clip or plex redirect # e.g. plex.tv watch later api.CreateListItemFromPlexItem(listitem) api.set_listitem_artwork(listitem) if kodi_id == 'plexnode': # Need to get yet another xml to get final url window('plex_%s.playmethod' % playurl, clear=True) xml = downloadutils.DownloadUtils().downloadUrl( '{server}%s' % item[0][0].attrib.get('key')) try: xml[0].attrib except (TypeError, AttributeError): log.error('Could not download %s' % item[0][0].attrib.get('key')) return playurl = tryEncode(xml[0].attrib.get('key')) window('plex_%s.playmethod' % playurl, value='DirectStream') playmethod = window('plex_%s.playmethod' % playurl) if playmethod == "Transcode": window('plex_%s.playmethod' % playurl, clear=True) playurl = tryEncode(playutils.audioSubsPref( listitem, tryDecode(playurl))) window('plex_%s.playmethod' % playurl, "Transcode") listitem.setPath(playurl) api.set_playback_win_props(playurl, listitem) result.listitem = listitem return result kodi_type = v.KODITYPE_FROM_PLEXTYPE[api.getType()] kodi_id = int(kodi_id) # ORGANIZE CURRENT PLAYLIST ################ contextmenu_play = window('plex_contextplay') == 'true' window('plex_contextplay', clear=True) homeScreen = getCondVisibility('Window.IsActive(home)') sizePlaylist = len(playqueue.items) if contextmenu_play: # Need to start with the items we're inserting here startPos = sizePlaylist else: # Can return -1 startPos = max(playqueue.kodi_pl.getposition(), 0) self.currentPosition = startPos propertiesPlayback = window('plex_playbackProps') == "true" introsPlaylist = False dummyPlaylist = False log.info("Playing from contextmenu: %s" % contextmenu_play) log.info("Playlist start position: %s" % startPos) log.info("Playlist plugin position: %s" % self.currentPosition) log.info("Playlist size: %s" % sizePlaylist) # RESUME POINT ################ seektime, runtime = api.getRuntime() if window('plex_customplaylist.seektime'): # Already got seektime, e.g. from playqueue & Plex companion seektime = int(window('plex_customplaylist.seektime')) # We need to ensure we add the intro and additional parts only once. # Otherwise we get a loop. if not propertiesPlayback: window('plex_playbackProps', value="true") log.info("Setting up properties in playlist.") # Where will the player need to start? # Do we need to get trailers? trailers = False if (api.getType() == v.PLEX_TYPE_MOVIE and not seektime and sizePlaylist < 2 and settings('enableCinema') == "true"): if settings('askCinema') == "true": trailers = xbmcgui.Dialog().yesno( lang(29999), "Play trailers?") else: trailers = True # Post to the PMS. REUSE THE PLAYQUEUE! xml = init_plex_playqueue(plex_id, plex_lib_UUID, mediatype=api.getType(), trailers=trailers) try: get_playlist_details_from_xml(playqueue, xml=xml) except KeyError: return if (not homeScreen and not seektime and sizePlaylist < 2 and window('plex_customplaylist') != "true" and not contextmenu_play): # Need to add a dummy file because the first item will fail log.debug("Adding dummy file to playlist.") dummyPlaylist = True add_listitem_to_Kodi_playlist( playqueue, startPos, xbmcgui.ListItem(), playurl, xml[0]) # Remove the original item from playlist remove_from_Kodi_playlist( playqueue, startPos+1) # Readd the original item to playlist - via jsonrpc so we have # full metadata add_item_to_kodi_playlist( playqueue, self.currentPosition+1, kodi_id=kodi_id, kodi_type=kodi_type, file=playurl) self.currentPosition += 1 # -- ADD TRAILERS ################ if trailers: for i, item in enumerate(xml): if i == len(xml) - 1: # Don't add the main movie itself break self.add_trailer(item) introsPlaylist = True # -- ADD MAIN ITEM ONLY FOR HOMESCREEN ############## if homeScreen and not seektime and not sizePlaylist: # Extend our current playlist with the actual item to play # only if there's no playlist first log.info("Adding main item to playlist.") add_item_to_kodi_playlist( playqueue, self.currentPosition, kodi_id, kodi_type) elif contextmenu_play: if window('useDirectPaths') == 'true': # Cannot add via JSON with full metadata because then we # Would be using the direct path log.debug("Adding contextmenu item for direct paths") if window('plex_%s.playmethod' % playurl) == "Transcode": window('plex_%s.playmethod' % playurl, clear=True) playurl = tryEncode(playutils.audioSubsPref( listitem, tryDecode(playurl))) window('plex_%s.playmethod' % playurl, value="Transcode") api.CreateListItemFromPlexItem(listitem) api.set_playback_win_props(playurl, listitem) api.set_listitem_artwork(listitem) add_listitem_to_Kodi_playlist( playqueue, self.currentPosition+1, convert_PKC_to_listitem(listitem), playurl, kodi_item={'id': kodi_id, 'type': kodi_type}) else: # Full metadata$ add_item_to_kodi_playlist( playqueue, self.currentPosition+1, kodi_id, kodi_type) self.currentPosition += 1 if seektime: window('plex_customplaylist.seektime', value=str(seektime)) # Ensure that additional parts are played after the main item self.currentPosition += 1 # -- CHECK FOR ADDITIONAL PARTS ################ if len(item[0]) > 1: self.add_part(item, api, kodi_id, kodi_type) if dummyPlaylist: # Added a dummy file to the playlist, # because the first item is going to fail automatically. log.info("Processed as a playlist. First item is skipped.") # Delete the item that's gonna fail! del playqueue.items[startPos] # Don't attach listitem return result # We just skipped adding properties. Reset flag for next time. elif propertiesPlayback: log.debug("Resetting properties playback flag.") window('plex_playbackProps', clear=True) # SETUP MAIN ITEM ########## # For transcoding only, ask for audio/subs pref if (window('plex_%s.playmethod' % playurl) == "Transcode" and not contextmenu_play): window('plex_%s.playmethod' % playurl, clear=True) playurl = tryEncode(playutils.audioSubsPref( listitem, tryDecode(playurl))) window('plex_%s.playmethod' % playurl, value="Transcode") listitem.setPath(playurl) api.set_playback_win_props(playurl, listitem) api.set_listitem_artwork(listitem) # PLAYBACK ################ if (homeScreen and seektime and window('plex_customplaylist') != "true" and not contextmenu_play): log.info("Play as a widget item") api.CreateListItemFromPlexItem(listitem) result.listitem = listitem return result elif ((introsPlaylist and window('plex_customplaylist') == "true") or (homeScreen and not sizePlaylist) or contextmenu_play): # Playlist was created just now, play it. # Contextmenu plays always need this log.info("Play playlist from starting position %s" % startPos) # Need a separate thread because Player won't return in time thread = Thread(target=Player().play, args=(playqueue.kodi_pl, None, False, startPos)) thread.setDaemon(True) thread.start() # Don't attach listitem return result else: log.info("Play as a regular item") result.listitem = listitem return result
elif mode == "texturecache": import artwork artwork.Artwork().FullTextureCacheSync() else: entrypoint.doMainListing() if (__name__ == "__main__"): xbmc.log('plugin.video.plexkodiconnect started') if enableProfiling: import cProfile import pstats import random from time import gmtime, strftime addonid = utils.tryDecode(addon_.getAddonInfo('id')) datapath = os.path.join( utils.tryDecode(xbmc.translatePath("special://profile/")), "addon_data", addonid) filename = os.path.join( datapath, strftime("%Y%m%d%H%M%S", gmtime()) + "-" + str(random.randrange(0, 100000)) + ".log") cProfile.run('Main()', filename) stream = open(filename + ".txt", 'w') p = pstats.Stats(filename, stream=stream) p.sort_stats("cumulative") p.print_stats()