def _doesThemeExist(self, directory): log("doesThemeExist: Checking directory: %s" % directory) # Check for custom theme directory if Settings.isThemeDirEnabled(): themeDir = os_path_join(directory, Settings.getThemeDirectory()) # Check if this directory exists if not dir_exists(themeDir): workingPath = directory # If the path currently ends in the directory separator # then we need to clear an extra one if (workingPath[-1] == os.sep) or (workingPath[-1] == os.altsep): workingPath = workingPath[:-1] # If not check to see if we have a DVD VOB if (os_path_split(workingPath)[1] == 'VIDEO_TS') or (os_path_split(workingPath)[1] == 'BDMV'): # Check the parent of the DVD Dir themeDir = os_path_split(workingPath)[0] themeDir = os_path_join(themeDir, Settings.getThemeDirectory()) directory = themeDir # check if the directory exists before searching if dir_exists(directory): # Generate the regex themeFileRegEx = Settings.getThemeFileRegEx(audioOnly=True) dirs, files = list_dir(directory) for aFile in files: m = re.search(themeFileRegEx, aFile, re.IGNORECASE) if m: log("doesThemeExist: Found match: " + aFile) return True return False
def __init__(self, extrasParent, videoType=None, title=None): log("VideoExtrasBase: Finding extras for %s" % extrasParent) self.videoType = videoType self.baseDirectory = extrasParent if self.baseDirectory.startswith("stack://"): self.baseDirectory = self.baseDirectory.split(" , ")[0] self.baseDirectory = self.baseDirectory.replace("stack://", "") # There is a problem if some-one is using windows shares with # \\SERVER\Name as when the addon gets called the first \ gets # removed, making an invalid path, so we add it back here elif self.baseDirectory.startswith("\\"): self.baseDirectory = "\\" + self.baseDirectory # Support special paths like smb:// means that we can not just call # os.path.isfile as it will return false even if it is a file # (A bit of a shame - but that's the way it is) fileExt = os.path.splitext(self.baseDirectory)[1] # If this is a file, then get it's parent directory if fileExt is not None and fileExt != "": self.baseDirectory = (os_path_split(self.baseDirectory))[0] self.filename = (os_path_split(extrasParent))[1] else: self.filename = None self.title = title log("VideoExtrasBase: Root directory: %s" % self.baseDirectory)
def _moveToThemeFolder(self, directory): log("moveToThemeFolder: path = %s" % directory) # Handle the case where we have a disk image if (os_path_split(directory)[1] == 'VIDEO_TS') or (os_path_split(directory)[1] == 'BDMV'): directory = os_path_split(directory)[0] dirs, files = list_dir(directory) for aFile in files: m = re.search(Settings.getThemeFileRegEx(directory), aFile, re.IGNORECASE) if m: srcpath = os_path_join(directory, aFile) log("fetchAllMissingThemes: Found match: %s" % srcpath) targetpath = os_path_join(directory, Settings.getThemeDirectory()) # Make sure the theme directory exists if not dir_exists(targetpath): try: xbmcvfs.mkdir(targetpath) except: log("fetchAllMissingThemes: Failed to create directory: %s" % targetpath, True, xbmc.LOGERROR) break else: log("moveToThemeFolder: directory already exists %s" % targetpath) # Add the filename to the path targetpath = os_path_join(targetpath, aFile) if not xbmcvfs.rename(srcpath, targetpath): log("moveToThemeFolder: Failed to move file from %s to %s" % (srcpath, targetpath))
def _generateThemeFilelist(self, rawPath): # Get the full path with any network alterations workingPath = self._getUsablePath(rawPath) themeList = self._getThemeFiles(workingPath) # If no themes have been found if len(themeList) < 1: # TV shows stored as ripped disc folders if ('VIDEO_TS' in workingPath) or ('BDMV' in workingPath): log("ThemeFiles: Found VIDEO_TS or BDMV in path: Correcting the path for DVDR tv shows", self.debug_logging_enabled) workingPath = os_path_split(workingPath)[0] themeList = self._getThemeFiles(workingPath) if len(themeList) < 1: workingPath = os_path_split(workingPath)[0] themeList = self._getThemeFiles(workingPath) else: # If no theme files were found in this path, look at the parent directory workingPath = os_path_split(workingPath)[0] # Check for the case where there is the theme forlder settings, we want to # check the parent folders themes directory if Settings.isThemeDirEnabled(): themeDir = os_path_join(workingPath, Settings.getThemeDirectory()) themeList = self._getThemeFiles(themeDir) # If there are still no themes, just check the parent directory if len(themeList) < 1: themeList = self._getThemeFiles(workingPath) log("ThemeFiles: Playlist size = %d" % len(themeList), self.debug_logging_enabled) log("ThemeFiles: Working Path = %s" % workingPath, self.debug_logging_enabled) return themeList
def findExtras(self, path, filename, exitOnFirst=False, noExtrasDirNeeded=False): # Make sure that the path and filename are OK try: path = path.encode('utf-8') except: pass try: filename = filename.encode('utf-8') except: pass # Get the extras that are stored in the extras directory i.e. /Extras/ files = self._getExtrasDirFiles(path, exitOnFirst, noExtrasDirNeeded) # Check if we only want the first entry, in which case exit after # we find the first if files and (exitOnFirst is True): return files # Then add the files that have the extras tag in the name i.e. -extras- files.extend(self._getExtrasFiles(path, filename, exitOnFirst)) # Check if we only want the first entry, in which case exit after # we find the first if files and (exitOnFirst is True): return files if Settings.isSearchNested(): # Nested search always needs the extras directory directory files.extend( self._getNestedExtrasFiles(path, filename, exitOnFirst)) files.sort() # Check if we have found any extras at this point if not files: # Check if we have a DVD image directory or Bluray image directory if (os_path_split(path)[1] == 'VIDEO_TS') or (os_path_split(path)[1] == 'BDMV'): log("VideoExtrasFinder: DVD image directory detected, checking = %s" % os_path_split(path)[0]) # If nesting extras inside a Disc image - always needs an Extras directory files = self.findExtras( os_path_split(path)[0], filename, exitOnFirst) return files
def getCoverImage(filePath, eBookFileName): # Check if there is a cached version coverTargetName = None fullpathLocalImage, bookExt = os.path.splitext(filePath) fullpathLocalImage = "%s.jpg" % fullpathLocalImage if xbmcvfs.exists(fullpathLocalImage): log("EBookBase: Found local cached image %s" % fullpathLocalImage) return fullpathLocalImage # Check for a cached cover coverTargetName = EBookBase.getCachedCover(eBookFileName) # If we reach here, then there was no cached cover image, so we need to extract one if coverTargetName in [None, ""]: ebook = EBookBase.createEBookObject(filePath) coverTargetName = ebook.extractCoverImage() ebook.tidyUp() del ebook # If there is still no cover image, check for folder.jpg in the same directory if coverTargetName in [None, ""]: baseDirectory = (os_path_split(filePath))[0] subdirs, filesInDir = xbmcvfs.listdir(baseDirectory) for fileInDir in filesInDir: if fileInDir.lower() in ['folder.jpg', 'cover.jpg', 'folder.png', 'cover.png']: coverTargetName = os_path_join(baseDirectory, fileInDir) return coverTargetName
def _getExistingCoverImage(self): # Check if there is a cached version, or a local one on the drive fullpathLocalImage, bookExt = os.path.splitext(self.filePath) # Store the directory that the file is in, default the the current path parentPath = self.filePath # Check to see if this is actually a file with an extension if (bookExt not in [None, ""]) and (len(bookExt) < 5): fullpathLocalImage1 = "%s.jpg" % fullpathLocalImage fullpathLocalImage2 = "%s.JPG" % fullpathLocalImage if xbmcvfs.exists(fullpathLocalImage1): log("AudioBookHandler: Found local cached image %s" % fullpathLocalImage1) return fullpathLocalImage1 if xbmcvfs.exists(fullpathLocalImage2): log("AudioBookHandler: Found local cached image %s" % fullpathLocalImage2) return fullpathLocalImage2 # If we reach here, then we were a file, so get the directory part parentPath = (os_path_split(self.filePath))[0] # Check for a file in the same directory but with the name # "cover.jpg" or "folder.jpg dirs, files = xbmcvfs.listdir(parentPath) for file in files: if file.lower() in ['folder.jpg', 'cover.jpg']: fullpathLocalImage = os_path_join(parentPath, file) log("AudioBookHandler: Found local directory cover %s" % fullpathLocalImage) return fullpathLocalImage # Check for a cached cover return self._getCachedCover(self.fileName)
def _getCustomPathDir(self, path): # Get the last element of the path pathLastDir = os_path_split(path)[1] # Create the path with this added custPath = Settings.getCustomPath(self.videoType) custPath = os_path_join(custPath, pathLastDir) log("VideoExtrasFinder: Checking existence of custom path %s" % custPath) # Check if this path exists if not dir_exists(custPath): # If it doesn't exist, check the path before that, this covers the # case where there is a TV Show with each season in it's own directory # Make sure we have enough elements to actually navigate back up the path if len(os_path_split((os_path_split(path)[0]))) < 2: log("VideoExtrasFinder: No parent directories to check %s" % path) else: path2ndLastDir = os_path_split((os_path_split(path)[0]))[1] custPath = Settings.getCustomPath(self.videoType) custPath = os_path_join(custPath, path2ndLastDir) custPath = os_path_join(custPath, pathLastDir) log("VideoExtrasFinder: Checking existence of custom path %s" % custPath) if not dir_exists(custPath): # If it still does not exist then check just the 2nd to last path custPath = Settings.getCustomPath(self.videoType) custPath = os_path_join(custPath, path2ndLastDir) log("VideoExtrasFinder: Checking existence of custom path %s" % custPath) if not dir_exists(custPath): # Some systems will store extras in the custom pass using the name # of the TV Show of Movie, so try that videoName = self.title if self.title in [None, ""]: if self.videoType == Settings.TVSHOWS: videoName = xbmc.getInfoLabel("ListItem.TVShowTitle") else: videoName = xbmc.getInfoLabel("ListItem.Title") videoName = normalize_string(videoName) # Now construct the path using the movie or TV show title custPath = Settings.getCustomPath(self.videoType) custPath = os_path_join(custPath, videoName) log("VideoExtrasFinder: Checking existence of custom path using title %s" % custPath) if not dir_exists(custPath): custPath = None return custPath
def __init__(self, eBookFilePath, removeFileWhenComplete=False): log("EBookBase: Loading book %s" % eBookFilePath) self.filePath = eBookFilePath self.fileName = os_path_split(eBookFilePath)[-1] self.isTempBookFile = removeFileWhenComplete try: self.filePath = self.filePath.decode("utf-8") except: pass
def __init__(self, audioBookFilePath): self.filePath = audioBookFilePath self.fileName = os_path_split(audioBookFilePath)[-1] self.coverImage = None self.title = None self.chapters = [] self.numChapters = 0 self.position = -1 self.chapterPosition = -1 self.totalDuration = -1 self.isComplete = None
def __init__(self, audioBookFilePath): self.filePath = audioBookFilePath self.fileName = os_path_split(audioBookFilePath)[-1] self.coverImage = None self.title = None self.chapters = [] self.numChapters = 0 self.position = -1 self.chapterPosition = -1 self.totalDuration = -1 self.isComplete = None self.hasArtwork = -1
def findExtras(self, path, filename, exitOnFirst=False, noExtrasDirNeeded=False): # Make sure that the path and filename are OK try: path = path.encode('utf-8') except: pass try: filename = filename.encode('utf-8') except: pass # Get the extras that are stored in the extras directory i.e. /Extras/ files = self._getExtrasDirFiles(path, exitOnFirst, noExtrasDirNeeded) # Check if we only want the first entry, in which case exit after # we find the first if files and (exitOnFirst is True): return files # Then add the files that have the extras tag in the name i.e. -extras- files.extend(self._getExtrasFiles(path, filename, exitOnFirst)) # Check if we only want the first entry, in which case exit after # we find the first if files and (exitOnFirst is True): return files if Settings.isSearchNested(): # Nested search always needs the extras directory directory files.extend(self._getNestedExtrasFiles(path, filename, exitOnFirst)) files.sort() # Check if we have found any extras at this point if not files: # Check if we have a DVD image directory or Bluray image directory if (os_path_split(path)[1] == 'VIDEO_TS') or (os_path_split(path)[1] == 'BDMV'): log("VideoExtrasFinder: DVD image directory detected, checking = %s" % os_path_split(path)[0]) # If nesting extras inside a Disc image - always needs an Extras directory files = self.findExtras(os_path_split(path)[0], filename, exitOnFirst) return files
def __init__(self): self.baseurl = None self.libraryContentsFile = None self.tvShowList = [] self.movieList = [] # Check if a local theme library is being used localThemeLibrary = Settings.getLocalThemeLibrary() if localThemeLibrary not in [None, ""]: self.libraryContentsFile = localThemeLibrary # Set the base of the library to the directory the contents file is in self.baseurl = os_path_split(localThemeLibrary)[0] # Make sure the last character is a slash self.baseurl = os_path_join(self.baseurl, "") else: # Read the registration file for the library details try: tvtunesRegStr = "PHR2dHVuZXNTdG9yZVJlZz4gICAgPGNvbmZpZz5odHRwOi8vc2l0ZXMuZ29vZ2xlLmNvbS9zaXRlL3JvYndlYnNldC90dnR1bmVzLXN0b3JlLWNvbmZpZy54bWw8L2NvbmZpZz48L3R2dHVuZXNTdG9yZVJlZz4=" # Get the library configuration from the registration file tvtunesRegET = ET.ElementTree( ET.fromstring(base64.b64decode(tvtunesRegStr))) configElem = tvtunesRegET.find('config') if configElem is not None: configLocation = configElem.text if configLocation not in [None, ""]: # Read in all the configuration details tvtunesLibraryConfig = urllib2.urlopen(configLocation) tvtunesLibraryConfigStr = tvtunesLibraryConfig.read() # Closes the connection after we have read the configuration try: tvtunesLibraryConfig.close() except: log( "ThemeLibrary: Failed to close connection for library config", xbmc.LOGERROR) tvtunesLibraryET = ET.ElementTree( ET.fromstring( base64.b64decode(tvtunesLibraryConfigStr))) baseUrlElem = tvtunesLibraryET.find('baseurl') if baseUrlElem is not None: self.baseurl = baseUrlElem.text storeContentsElem = tvtunesLibraryET.find( 'storecontent') if storeContentsElem is not None: self.libraryContentsFile = storeContentsElem.text except: log("ThemeLibrary: %s" % traceback.format_exc(), xbmc.LOGERROR)
def createEBookObject(filePath): localFilePath = filePath removeWhenComplete = False if filePath.startswith('smb://') or filePath.startswith('nfs://'): try: # Copy the file to the local disk justFileName = os_path_split(filePath)[-1] copiedFile = os_path_join(Settings.getTempLocation(), justFileName) copy = xbmcvfs.copy(filePath, copiedFile) if copy: log("EBookBase: copy successful for %s" % copiedFile) localFilePath = copiedFile removeWhenComplete = True else: log("EBookBase: copy failed from %s to %s" % (filePath, copiedFile)) except: log("EBookBase: Failed to copy file %s to local directory" % filePath) elif filePath.startswith('http://') or filePath.startswith('https://'): log("EBookBase: Book source is %s" % filePath) try: justFileName = 'opds.epub' if '/mobi/' in filePath: justFileName = 'opds.mobi' elif '/pdf/' in filePath: justFileName = 'opds.pdf' copiedFile = os_path_join(Settings.getTempLocation(), justFileName) fp, h = urllib.urlretrieve(filePath, copiedFile) log(h) localFilePath = copiedFile removeWhenComplete = True except: log("EBookBase: Failed to download file %s to local directory" % filePath) bookType = None # Check which type of EBook it is if localFilePath.lower().endswith('.epub'): bookType = EPubEBook(localFilePath, removeWhenComplete) elif localFilePath.lower().endswith('.mobi'): bookType = MobiEBook(localFilePath, removeWhenComplete) elif localFilePath.lower().endswith('.pdf'): bookType = PdfEBook(localFilePath, removeWhenComplete) else: log("EBookBase: Unknown book type for %s (%s)" % (filePath, localFilePath)) return bookType
def getPathForVideoItem(self, videoItem): path = "" # Get the path where the theme should be stored if Settings.isCustomPathEnabled(): path = os_path_join(Settings.getCustomPath(), normalize_string(videoItem['title'])) else: path = videoItem['file'] # Handle stacked files that have a custom file name format if path.startswith("stack://"): path = path.replace("stack://", "").split(" , ", 1)[0] # Need to remove the filename from the end as we just want the directory fileExt = os.path.splitext(path)[1] # If this is a file, then get it's parent directory if fileExt is not None and fileExt != "": path = os_path_split(path)[0] return path
def loadExtras(self, path, filename, exitOnFirst=False): # First check to see if there is a videoextras.nfo file extradirs, extras = self._getNfoInfo(path) if (len(extradirs) > 0) or (len(extras) > 0): # There are some extras defined via an NFO file extrasList = [] # Read the extras files from the directories for aDir in extradirs: extrasList = extrasList + self.findExtras( aDir, filename, exitOnFirst, noExtrasDirNeeded=True) # Don't look for more than one if we are only checking for existence of an extra if exitOnFirst: break # For each of the files, get the directory and filename split # and create the extrasItem for anExtraFile in extras: extraItem = ExtrasItem(os_path_split(anExtraFile)[0], anExtraFile, extrasDb=self.extrasDb, defaultFanArt=self.defaultFanArt) extrasList.append(extraItem) # Don't look for more than one if we are only checking for existence of an extra if exitOnFirst: break # Sort the list before returning extrasList.sort() return extrasList # Check if the files are stored in a custom path if Settings.isCustomPathEnabled(): filename = None path = self._getCustomPathDir(path) if path is None: return [] else: log("VideoExtrasFinder: Searching in custom path %s" % path) return self.findExtras( path, filename, exitOnFirst, noExtrasDirNeeded=Settings.isCustomPathEnabled())
def _getCopiedFileIfNeeded(self, fullPath): copiedFile = None if fullPath.startswith('smb://') or fullPath.startswith('nfs://'): try: # Copy the file to the local disk justFileName = os_path_split(fullPath)[-1] copiedFile = os_path_join(Settings.getTempLocation(), justFileName) copy = xbmcvfs.copy(fullPath, copiedFile) if copy: log("AudioBookHandler: copy successful for %s" % copiedFile) else: log("AudioBookHandler: copy failed from %s to %s" % (fullPath, copiedFile)) copiedFile = None except: log("AudioBookHandler: Failed to copy file %s to local directory" % fullPath) copiedFile = None return copiedFile
def getFanArt(filePath): # Check if there is a cached version fullpathLocalImage, bookExt = os.path.splitext(filePath) fullpathLocalImage = "%s-fanart.jpg" % fullpathLocalImage if xbmcvfs.exists(fullpathLocalImage): log("EBookBase: Found book fanart image %s" % fullpathLocalImage) return fullpathLocalImage # Now check if there is a default fanart file fanartImage = FANART baseDirectory = (os_path_split(filePath))[0] subdirs, filesInDir = xbmcvfs.listdir(baseDirectory) for fileInDir in filesInDir: if fileInDir.lower() in ['fanart.jpg', 'fanart.png']: fanartImage = os_path_join(baseDirectory, fileInDir) return fanartImage
def _getExistingCoverImage(self): # Check if there is a cached version, or a local one on the drive fullpathLocalImage, bookExt = os.path.splitext(self.filePath) # Store the directory that the file is in, default the the current path parentPath = self.filePath # Check to see if this is actually a file with an extension if (bookExt not in [None, ""]) and (len(bookExt) < 5): fullpathLocalImage1 = "%s.jpg" % fullpathLocalImage fullpathLocalImage2 = "%s.JPG" % fullpathLocalImage fullpathLocalImage3 = "%s.png" % fullpathLocalImage fullpathLocalImage4 = "%s.PNG" % fullpathLocalImage if xbmcvfs.exists(fullpathLocalImage1): log("AudioBookHandler: Found local cached image %s" % fullpathLocalImage1) return fullpathLocalImage1 if xbmcvfs.exists(fullpathLocalImage2): log("AudioBookHandler: Found local cached image %s" % fullpathLocalImage2) return fullpathLocalImage2 if xbmcvfs.exists(fullpathLocalImage3): log("AudioBookHandler: Found local cached image %s" % fullpathLocalImage3) return fullpathLocalImage3 if xbmcvfs.exists(fullpathLocalImage4): log("AudioBookHandler: Found local cached image %s" % fullpathLocalImage4) return fullpathLocalImage4 # If we reach here, then we were a file, so get the directory part parentPath = (os_path_split(self.filePath))[0] # Check for a file in the same directory but with the name # "cover.jpg" or "folder.jpg dirs, files = xbmcvfs.listdir(parentPath) for file in files: if file.lower() in ['folder.jpg', 'cover.jpg', 'folder.png', 'cover.png']: fullpathLocalImage = os_path_join(parentPath, file) log("AudioBookHandler: Found local directory cover %s" % fullpathLocalImage) return fullpathLocalImage # Check for a cached cover return self._getCachedCover(self.fileName)
def _getUsablePath(self, rawPath): workingPath = rawPath # Start by removing the stack details if workingPath.startswith("stack://"): workingPath = workingPath.replace("stack://", "").split(" , ", 1)[0] if Settings.isSmbEnabled() and not ('@' in workingPath): if workingPath.startswith("smb://"): log("### Try authentication share") workingPath = workingPath.replace( "smb://", "smb://%s:%s@" % (Settings.getSmbUser(), Settings.getSmbPassword())) log("### %s" % workingPath) # Also handle the apple format elif workingPath.startswith("afp://"): log("### Try authentication share") workingPath = workingPath.replace( "afp://", "afp://%s:%s@" % (Settings.getSmbUser(), Settings.getSmbPassword())) log("### %s" % workingPath) # handle episodes stored as rar files if workingPath.startswith("rar://"): workingPath = workingPath.replace("rar://", "") fileExt = None if os_path_isfile(workingPath): fileExt = os.path.splitext(workingPath)[1] # If this is a file, then get it's parent directory # Also limit file extensions to a maximum of 4 characters if fileExt is not None and fileExt != "" and len(fileExt) < 5: workingPath = os_path_split(workingPath)[0] # If the path currently ends in the directory separator # then we need to clear an extra one if (workingPath[-1] == os.sep) or (workingPath[-1] == os.altsep): workingPath = workingPath[:-1] return workingPath
def _generateOrderAndDisplay(self, filename): # First thing is to trim the display name from the filename # Get just the filename, don't need the full path displayName = os_path_split(filename)[1] # Remove the file extension (e.g .avi) displayName = os.path.splitext(displayName)[0] # Remove anything before the -extras- tag (if it exists) extrasTag = Settings.getExtrasFileTag() if (extrasTag != "") and (extrasTag in displayName): justDescription = displayName.split(extrasTag, 1)[1] if len(justDescription) > 0: displayName = justDescription result = (displayName, displayName) # Search for the order which will be written as [n] # Followed by the display name match = re.search("^\[(?P<order>.+)\](?P<Display>.*)", displayName) if match: orderKey = match.group('order') if orderKey != "": result = (orderKey, match.group('Display')) return result
def _generateThemeFilelistWithDirs(self, rawPath): themeFiles = [] # Check the theme directory if it is set up if Settings.isThemeDirEnabled(): themeDir = self._getUsablePath(rawPath) themeDir = os_path_join(themeDir, Settings.getThemeDirectory()) themeFiles = self._generateThemeFilelist(themeDir) # Check for the case where there is a DVD directory and the themes # directory is above it if len(themeFiles) < 1: if ('VIDEO_TS' in rawPath) or ('BDMV' in rawPath): log("ThemeFiles: Found VIDEO_TS in path: Correcting the path for DVDR tv shows", self.debug_logging_enabled) themeDir = self._getUsablePath(rawPath) themeDir = os_path_split(themeDir)[0] themeDir = os_path_join(themeDir, Settings.getThemeDirectory()) themeFiles = self._generateThemeFilelist(themeDir) # If no themes were found in the directory then search the normal location if len(themeFiles) < 1: themeFiles = self._generateThemeFilelist(rawPath) return themeFiles
def _getUsablePath(self, rawPath): workingPath = rawPath # Start by removing the stack details if workingPath.startswith("stack://"): workingPath = workingPath.replace("stack://", "").split(" , ", 1)[0] if Settings.isSmbEnabled() and not ('@' in workingPath): if workingPath.startswith("smb://"): log("### Try authentication share") workingPath = workingPath.replace("smb://", "smb://%s:%s@" % (Settings.getSmbUser(), Settings.getSmbPassword())) log("### %s" % workingPath) # Also handle the apple format elif workingPath.startswith("afp://"): log("### Try authentication share") workingPath = workingPath.replace("afp://", "afp://%s:%s@" % (Settings.getSmbUser(), Settings.getSmbPassword())) log("### %s" % workingPath) # handle episodes stored as rar files if workingPath.startswith("rar://"): workingPath = workingPath.replace("rar://", "") # Support special paths like smb:// means that we can not just call # os.path.isfile as it will return false even if it is a file # (A bit of a shame - but that's the way it is) fileExt = None if workingPath.startswith("smb://") or workingPath.startswith("afp://") or os.path.isfile(workingPath): fileExt = os.path.splitext(workingPath)[1] # If this is a file, then get it's parent directory # Also limit file extensions to a maximum of 4 characters if fileExt is not None and fileExt != "" and len(fileExt) < 5: workingPath = os_path_split(workingPath)[0] # If the path currently ends in the directory separator # then we need to clear an extra one if (workingPath[-1] == os.sep) or (workingPath[-1] == os.altsep): workingPath = workingPath[:-1] return workingPath
def getFanArt(self): baseDirectory = self.filePath if self.filePath.lower().endswith('.m4b'): # Check if there is a file just for this audiobook fullpathLocalImage, bookExt = os.path.splitext(self.filePath) fullpathLocalImage = "%s-fanart.jpg" % fullpathLocalImage if xbmcvfs.exists(fullpathLocalImage): log("AudioBookHandler: Found book fanart image %s" % fullpathLocalImage) return fullpathLocalImage # Get the name of the directory this file is in baseDirectory = (os_path_split(self.filePath))[0] # Now check if there is a default fanart file fanartImage = FANART subdirs, filesInDir = xbmcvfs.listdir(baseDirectory) for fileInDir in filesInDir: if fileInDir.lower() in ['fanart.jpg', 'fanart.png']: fanartImage = os_path_join(baseDirectory, fileInDir) break return fanartImage
def loadExtras(self, path, filename, exitOnFirst=False): # First check to see if there is a videoextras.nfo file extradirs, extras = self._getNfoInfo(path) if (len(extradirs) > 0) or (len(extras) > 0): # There are some extras defined via an NFO file extrasList = [] # Read the extras files from the directories for aDir in extradirs: extrasList = extrasList + self.findExtras(aDir, filename, exitOnFirst, noExtrasDirNeeded=True) # Don't look for more than one if we are only checking for existence of an extra if exitOnFirst: break # For each of the files, get the directory and filename split # and create the extrasItem for anExtraFile in extras: extraItem = ExtrasItem(os_path_split(anExtraFile)[0], anExtraFile, extrasDb=self.extrasDb, defaultFanArt=self.defaultFanArt) extrasList.append(extraItem) # Don't look for more than one if we are only checking for existence of an extra if exitOnFirst: break # Sort the list before returning extrasList.sort() return extrasList # Check if the files are stored in a custom path if Settings.isCustomPathEnabled(): filename = None path = self._getCustomPathDir(path) if path is None: return [] else: log("VideoExtrasFinder: Searching in custom path %s" % path) return self.findExtras(path, filename, exitOnFirst, noExtrasDirNeeded=Settings.isCustomPathEnabled())
def _generateOrderAndDisplay(self, filename): # First thing is to trim the display name from the filename # Get just the filename, don't need the full path displayName = os_path_split(filename)[1] # Remove the file extension (e.g .avi) displayName = os.path.splitext(displayName)[0] # Add path orgString, success, tailString = self.directory.rpartition("Extras") if not success: orgString, success, tailString = self.directory.rpartition("Featurettes") if tailString: orgString, success, parentString = tailString.rpartition("/") if not success: orgString, success, parentString = tailString.rpartition("\\") displayName = parentString + "\\" + displayName else: displayName = "\\" + displayName # Remove anything before the -extras- tag (if it exists) extrasTag = Settings.getExtrasFileTag() if (extrasTag != "") and (extrasTag in displayName): justDescription = displayName.split(extrasTag, 1)[1] if len(justDescription) > 0: displayName = justDescription result = (displayName, displayName) # Search for the order which will be written as [n] # Followed by the display name match = re.search("^\[(?P<order>.+)\](?P<Display>.*)", displayName) if match: orderKey = match.group('order') if orderKey != "": result = (orderKey, match.group('Display')) return result
def _loadFromFile(self): # Get the videos schedule that is stored in the file scheduleFileName = Settings.getScheduleFile() if scheduleFileName in [None, ""]: log("Schedule: No schedule file set") return log("Schedule: Searching for schedule file: %s" % scheduleFileName) # Return False if file does not exist if not xbmcvfs.exists(scheduleFileName): log("Schedule: No schedule file found: %s" % scheduleFileName) return # Save off the time this file was modified statFile = xbmcvfs.Stat(scheduleFileName) self.lastScheduleModified = statFile.st_mtime() log("Schedule: Reading in schedule file with modify time: %s" % str(self.lastScheduleModified)) # The file exists, so start loading it try: # Need to first load the contents of the file into # a string, this is because the XML File Parse option will # not handle formats like smb:// scheduleFile = xbmcvfs.File(scheduleFileName, 'r') scheduleFileStr = scheduleFile.read() scheduleFile.close() # Create an XML parser scheduleXml = ET.ElementTree(ET.fromstring(scheduleFileStr)) rootElement = scheduleXml.getroot() log("Schedule: Root element is = %s" % rootElement.tag) # Check which format if being used if rootElement.tag == "schedule": log("Schedule: Schedule format file detected") # <schedule> # <rule start="14:24" end="14:37" video="video3.mkv" overlay="WindowFrame1.png" /> # </schedule> # Get the directory that the schedule file is in as this might be needed # if we have local paths in the XML file directory = os_path_split(scheduleFileName)[0] # There could be multiple rule entries, so loop through all of them itemNum = self.idOffset + 1 for ruleElem in scheduleXml.findall('rule'): if ruleElem is not None: videoFile = ruleElem.get('video', None) overlayFile = ruleElem.get('overlay', None) startTime = self._convertTimeToMinutes( ruleElem.get('start', "00:00")) endTime = self._convertTimeToMinutes( ruleElem.get('end', "00:00")) if (videoFile not in [None, ""]) and (startTime not in [ None, "" ]) and (endTime not in [None, ""]): # Make it a full path if it is not already if videoFile.startswith('..') or ( ("/" not in videoFile) and ("\\" not in videoFile)): videoFile = os_path_join(directory, videoFile) if overlayFile not in [None, ""]: if overlayFile.startswith('..') or ( ("/" not in overlayFile) and ("\\" not in overlayFile)): overlayFile = os_path_join( directory, overlayFile) log("Schedule File: Item %d (Start:%d, End:%d) contains video %s" % (itemNum, startTime, endTime, videoFile)) # Support special paths like smb:// means that we can not just call # os.path.isfile as it will return false even if it is a file # (A bit of a shame - but that's the way it is) if videoFile.startswith("smb://") or os_path_isfile( videoFile): details = { 'id': itemNum, 'start': startTime, 'end': endTime, 'video': videoFile, 'overlay': overlayFile } self.scheduleDetails.append(details) else: log("Schedule: File does not exist: %s" % videoFile) itemNum = itemNum + 1 else: log("Schedule: Unknown schedule file format") del scheduleXml except: log( "Schedule: Failed to process schedule file: %s" % scheduleFileName, xbmc.LOGERROR) log("Schedule: %s" % traceback.format_exc(), xbmc.LOGERROR)
def _runFFmpegCommand(self, inputFileName, coverTargetName=None): # Check to see if ffmpeg is enabled ffmpeg = Settings.getFFmpegLocation() if ffmpeg in [None, ""]: log("AudioBookHandler: ffmpeg not enabled") return None log("AudioBookHandler: ffmpeg location %s" % ffmpeg) log("AudioBookHandler: Running ffmpeg for %s" % inputFileName) # FFmpeg will not recognise paths that start with smb:// or nfs:// # These paths are specific to Kodi, so we need to copy the file locally # before we can run the FFmpeg command copiedFile = None fullFileName = inputFileName if fullFileName.startswith('smb://') or fullFileName.startswith( 'nfs://'): try: # Copy the file to the local disk justFileName = os_path_split(fullFileName)[-1] copiedFile = os_path_join(Settings.getTempLocation(), justFileName) copy = xbmcvfs.copy(fullFileName, copiedFile) if copy: log("AudioBookHandler: copy successful for %s" % copiedFile) fullFileName = copiedFile else: log("AudioBookHandler: copy failed from %s to %s" % (fullFileName, copiedFile)) except: log("AudioBookHandler: Failed to copy file %s to local directory" % fullFileName) info = None # Use ffmpeg to read the audio book and extract all of the details startupinfo = None if sys.platform.lower() == 'win32': # Need to stop the dialog appearing on windows startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW # Check if we need the image coverTempName = None if coverTargetName not in [None, '']: coverTempName = os_path_join(Settings.getTempLocation(), 'maincover.jpg') # Remove the temporary name if it is already there if xbmcvfs.exists(coverTempName): xbmcvfs.delete(coverTempName) try: # Generate the ffmpeg command ffmpegCmd = [ffmpeg, '-hide_banner', '-y', '-i', fullFileName] # Handle non ascii characters in the file name path try: ffmpegCmd[4] = ffmpegCmd[4].decode('utf-8').encode( locale.getpreferredencoding()) except: log("AudioBookHandler: Failed file system encoding ffmpeg command 1, using default" ) try: ffmpegCmd[4] = ffmpegCmd[4].encode( locale.getpreferredencoding()) except: log("AudioBookHandler: Failed file system encoding ffmpeg command 2, using default" ) try: ffmpegCmd[4] = ffmpegCmd[4].decode().encode( locale.getpreferredencoding()) except: log("AudioBookHandler: Failed file system encoding ffmpeg command 3, using default" ) # Add the output image to the command line if it is needed if coverTempName is not None: try: coverTempName = coverTempName.decode('utf-8').encode( locale.getpreferredencoding()) except: log("AudioBookHandler: Failed file system encoding coverTempName ffmpeg command 1, using default" ) try: coverTempName = coverTempName.encode( locale.getpreferredencoding()) except: log("AudioBookHandler: Failed file system encoding coverTempName ffmpeg command 2, using default" ) ffmpegCmd.append(coverTempName) # Make the ffmpeg call try: info = subprocess.check_output(ffmpegCmd, shell=False, startupinfo=startupinfo, stderr=subprocess.STDOUT) except subprocess.CalledProcessError as error: # This exception will be thrown if ffmpeg prints to STDERR, which it will do if # you try and run the command without an output (i.e. image file), but in most # cases it does actually have the information we need log("AudioBookHandler: CalledProcessError received, processing remaining output" ) info = error.output except: # Unfortunately there are still systems that use Python 2.6 which does not # have check_output, so it that fails, we just use Popen proc = subprocess.Popen(ffmpegCmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) info = "" for outStr in proc.communicate(): if outStr not in [None, ""]: info = "%s%s\n" % (info, outStr) except: log( "AudioBookHandler: Failed to get data using ffmpeg for file %s with error %s" % (self.filePath, traceback.format_exc()), xbmc.LOGERROR) # If we had to copy the file locally, make sure we delete it if copiedFile not in [None, ""]: if xbmcvfs.exists(copiedFile): xbmcvfs.delete(copiedFile) # Check if there is an image in the temporary location if coverTempName not in [None, ""]: if xbmcvfs.exists(coverTempName): # Now move the file to the covers cache directory copy = xbmcvfs.copy(coverTempName, coverTargetName) if copy: log("AudioBookHandler: copy successful for %s" % coverTargetName) else: log("AudioBookHandler: copy failed from %s to %s" % (coverTempName, coverTargetName)) # Tidy up the image that we actually do not need xbmcvfs.delete(coverTempName) # Now the command has been run ffmpegOutput = None if info not in [None, ""]: ffmpegOutput = self._processFFmpegOutput(info) return ffmpegOutput
def addCustomCollection(self, customXmlFile): log("CollectSets: Checking custom xml file: %s" % customXmlFile) # Try and load the collection file to ensure all the data is correct collectionDetails = self.loadCollection(customXmlFile, False) if collectionDetails in [None, ""]: log("CollectSets: No collection details returned for %s" % customXmlFile) # TODO: Show error return False collectionName = collectionDetails['name'] if collectionName.lower() in [ 'aquarium', 'beach', 'clock', 'fireplace', 'miscellaneous', 'snow', 'space', 'waterfall', 'apple tv' ]: log("CollectSets: Collection name clashes %s" % collectionName) # We return True here, as we have already displayed an error msg = "%s: %s" % (ADDON.getLocalizedString(32084), collectionName) xbmcgui.Dialog().notification(ADDON.getLocalizedString(32005), msg, ICON, 5000, False) return True # check the number of videos if len(collectionDetails['videos']) < 1: log("CollectSets: Collection contains no videos %s" % customXmlFile) # TODO: Show error return False # Check each of the settings for a video, must have name, filename and primary for videoItem in collectionDetails['videos']: if videoItem['name'] in [None, ""]: log("CollectSets: Video without a name in collection %s" % customXmlFile) # TODO: Show error return False if videoItem['filename'] in [None, ""]: log("CollectSets: Video without a filename in collection %s" % customXmlFile) # TODO: Show error return False if videoItem['primary'] in [None, ""]: log("CollectSets: Video without a primary in collection %s" % customXmlFile) # TODO: Show error return False customCollections = self.getCustomCollectionSets() # Add check to see if it clashes with a different custom collection if collectionName in customCollections.keys(): log("CollectSets: Custom collection name clashes %s" % collectionName) # We return True here, as we have already displayed an error msg = "%s: %s" % (ADDON.getLocalizedString(32084), collectionName) xbmcgui.Dialog().notification(ADDON.getLocalizedString(32005), msg, ICON, 5000, False) return True # If we have reached here then we are OK to add the custom set, so take a copy of # it to the addon settings directory finalCustomXmlFile = os_path_join(Settings.getCustomFolder(), os_path_split(customXmlFile)[-1]) log("CollectSets: Copy from %s to %s" % (customXmlFile, finalCustomXmlFile)) copy = xbmcvfs.copy(customXmlFile, finalCustomXmlFile) if copy: # Now get the details that are required for the collection customCollections[collectionName] = { 'name': collectionName, 'filename': finalCustomXmlFile, 'image': collectionDetails['image'], 'default': False } # save the new set of custom collections self.saveCustomCollections(customCollections) return True
def getThemes(self): themePath = "" # Only need the theme path for videos if not WindowShowing.isMusicSection(): # Check if the files are stored in a custom path if Settings.isCustomPathEnabled(): if not WindowShowing.isMovies(): videotitle = xbmc.getInfoLabel("ListItem.TVShowTitle") else: videotitle = xbmc.getInfoLabel("ListItem.Title") videotitle = normalize_string(videotitle) themePath = os_path_join(Settings.getCustomPath(), videotitle) # Looking at the TV Show information page elif WindowShowing.isMovieInformation() and ( WindowShowing.isTvShowTitles() or WindowShowing.isTvShows()): themePath = xbmc.getInfoLabel("ListItem.FilenameAndPath") else: themePath = xbmc.getInfoLabel("ListItem.Path") # To try and reduce the amount of "noise" in the logging, where the # same check is logged again and again, we record if it has been # logged for this video, and then do not do it again until the # video changes and what we would print wound be different debug_logging_enabled = False # Only log if something is different from the last time we logged if self.lastLoggedThemePath != themePath: debug_logging_enabled = True self.lastLoggedThemePath = themePath log("TunesBackend: themePath = %s" % themePath, debug_logging_enabled) # Check if the selection is a Movie Set if WindowShowing.isMovieSet(): movieSetMap = self._getMovieSetFileList() if Settings.isCustomPathEnabled(): # Need to make the values part (the path) point to the custom path # rather than the video file for aKey in movieSetMap.keys(): videotitle = normalize_string(aKey) movieSetMap[aKey] = os_path_join(Settings.getCustomPath(), videotitle) if len(movieSetMap) < 1: themefile = ThemeFiles( "", debug_logging_enabled=debug_logging_enabled) else: themefile = ThemeFiles( themePath, movieSetMap.values(), debug_logging_enabled=debug_logging_enabled) # When the reference is into the database and not the file system # then don't return it elif themePath.startswith("videodb:"): # If in either the Tv Show List or the Movie list then # need to stop the theme is selecting the back button if WindowShowing.isMovies() or WindowShowing.isTvShowTitles(): themefile = ThemeFiles( "", debug_logging_enabled=debug_logging_enabled) else: # Load the previous theme themefile = self.newThemeFiles else: if WindowShowing.isMusicSection(): themefile = MusicThemeFiles(debug_logging_enabled) else: themefile = ThemeFiles( themePath, debug_logging_enabled=debug_logging_enabled) # Check if no themes were found for this item, there is a case if it is a # TV Show and it is nested Show-Name/Series-X/Episode-Directory/Episode.ext # Then this will not pick up themes in the root of the TV Show directory if (not themefile.hasThemes()) and ( not Settings.isCustomPathEnabled() ) and WindowShowing.isEpisodes(): tvshowTitle = xbmc.getInfoLabel("ListItem.TVShowTitle") if tvshowTitle not in [None, ""]: try: # Make a call to the database to find out the root path of this TV Show filterStr = '{"operator": "is", "field": "title", "value": "%s"}' % tvshowTitle cmd = '{"jsonrpc": "2.0", "method": "VideoLibrary.GetTVShows", "params": {"properties": ["file"], "filter": %s},"id": 1 }' % filterStr json_query = xbmc.executeJSONRPC(cmd) json_query = simplejson.loads(json_query) if ("result" in json_query) and ( 'tvshows' in json_query['result']): # Get the path to the TV Show and compare it to where we were previously # looking tvshowList = json_query['result']['tvshows'] if len(tvshowList) == 1: tvshowPath = json_query['result'][ 'tvshows'][0]['file'] # Make sure we have not already checked this path # We will already have checked the parent path as well if (tvshowPath != themePath) and ( tvshowPath != os_path_split(themePath)[0]): # So we know that we haven't checked the root of this TV Show yet log( "TunesBackend: Checking root TV Show Path = %s" % tvshowPath, debug_logging_enabled) themefile = ThemeFiles( tvshowPath, debug_logging_enabled= debug_logging_enabled) except: log( "TunesBackend: Failed to check root TV Show %s" % traceback.format_exc(), debug_logging_enabled) return themefile
def _loadFromFile(self): # Get the videos schedule that is stored in the file scheduleFileName = Settings.getScheduleFile() if scheduleFileName in [None, ""]: log("Schedule: No schedule file set") return log("Schedule: Searching for schedule file: %s" % scheduleFileName) # Return False if file does not exist if not xbmcvfs.exists(scheduleFileName): log("Schedule: No schedule file found: %s" % scheduleFileName) return # Save off the time this file was modified statFile = xbmcvfs.Stat(scheduleFileName) self.lastScheduleModified = statFile.st_mtime() log("Schedule: Reading in schedule file with modify time: %s" % str(self.lastScheduleModified)) # The file exists, so start loading it try: # Need to first load the contents of the file into # a string, this is because the XML File Parse option will # not handle formats like smb:// scheduleFile = xbmcvfs.File(scheduleFileName, 'r') scheduleFileStr = scheduleFile.read() scheduleFile.close() # Create an XML parser scheduleXml = ET.ElementTree(ET.fromstring(scheduleFileStr)) rootElement = scheduleXml.getroot() log("Schedule: Root element is = %s" % rootElement.tag) # Check which format if being used if rootElement.tag == "schedule": log("Schedule: Schedule format file detected") # <schedule> # <rule start="14:24" end="14:37" video="video3.mkv" overlay="WindowFrame1.png" /> # </schedule> # Get the directory that the schedule file is in as this might be needed # if we have local paths in the XML file directory = os_path_split(scheduleFileName)[0] # There could be multiple rule entries, so loop through all of them itemNum = self.idOffset + 1 for ruleElem in scheduleXml.findall('rule'): if ruleElem is not None: videoFile = ruleElem.get('video', None) overlayFile = ruleElem.get('overlay', None) startTime = self._convertTimeToMinutes(ruleElem.get('start', "00:00")) endTime = self._convertTimeToMinutes(ruleElem.get('end', "00:00")) if (videoFile not in [None, ""]) and (startTime not in [None, ""]) and (endTime not in [None, ""]): # Make it a full path if it is not already if videoFile.startswith('..') or (("/" not in videoFile) and ("\\" not in videoFile)): videoFile = os_path_join(directory, videoFile) if overlayFile not in [None, ""]: if overlayFile.startswith('..') or (("/" not in overlayFile) and ("\\" not in overlayFile)): overlayFile = os_path_join(directory, overlayFile) log("Schedule File: Item %d (Start:%d, End:%d) contains video %s" % (itemNum, startTime, endTime, videoFile)) # Support special paths like smb:// means that we can not just call # os.path.isfile as it will return false even if it is a file # (A bit of a shame - but that's the way it is) if videoFile.startswith("smb://") or os_path_isfile(videoFile): details = {'id': itemNum, 'start': startTime, 'end': endTime, 'video': videoFile, 'overlay': overlayFile} self.scheduleDetails.append(details) else: log("Schedule: File does not exist: %s" % videoFile) itemNum = itemNum + 1 else: log("Schedule: Unknown schedule file format") del scheduleXml except: log("Schedule: Failed to process schedule file: %s" % scheduleFileName, xbmc.LOGERROR) log("Schedule: %s" % traceback.format_exc(), xbmc.LOGERROR)
def addCustomCollection(self, customXmlFile): log("CollectSets: Checking custom xml file: %s" % customXmlFile) # Try and load the collection file to ensure all the data is correct collectionDetails = self.loadCollection(customXmlFile, False) if collectionDetails in [None, ""]: log("CollectSets: No collection details returned for %s" % customXmlFile) # TODO: Show error return False collectionName = collectionDetails['name'] if collectionName.lower() in ['aquarium', 'beach', 'clock', 'fireplace', 'miscellaneous', 'snow', 'space', 'waterfall', 'apple tv']: log("CollectSets: Collection name clashes %s" % collectionName) # We return True here, as we have already displayed an error msg = "%s: %s" % (ADDON.getLocalizedString(32084), collectionName) xbmcgui.Dialog().notification(ADDON.getLocalizedString(32005), msg, ICON, 5000, False) return True # check the number of videos if len(collectionDetails['videos']) < 1: log("CollectSets: Collection contains no videos %s" % customXmlFile) # TODO: Show error return False # Check each of the settings for a video, must have name, filename and primary for videoItem in collectionDetails['videos']: if videoItem['name'] in [None, ""]: log("CollectSets: Video without a name in collection %s" % customXmlFile) # TODO: Show error return False if videoItem['filename'] in [None, ""]: log("CollectSets: Video without a filename in collection %s" % customXmlFile) # TODO: Show error return False if videoItem['primary'] in [None, ""]: log("CollectSets: Video without a primary in collection %s" % customXmlFile) # TODO: Show error return False customCollections = self.getCustomCollectionSets() # Add check to see if it clashes with a different custom collection if collectionName in customCollections.keys(): log("CollectSets: Custom collection name clashes %s" % collectionName) # We return True here, as we have already displayed an error msg = "%s: %s" % (ADDON.getLocalizedString(32084), collectionName) xbmcgui.Dialog().notification(ADDON.getLocalizedString(32005), msg, ICON, 5000, False) return True # If we have reached here then we are OK to add the custom set, so take a copy of # it to the addon settings directory finalCustomXmlFile = os_path_join(Settings.getCustomFolder(), os_path_split(customXmlFile)[-1]) log("CollectSets: Copy from %s to %s" % (customXmlFile, finalCustomXmlFile)) copy = xbmcvfs.copy(customXmlFile, finalCustomXmlFile) if copy: # Now get the details that are required for the collection customCollections[collectionName] = {'name': collectionName, 'filename': finalCustomXmlFile, 'image': collectionDetails['image'], 'default': False} # save the new set of custom collections self.saveCustomCollections(customCollections) return True