def _findFilesInPath(self, startpath): """Finds files from startpath, could be called recursively """ allfiles = [] if not os.access(startpath, os.R_OK): log().info("Skipping inaccessible path %s" % startpath) return allfiles for subf in os.listdir(string_type(startpath)): newpath = os.path.join(startpath, subf) newpath = os.path.abspath(newpath) if os.path.isfile(newpath): if not self._checkExtension(subf): continue elif self._blacklistedFilename(subf): continue else: allfiles.append(newpath) else: if self.recursive: allfiles.extend(self._findFilesInPath(newpath)) #end if recursive #end if isfile #end for sf return allfiles
def p(*args, **kw): """Rough implementation of the Python 3 print function, http://www.python.org/dev/peps/pep-3105/ def print(*args, sep=' ', end='\n', file=None) """ kw.setdefault('encoding', 'utf-8') kw.setdefault('sep', ' ') kw.setdefault('end', '\n') kw.setdefault('file', sys.stdout) if not PY2: print(kw['sep'].join(string_type(x) for x in args)) return new_args = [] for x in args: if not isinstance(x, basestring): new_args.append(repr(x)) else: if kw['encoding'] is not None: new_args.append(x.encode(kw['encoding'])) else: new_args.append(x) out = kw['sep'].join(new_args) kw['file'].write(out + kw['end'])
def populateFromTvdb(self, tvdb_instance, force_name=None, series_id=None): """Queries the tvdb_api.Tvdb instance for episode name and corrected series name. If series cannot be found, it will warn the user. If the episode is not found, it will use the corrected show name and not set an episode name. If the site is unreachable, it will warn the user. If the user aborts it will catch tvdb_api's user abort error and raise tvnamer's """ try: if series_id is None: show = tvdb_instance[force_name or self.seriesname] else: series_id = int(series_id) tvdb_instance._getShowData(series_id, Config['language']) show = tvdb_instance[series_id] except tvdb_error as errormsg: raise DataRetrievalError("Error with www.thetvdb.com: %s" % errormsg) except tvdb_shownotfound: # No such series found. raise ShowNotFound("Show %s not found on www.thetvdb.com" % self.seriesname) except tvdb_userabort as error: raise UserAbort(string_type(error)) else: # Series was found, use corrected series name self.seriesname = replaceOutputSeriesName(show['seriesname']) if isinstance(self, DatedEpisodeInfo): # Date-based episode epnames = [] for cepno in self.episodenumbers: try: sr = show.airedOn(cepno) if len(sr) > 1: # filter out specials if multiple episodes aired on the day sr = [s for s in sr if s['seasonnumber'] != '0'] if len(sr) > 1: raise EpisodeNotFound( "Ambigious air date %s, there were %s episodes on that day" % (cepno, len(sr))) epnames.append(sr[0]['episodename']) except tvdb_episodenotfound: raise EpisodeNotFound( "Episode that aired on %s could not be found" % (cepno)) self.episodename = epnames return if not hasattr(self, "seasonnumber") or self.seasonnumber is None: # Series without concept of seasons have all episodes in season 1 seasonnumber = 1 else: seasonnumber = self.seasonnumber epnames = [] for cepno in self.episodenumbers: try: episodeinfo = show[seasonnumber][cepno] except tvdb_seasonnotfound: raise SeasonNotFound( "Season %s of show %s could not be found" % (seasonnumber, self.seriesname)) except tvdb_episodenotfound: # Try to search by absolute_number sr = show.search(cepno, "absolute_number") if len(sr) > 1: # For multiple results try and make sure there is a direct match unsure = True for e in sr: if int(e['absolute_number']) == cepno: epnames.append(e['episodename']) unsure = False # If unsure error out if unsure: raise EpisodeNotFound( "No episode actually matches %s, found %s results instead" % (cepno, len(sr))) elif len(sr) == 1: epnames.append(sr[0]['episodename']) else: raise EpisodeNotFound( "Episode %s of show %s, season %s could not be found (also tried searching by absolute episode number)" % (cepno, self.seriesname, seasonnumber)) except tvdb_attributenotfound: raise EpisodeNameNotFound( "Could not find episode name for %s" % cepno) else: epnames.append(episodeinfo['episodename']) self.episodename = epnames
def makeValidFilename(value, normalize_unicode=False, windows_safe=False, custom_blacklist=None, replace_with="_"): """ Takes a string and makes it into a valid filename. normalize_unicode replaces accented characters with ASCII equivalent, and removes characters that cannot be converted sensibly to ASCII. windows_safe forces Windows-safe filenames, regardless of current platform custom_blacklist specifies additional characters that will removed. This will not touch the extension separator: >>> makeValidFilename("T.est.avi", custom_blacklist=".") 'T_est.avi' """ if windows_safe: # Allow user to make Windows-safe filenames, if they so choose sysname = "Windows" else: sysname = platform.system() # If the filename starts with a . prepend it with an underscore, so it # doesn't become hidden. # This is done before calling splitext to handle filename of ".", as # splitext acts differently in python 2.5 and 2.6 - 2.5 returns ('', '.') # and 2.6 returns ('.', ''), so rather than special case '.', this # special-cases all files starting with "." equally (since dotfiles have # no extension) if value.startswith("."): value = "_" + value # Treat extension seperatly value, extension = split_extension(value) # Remove any null bytes value = value.replace("\0", "") # Blacklist of characters if sysname == 'Darwin': # : is technically allowed, but Finder will treat it as / and will # generally cause weird behaviour, so treat it as invalid. blacklist = r"/:" elif sysname in ['Linux', 'FreeBSD']: blacklist = r"/" else: # platform.system docs say it could also return "Windows" or "Java". # Failsafe and use Windows sanitisation for Java, as it could be any # operating system. blacklist = r"\/:*?\"<>|" # Append custom blacklisted characters if custom_blacklist is not None: blacklist += custom_blacklist # Replace every blacklisted character with a underscore value = re.sub("[%s]" % re.escape(blacklist), replace_with, value) # Remove any trailing whitespace value = value.strip() # There are a bunch of filenames that are not allowed on Windows. # As with character blacklist, treat non Darwin/Linux platforms as Windows if sysname not in ['Darwin', 'Linux']: invalid_filenames = [ "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" ] if value in invalid_filenames: value = "_" + value # Replace accented characters with ASCII equivalent if normalize_unicode: import unicodedata value = string_type(value) # cast data to unicode value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') # Truncate filenames to valid/sane length. # NTFS is limited to 255 characters, HFS+ and EXT3 don't seem to have # limits, FAT32 is 254. I doubt anyone will take issue with losing that # one possible character, and files over 254 are pointlessly unweidly max_len = 254 if len(value + extension) > max_len: if len(extension) > len(value): # Truncate extension instead of filename, no extension should be # this long.. new_length = max_len - len(value) extension = extension[:new_length] else: # File name is longer than extension, truncate filename. new_length = max_len - len(extension) value = value[:new_length] return value + extension
def run_tvnamer(with_files, with_flags=None, with_input="", with_config=None, run_on_directory=False, with_coverage=True): """Runs tvnamer on list of file-names in with_files. with_files is a list of strings. with_flags is a list of command line arguments to pass to tvnamer. with_input is the sent to tvnamer's stdin with_config is a string containing the tvnamer to run tvnamer with. Returns a dict with stdout, stderr and a list of files created """ # Create dummy files (config and episodes) episodes_location = make_temp_dir() dummy_files = make_dummy_files(with_files, episodes_location) if with_config is not None: configfname = make_temp_config(with_config) conf_args = ['-c', configfname] else: conf_args = [] if with_flags is None: with_flags = [] if run_on_directory: files = [episodes_location] else: files = dummy_files # Copy sys.path to PYTHONPATH so same modules are available as in # test environmen env = os.environ.copy() env['PYTHONPATH'] = ":".join(sys.path) if with_coverage: # Get path to .coveragerc in parent dir covconf = os.path.abspath( os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".coveragerc")) # Set env vars to flag to tvnamer/__init__.py to configure coverage.py env['COVERAGE_PROCESS_START'] = covconf env['TVNAMER_COVERAGE_SUBPROCESS'] = '' # Construct command cmd = [sys.executable, "-m", "tvnamer"] + conf_args + with_flags + files p("Running command:") p(" ".join(cmd)) proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, # All stderr to stdout stdin=subprocess.PIPE, env=env) proc.stdin.write(with_input.encode("utf-8")) output, _ = proc.communicate() output = output.decode("utf-8") if PY2: def unicodify(obj, encoding="utf-8"): if isinstance(obj, basestring): if not isinstance(obj, unicode): obj = unicode(obj, encoding) return obj output = unicodify(output) created_files = [] for walkroot, walkdirs, walkfiles in os.walk( string_type(episodes_location)): curlist = [ os.path.join(walkroot, unicodedata.normalize( 'NFKD', name)) # Make unicode stuff consistent for name in walkfiles ] # Remove episodes_location from start of path curlist = [relpath(x, episodes_location) for x in curlist] created_files.extend(curlist) # Clean up dummy files and config clear_temp_dir(episodes_location) if with_config is not None: os.unlink(configfname) return { 'output': output, 'files': created_files, 'returncode': proc.returncode }
def clear_temp_dir(location): """Removes file or directory at specified location """ p("Clearing %s" % string_type(location)) shutil.rmtree(location)
def populateFromTvdb(self, tvdb_instance, force_name=None, series_id=None): """Queries the tvdb_api.Tvdb instance for episode name and corrected series name. If series cannot be found, it will warn the user. If the episode is not found, it will use the corrected show name and not set an episode name. If the site is unreachable, it will warn the user. If the user aborts it will catch tvdb_api's user abort error and raise tvnamer's """ try: if series_id is None: show = tvdb_instance[force_name or self.seriesname] else: series_id = int(series_id) tvdb_instance._getShowData(series_id, Config['language']) show = tvdb_instance[series_id] except tvdb_error as errormsg: raise DataRetrievalError("Error with www.thetvdb.com: %s" % errormsg) except tvdb_shownotfound: # No such series found. raise ShowNotFound("Show %s not found on www.thetvdb.com" % self.seriesname) except tvdb_userabort as error: raise UserAbort(string_type(error)) else: # Series was found, use corrected series name self.seriesname = replaceOutputSeriesName(show['seriesname']) if isinstance(self, DatedEpisodeInfo): # Date-based episode epnames = [] for cepno in self.episodenumbers: try: sr = show.airedOn(cepno) if len(sr) > 1: # filter out specials if multiple episodes aired on the day sr = [ s for s in sr if s['seasonnumber'] != '0' ] if len(sr) > 1: raise EpisodeNotFound( "Ambigious air date %s, there were %s episodes on that day" % ( cepno, len(sr))) epnames.append(sr[0]['episodename']) except tvdb_episodenotfound: raise EpisodeNotFound( "Episode that aired on %s could not be found" % ( cepno)) self.episodename = epnames return if not hasattr(self, "seasonnumber") or self.seasonnumber is None: # Series without concept of seasons have all episodes in season 1 seasonnumber = 1 else: seasonnumber = self.seasonnumber epnames = [] for cepno in self.episodenumbers: try: episodeinfo = show[seasonnumber][cepno] except tvdb_seasonnotfound: raise SeasonNotFound( "Season %s of show %s could not be found" % ( seasonnumber, self.seriesname)) except tvdb_episodenotfound: # Try to search by absolute_number sr = show.search(cepno, "absolute_number") if len(sr) > 1: # For multiple results try and make sure there is a direct match unsure = True for e in sr: if int(e['absolute_number']) == cepno: epnames.append(e['episodename']) unsure = False # If unsure error out if unsure: raise EpisodeNotFound( "No episode actually matches %s, found %s results instead" % (cepno, len(sr))) elif len(sr) == 1: epnames.append(sr[0]['episodename']) else: raise EpisodeNotFound( "Episode %s of show %s, season %s could not be found (also tried searching by absolute episode number)" % ( cepno, self.seriesname, seasonnumber)) except tvdb_attributenotfound: raise EpisodeNameNotFound( "Could not find episode name for %s" % cepno) else: epnames.append(episodeinfo['episodename']) self.episodename = epnames
def makeValidFilename(value, normalize_unicode = False, windows_safe = False, custom_blacklist = None, replace_with = "_"): """ Takes a string and makes it into a valid filename. normalize_unicode replaces accented characters with ASCII equivalent, and removes characters that cannot be converted sensibly to ASCII. windows_safe forces Windows-safe filenames, regardless of current platform custom_blacklist specifies additional characters that will removed. This will not touch the extension separator: >>> makeValidFilename("T.est.avi", custom_blacklist=".") 'T_est.avi' """ if windows_safe: # Allow user to make Windows-safe filenames, if they so choose sysname = "Windows" else: sysname = platform.system() # If the filename starts with a . prepend it with an underscore, so it # doesn't become hidden. # This is done before calling splitext to handle filename of ".", as # splitext acts differently in python 2.5 and 2.6 - 2.5 returns ('', '.') # and 2.6 returns ('.', ''), so rather than special case '.', this # special-cases all files starting with "." equally (since dotfiles have # no extension) if value.startswith("."): value = "_" + value # Treat extension seperatly value, extension = split_extension(value) # Remove any null bytes value = value.replace("\0", "") # Blacklist of characters if sysname == 'Darwin': # : is technically allowed, but Finder will treat it as / and will # generally cause weird behaviour, so treat it as invalid. blacklist = r"/:" elif sysname in ['Linux', 'FreeBSD']: blacklist = r"/" else: # platform.system docs say it could also return "Windows" or "Java". # Failsafe and use Windows sanitisation for Java, as it could be any # operating system. blacklist = r"\/:*?\"<>|" # Append custom blacklisted characters if custom_blacklist is not None: blacklist += custom_blacklist # Replace every blacklisted character with a underscore value = re.sub("[%s]" % re.escape(blacklist), replace_with, value) # Remove any trailing whitespace value = value.strip() # There are a bunch of filenames that are not allowed on Windows. # As with character blacklist, treat non Darwin/Linux platforms as Windows if sysname not in ['Darwin', 'Linux']: invalid_filenames = ["CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"] if value in invalid_filenames: value = "_" + value # Replace accented characters with ASCII equivalent if normalize_unicode: import unicodedata value = string_type(value) # cast data to unicode value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') # Truncate filenames to valid/sane length. # NTFS is limited to 255 characters, HFS+ and EXT3 don't seem to have # limits, FAT32 is 254. I doubt anyone will take issue with losing that # one possible character, and files over 254 are pointlessly unweidly max_len = 254 if len(value + extension) > max_len: if len(extension) > len(value): # Truncate extension instead of filename, no extension should be # this long.. new_length = max_len - len(value) extension = extension[:new_length] else: # File name is longer than extension, truncate filename. new_length = max_len - len(extension) value = value[:new_length] return value + extension
def run_tvnamer(with_files, with_flags = None, with_input = "", with_config = None, run_on_directory = False): """Runs tvnamer on list of file-names in with_files. with_files is a list of strings. with_flags is a list of command line arguments to pass to tvnamer. with_input is the sent to tvnamer's stdin with_config is a string containing the tvnamer to run tvnamer with. Returns a dict with stdout, stderr and a list of files created """ # Create dummy files (config and episodes) tvnpath = get_tvnamer_path() episodes_location = make_temp_dir() dummy_files = make_dummy_files(with_files, episodes_location) if with_config is not None: configfname = make_temp_config(with_config) conf_args = ['-c', configfname] else: conf_args = [] if with_flags is None: with_flags = [] if run_on_directory: files = [episodes_location] else: files = dummy_files # Construct command cmd = [sys.executable, tvnpath] + conf_args + with_flags + files p("Running command:") p(" ".join(cmd)) # Copy sys.path to PYTHONPATH so same modules are available as in # test environmen env = os.environ.copy() env['PYTHONPATH'] = ":".join(sys.path) proc = subprocess.Popen( cmd, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, # All stderr to stdout stdin = subprocess.PIPE, env=env) proc.stdin.write(with_input.encode("utf-8")) output, _ = proc.communicate() if PY2: def unicodify(obj, encoding = "utf-8"): if isinstance(obj, basestring): if not isinstance(obj, unicode): obj = unicode(obj, encoding) return obj output = unicodify(output) created_files = [] for walkroot, walkdirs, walkfiles in os.walk(string_type(episodes_location)): curlist = [os.path.join(walkroot, name) for name in walkfiles] # Remove episodes_location from start of path curlist = [relpath(x, episodes_location) for x in curlist] created_files.extend(curlist) # Clean up dummy files and config clear_temp_dir(episodes_location) if with_config is not None: os.unlink(configfname) return { 'output': output, 'files': created_files, 'returncode': proc.returncode}
def run_tvnamer(with_files, with_flags=None, with_input="", with_config=None, run_on_directory=False): """Runs tvnamer on list of file-names in with_files. with_files is a list of strings. with_flags is a list of command line arguments to pass to tvnamer. with_input is the sent to tvnamer's stdin with_config is a string containing the tvnamer to run tvnamer with. Returns a dict with stdout, stderr and a list of files created """ # Create dummy files (config and episodes) tvnpath = get_tvnamer_path() episodes_location = make_temp_dir() dummy_files = make_dummy_files(with_files, episodes_location) if with_config is not None: configfname = make_temp_config(with_config) conf_args = ['-c', configfname] else: conf_args = [] if with_flags is None: with_flags = [] if run_on_directory: files = [episodes_location] else: files = dummy_files # Construct command cmd = [sys.executable, tvnpath] + conf_args + with_flags + files p("Running command:") p(" ".join(cmd)) # Copy sys.path to PYTHONPATH so same modules are available as in # test environmen env = os.environ.copy() env['PYTHONPATH'] = ":".join(sys.path) proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, # All stderr to stdout stdin=subprocess.PIPE, env=env) proc.stdin.write(with_input.encode("utf-8")) output, _ = proc.communicate() if PY2: def unicodify(obj, encoding="utf-8"): if isinstance(obj, basestring): if not isinstance(obj, unicode): obj = unicode(obj, encoding) return obj output = unicodify(output) created_files = [] for walkroot, walkdirs, walkfiles in os.walk( string_type(episodes_location)): curlist = [os.path.join(walkroot, name) for name in walkfiles] # Remove episodes_location from start of path curlist = [relpath(x, episodes_location) for x in curlist] created_files.extend(curlist) # Clean up dummy files and config clear_temp_dir(episodes_location) if with_config is not None: os.unlink(configfname) return { 'output': output, 'files': created_files, 'returncode': proc.returncode }