def rename_file(old, new): p("rename %s to %s" % (old, new)) stat = os.stat(old) os.rename(old, new) try: os.utime(new, (stat.st_atime, stat.st_mtime)) except OSError as ex: if ex.errno == errno.EPERM: warn("WARNING: Could not preserve times for %s " "(owner UID mismatch?)" % new) else: raise
def get_tvnamer_path(): """Gets the path to tvnamer/main.py """ cur_location, _ = os.path.split(os.path.abspath(sys.path[0])) for cdir in [".", ".."]: tvnamer_location = os.path.abspath( os.path.join(cur_location, cdir, "tvnamer", "main.py")) if os.path.isfile(tvnamer_location): return tvnamer_location else: p(tvnamer_location) else: raise IOError("tvnamer/main.py could not be found in . or ..")
def get_tvnamer_path(): """Gets the path to tvnamer/main.py """ cur_location, _ = os.path.split(os.path.abspath(sys.path[0])) for cdir in [".", ".."]: tvnamer_location = os.path.abspath( os.path.join(cur_location, cdir, "main.py")) if os.path.isfile(tvnamer_location): return tvnamer_location else: p(tvnamer_location) else: raise IOError("tvnamer/main.py could not be found in . or ..")
def confirm(question, options, default="y"): """Takes a question (string), list of options and a default value (used when user simply hits enter). Asks until valid option is entered. """ # Highlight default option with [ ] options_str = [] for x in options: if x == default: x = "[%s]" % x if x != '': options_str.append(x) options_str = "/".join(options_str) while True: p(question) p("(%s) " % (options_str), end="") try: ans = raw_input().strip() except KeyboardInterrupt as errormsg: p("\n", errormsg) raise UserAbort(errormsg) if ans in options: return ans elif ans == '': return default
def confirm(question, options, default = "y"): """Takes a question (string), list of options and a default value (used when user simply hits enter). Asks until valid option is entered. """ # Highlight default option with [ ] options_str = [] for x in options: if x == default: x = "[%s]" % x if x != '': options_str.append(x) options_str = "/".join(options_str) while True: p(question) p("(%s) " % (options_str), end="") try: ans = raw_input().strip() except KeyboardInterrupt as errormsg: p("\n", errormsg) raise UserAbort(errormsg) if ans in options: return ans elif ans == '': return default
def clear_temp_dir(location): """Removes file or directory at specified location """ p("Clearing %s" % string_type(location)) shutil.rmtree(location)
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 warn(text): """Displays message to sys.stderr """ p(text, file = sys.stderr)
def main(): """Parses command line arguments, displays errors from tvnamer in terminal """ opter = cliarg_parser.getCommandlineParser(defaults) opts, args = opter.parse_args() if opts.verbose: logging.basicConfig( level = logging.DEBUG, format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s") else: logging.basicConfig() # If a config is specified, load it, update the defaults using the loaded # values, then reparse the options with the updated defaults. default_configuration = os.path.expanduser("~/.tvnamer.json") if opts.loadconfig is not None: # Command line overrides loading ~/.tvnamer.json configToLoad = opts.loadconfig elif os.path.isfile(default_configuration): # No --config arg, so load default config if it exists configToLoad = default_configuration else: # No arg, nothing at default config location, don't load anything configToLoad = None if configToLoad is not None: p("Loading config: %s" % (configToLoad)) try: loadedConfig = json.load(open(os.path.expanduser(configToLoad))) except ValueError as e: p("Error loading config: %s" % e) opter.exit(1) else: # Config loaded, update optparser's defaults and reparse defaults.update(loadedConfig) opter = cliarg_parser.getCommandlineParser(defaults) opts, args = opter.parse_args() # Decode args using filesystem encoding (done after config loading # as the args are reparsed when the config is loaded) if PY2: args = [x.decode(sys.getfilesystemencoding()) for x in args] # Save config argument if opts.saveconfig is not None: p("Saving config: %s" % (opts.saveconfig)) configToSave = dict(opts.__dict__) del configToSave['saveconfig'] del configToSave['loadconfig'] del configToSave['showconfig'] json.dump( configToSave, open(os.path.expanduser(opts.saveconfig), "w+"), sort_keys=True, indent=4) opter.exit(0) # Show config argument if opts.showconfig: print(json.dumps(opts.__dict__, sort_keys=True, indent=2)) return # Process values if opts.batch: opts.select_first = True opts.always_rename = True # Update global config object Config.update(opts.__dict__) if Config["move_files_only"] and not Config["move_files_enable"]: p("#" * 20) p("Parameter move_files_enable cannot be set to false while parameter move_only is set to true.") p("#" * 20) opter.exit(0) if Config['titlecase_filename'] and Config['lowercase_filename']: warnings.warn("Setting 'lowercase_filename' clobbers 'titlecase_filename' option") if len(args) == 0: opter.error("No filenames or directories supplied") try: tvnamer(paths = sorted(args)) except NoValidFilesFoundError: opter.error("No valid files were supplied") except UserAbort as errormsg: opter.error(errormsg) except SkipBehaviourAbort as errormsg: opter.error(errormsg)
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 newPath(self, new_path = None, new_fullpath = None, force = False, always_copy = False, always_move = False, leave_symlink = False, create_dirs = True, getPathPreview = False): """Moves the file to a new path. If it is on the same partition, it will be moved (unless always_copy is True) If it is on a different partition, it will be copied, and the original only deleted if always_move is True. If the target file already exists, it will raise OSError unless force is True. If it was moved, a symlink will be left behind with the original name pointing to the file's new destination if leave_symlink is True. """ if always_copy and always_move: raise ValueError("Both always_copy and always_move cannot be specified") if (new_path is None and new_fullpath is None) or (new_path is not None and new_fullpath is not None): raise ValueError("Specify only new_dir or new_fullpath") old_dir, old_filename = os.path.split(self.filename) if new_path is not None: # Join new filepath to old one (to handle realtive dirs) new_dir = os.path.abspath(os.path.join(old_dir, new_path)) # Join new filename onto new filepath new_fullpath = os.path.join(new_dir, old_filename) else: # Join new filepath to old one (to handle realtive dirs) new_fullpath = os.path.abspath(os.path.join(old_dir, new_fullpath)) new_dir = os.path.dirname(new_fullpath) if len(Config['move_files_fullpath_replacements']) > 0: p("Before custom full path replacements: %s" % (new_fullpath)) new_fullpath = applyCustomFullpathReplacements(new_fullpath) new_dir = os.path.dirname(new_fullpath) p("New path: %s" % new_fullpath) if getPathPreview: return new_fullpath if create_dirs: try: os.makedirs(new_dir) except OSError as e: if e.errno != 17: raise else: p("Created directory %s" % new_dir) if os.path.isfile(new_fullpath): # If the destination exists, raise exception unless force is True if not force: raise OSError("File %s already exists, not forcefully moving %s" % ( new_fullpath, self.filename)) if same_partition(self.filename, new_dir): if always_copy: # Same partition, but forced to copy copy_file(self.filename, new_fullpath) else: # Same partition, just rename the file to move it rename_file(self.filename, new_fullpath) # Leave a symlink behind if configured to do so if leave_symlink: symlink_file(new_fullpath, self.filename) else: # File is on different partition (different disc), copy it copy_file(self.filename, new_fullpath) if always_move: # Forced to move file, we just trash old file p("Deleting %s" % (self.filename)) delete_file(self.filename) # Leave a symlink behind if configured to do so if leave_symlink: symlink_file(new_fullpath, self.filename) self.filename = new_fullpath
def copy_file(old, new): p("copy %s to %s" % (old, new)) shutil.copyfile(old, new) shutil.copystat(old, new)
def verify_out_data(out_data, expected_files, expected_returncode = 0): """Verifies the out_data from run_tvnamer contains the expected files. Prints the stdout/stderr/files, then asserts all files exist. If an assertion fails, nosetest will handily print the stdout/etc. """ p("Return code: %d" % out_data['returncode']) p("Expected files:", expected_files) p("Got files: ", [x for x in out_data['files']]) p("\n" + "*" * 20 + "\n") p("output:\n") p(out_data['output']) # Check number of files if len(expected_files) != len(out_data['files']): raise AssertionError("Expected %d files, but got %d" % ( len(expected_files), len(out_data['files']))) # Check all files were created for cur in expected_files: if cur not in out_data['files']: raise AssertionError("File named %r not created" % (cur)) # Check exit code is zero if out_data['returncode'] != expected_returncode: raise AssertionError("Exit code was %d, not %d" % (out_data['returncode'], expected_returncode))
def verify_out_data(out_data, expected_files): """Verifies the out_data from run_tvnamer contains the expected files. Prints the stdout/stderr/files, then asserts all files exist. If an assertion fails, nosetest will handily print the stdout/etc. """ p("Expected files:", expected_files) p("Got files:", out_data['files']) p("\n" + "*" * 20 + "\n") p("stdout:\n") p(out_data['stdout']) p("\n" + "*" * 20 + "\n") p("stderr:\n") p(out_data['stderr']) # Check number of files if len(expected_files) != len(out_data['files']): raise AssertionError("Expected %d files, but got %d" % ( len(expected_files), len(out_data['files']))) # Check all files were created for cur in expected_files: if cur not in out_data['files']: raise AssertionError("File named %r not created" % (cur))
def newPath(self, new_path=None, new_fullpath=None, force=False, always_copy=False, always_move=False, leave_symlink=False, create_dirs=True, getPathPreview=False): """Moves the file to a new path. If it is on the same partition, it will be moved (unless always_copy is True) If it is on a different partition, it will be copied, and the original only deleted if always_move is True. If the target file already exists, it will raise OSError unless force is True. If it was moved, a symlink will be left behind with the original name pointing to the file's new destination if leave_symlink is True. """ if always_copy and always_move: raise ValueError( "Both always_copy and always_move cannot be specified") if (new_path is None and new_fullpath is None) or (new_path is not None and new_fullpath is not None): raise ValueError("Specify only new_dir or new_fullpath") old_dir, old_filename = os.path.split(self.filename) if new_path is not None: # Join new filepath to old one (to handle realtive dirs) new_dir = os.path.abspath(os.path.join(old_dir, new_path)) # Join new filename onto new filepath new_fullpath = os.path.join(new_dir, old_filename) else: # Join new filepath to old one (to handle realtive dirs) new_fullpath = os.path.abspath(os.path.join(old_dir, new_fullpath)) new_dir = os.path.dirname(new_fullpath) if len(Config['move_files_fullpath_replacements']) > 0: p("Before custom full path replacements: %s" % (new_fullpath)) new_fullpath = applyCustomFullpathReplacements(new_fullpath) new_dir = os.path.dirname(new_fullpath) p("New path: %s" % new_fullpath) if getPathPreview: return new_fullpath if create_dirs: try: os.makedirs(new_dir) except OSError as e: if e.errno != 17: raise else: p("Created directory %s" % new_dir) if os.path.isfile(new_fullpath): # If the destination exists, raise exception unless force is True if not force: raise OSError( "File %s already exists, not forcefully moving %s" % (new_fullpath, self.filename)) if same_partition(self.filename, new_dir): if always_copy: # Same partition, but forced to copy copy_file(self.filename, new_fullpath) else: # Same partition, just rename the file to move it rename_file(self.filename, new_fullpath) # Leave a symlink behind if configured to do so if leave_symlink: symlink_file(new_fullpath, self.filename) else: # File is on different partition (different disc), copy it copy_file(self.filename, new_fullpath) if always_move: # Forced to move file, we just trash old file p("Deleting %s" % (self.filename)) delete_file(self.filename) # Leave a symlink behind if configured to do so if leave_symlink: symlink_file(new_fullpath, self.filename) self.filename = new_fullpath
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)) proc = Popen( cmd, stdout = PIPE, stderr = PIPE, stdin = PIPE) proc.stdin.write(with_input) stdout, stderr = proc.communicate() stdout, stderr = [unicodify(x) for x in (stdout, stderr)] created_files = [] for walkroot, walkdirs, walkfiles in os.walk(unicode(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 { 'stdout': stdout, 'stderr': stderr, 'files': created_files}
def clear_temp_dir(location): """Removes file or directory at specified location """ p("Clearing %s" % unicode(location)) shutil.rmtree(location)
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)) proc = Popen( cmd, stdout = PIPE, stderr = PIPE, stdin = PIPE) proc.stdin.write(with_input) stdout, stderr = proc.communicate() stdout, stderr = [unicodify(x) for x in (stdout, stderr)] created_files = [] for walkroot, walkdirs, walkfiles in os.walk(unicode(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 { 'stdout': stdout, 'stderr': stderr, 'files': created_files, 'returncode': proc.returncode}
def verify_out_data(out_data, expected_files, expected_returncode=0): """Verifies the out_data from run_tvnamer contains the expected files. Prints the stdout/stderr/files, then asserts all files exist. If an assertion fails, nosetest will handily print the stdout/etc. """ p("Return code: %d" % out_data['returncode']) p("Expected files:", expected_files) p("Got files: ", [x for x in out_data['files']]) p("\n" + "*" * 20 + "\n") p("output:\n") p(out_data['output']) # Check number of files if len(expected_files) != len(out_data['files']): raise AssertionError("Expected %d files, but got %d" % (len(expected_files), len(out_data['files']))) # Check all files were created for cur in expected_files: if cur not in out_data['files']: raise AssertionError("File named %r not created" % (cur)) # Check exit code is zero if out_data['returncode'] != expected_returncode: raise AssertionError("Exit code was %d, not %d" % (out_data['returncode'], expected_returncode))
def tvnamer(paths): """Main tvnamer function, takes an array of paths, does stuff. """ p("#" * 20) p("# Starting tvnamer") episodes_found = [] for cfile in findFiles(paths): parser = FileParser(cfile) try: episode = parser.parse() except InvalidFilename as e: warn("Invalid filename: %s" % e) else: if episode.seriesname is None and Config['force_name'] is None and Config['series_id'] is None: warn("Parsed filename did not contain series name (and --name or --series-id not specified), skipping: %s" % cfile) else: episodes_found.append(episode) if len(episodes_found) == 0: raise NoValidFilesFoundError() p("# Found %d episode" % len(episodes_found) + ("s" * (len(episodes_found) > 1))) # Sort episodes by series name, season and episode number episodes_found.sort(key = lambda x: x.sortable_info()) # episode sort order if Config['order'] == 'dvd': dvdorder = True else: dvdorder = False if not PY2 and os.getenv("TRAVIS", "false") == "true": # Disable caching on Travis-CI because in Python 3 it errors with: # # Can't pickle <class 'http.cookiejar.DefaultCookiePolicy'>: it's not the same object as http.cookiejar.DefaultCookiePolicy cache = False else: cache = True tvdb_instance = Tvdb( interactive = not Config['select_first'], search_all_languages = Config['search_all_languages'], language = Config['language'], dvdorder = dvdorder, cache=cache, ) for episode in episodes_found: processFile(tvdb_instance, episode) p('') p("#" * 20) p("# Done")
def tvnamer(paths): """Main tvnamer function, takes an array of paths, does stuff. """ p("#" * 20) p("# Starting tvnamer") episodes_found = [] for cfile in findFiles(paths): parser = FileParser(cfile) try: episode = parser.parse() except InvalidFilename as e: warn("Invalid filename: %s" % e) else: if episode.seriesname is None and Config[ 'force_name'] is None and Config['series_id'] is None: warn( "Parsed filename did not contain series name (and --name or --series-id not specified), skipping: %s" % cfile) else: episodes_found.append(episode) if len(episodes_found) == 0: raise NoValidFilesFoundError() p("# Found %d episode" % len(episodes_found) + ("s" * (len(episodes_found) > 1))) # Sort episodes by series name, season and episode number episodes_found.sort(key=lambda x: x.sortable_info()) # episode sort order if Config['order'] == 'dvd': dvdorder = True else: dvdorder = False if not PY2 and os.getenv("TRAVIS", "false") == "true": # Disable caching on Travis-CI because in Python 3 it errors with: # # Can't pickle <class 'http.cookiejar.DefaultCookiePolicy'>: it's not the same object as http.cookiejar.DefaultCookiePolicy cache = False else: cache = True tvdb_instance = Tvdb( interactive=not Config['select_first'], search_all_languages=Config['search_all_languages'], language=Config['language'], dvdorder=dvdorder, cache=cache, ) for episode in episodes_found: processFile(tvdb_instance, episode) p('') p("#" * 20) p("# Done")
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 symlink_file(target, name): p("symlink %s to %s" % (name, target)) os.symlink(target, name)
def processFile(tvdb_instance, episode): """Gets episode name, prompts user for input """ p("#" * 20) p("# Processing file: %s" % episode.fullfilename) if len(Config['input_filename_replacements']) > 0: replaced = applyCustomInputReplacements(episode.fullfilename) p("# With custom replacements: %s" % (replaced)) # Use force_name option. Done after input_filename_replacements so # it can be used to skip the replacements easily if Config['force_name'] is not None: episode.seriesname = Config['force_name'] p("# Detected series: %s (%s)" % (episode.seriesname, episode.number_string())) try: episode.populateFromTvdb(tvdb_instance, force_name=Config['force_name'], series_id=Config['series_id']) except (DataRetrievalError, ShowNotFound) as errormsg: if Config['always_rename'] and Config['skip_file_on_error'] is True: if Config['skip_behaviour'] == 'exit': warn("Exiting due to error: %s" % errormsg) raise SkipBehaviourAbort() warn("Skipping file due to error: %s" % errormsg) return else: warn(errormsg) except (SeasonNotFound, EpisodeNotFound, EpisodeNameNotFound) as errormsg: # Show was found, so use corrected series name if Config['always_rename'] and Config['skip_file_on_error']: if Config['skip_behaviour'] == 'exit': warn("Exiting due to error: %s" % errormsg) raise SkipBehaviourAbort() warn("Skipping file due to error: %s" % errormsg) return warn(errormsg) cnamer = Renamer(episode.fullpath) shouldRename = False if Config["move_files_only"]: newName = episode.fullfilename shouldRename = True else: newName = episode.generateFilename() if newName == episode.fullfilename: p("#" * 20) p("Existing filename is correct: %s" % episode.fullfilename) p("#" * 20) shouldRename = True else: p("#" * 20) p("Old filename: %s" % episode.fullfilename) if len(Config['output_filename_replacements']) > 0: # Show filename without replacements p("Before custom output replacements: %s" % (episode.generateFilename(preview_orig_filename = False))) p("New filename: %s" % newName) if Config['always_rename']: doRenameFile(cnamer, newName) if Config['move_files_enable']: if Config['move_files_destination_is_filepath']: doMoveFile(cnamer = cnamer, destFilepath = getMoveDestination(episode)) else: doMoveFile(cnamer = cnamer, destDir = getMoveDestination(episode)) return elif Config['dry_run']: p("%s will be renamed to %s" % (episode.fullfilename, newName)) if Config['move_files_enable']: p("%s will be moved to %s" % (newName, getMoveDestination(episode))) return ans = confirm("Rename?", options = ['y', 'n', 'a', 'q'], default = 'y') if ans == "a": p("Always renaming") Config['always_rename'] = True shouldRename = True elif ans == "q": p("Quitting") raise UserAbort("User exited with q") elif ans == "y": p("Renaming") shouldRename = True elif ans == "n": p("Skipping") else: p("Invalid input, skipping") if shouldRename: doRenameFile(cnamer, newName) if shouldRename and Config['move_files_enable']: newPath = getMoveDestination(episode) if Config['dry_run']: p("%s will be moved to %s" % (newName, getMoveDestination(episode))) return if Config['move_files_destination_is_filepath']: doMoveFile(cnamer = cnamer, destFilepath = newPath, getPathPreview = True) else: doMoveFile(cnamer = cnamer, destDir = newPath, getPathPreview = True) if not Config['batch'] and Config['move_files_confirmation']: ans = confirm("Move file?", options = ['y', 'n', 'q'], default = 'y') else: ans = 'y' if ans == 'y': p("Moving file") doMoveFile(cnamer, newPath) elif ans == 'q': p("Quitting") raise UserAbort("user exited with q")
def warn(text): """Displays message to sys.stderr """ p(text, file=sys.stderr)
def processFile(tvdb_instance, episode): """Gets episode name, prompts user for input """ p("#" * 20) p("# Processing file: %s" % episode.fullfilename) if len(Config['input_filename_replacements']) > 0: replaced = applyCustomInputReplacements(episode.fullfilename) p("# With custom replacements: %s" % (replaced)) # Use force_name option. Done after input_filename_replacements so # it can be used to skip the replacements easily if Config['force_name'] is not None: episode.seriesname = Config['force_name'] p("# Detected series: %s (%s)" % (episode.seriesname, episode.number_string())) try: episode.populateFromTvdb(tvdb_instance, force_name=Config['force_name'], series_id=Config['series_id']) except (DataRetrievalError, ShowNotFound) as errormsg: if Config['always_rename'] and Config['skip_file_on_error'] is True: if Config['skip_behaviour'] == 'exit': warn("Exiting due to error: %s" % errormsg) raise SkipBehaviourAbort() warn("Skipping file due to error: %s" % errormsg) return else: warn(errormsg) except (SeasonNotFound, EpisodeNotFound, EpisodeNameNotFound) as errormsg: # Show was found, so use corrected series name if Config['always_rename'] and Config['skip_file_on_error']: if Config['skip_behaviour'] == 'exit': warn("Exiting due to error: %s" % errormsg) raise SkipBehaviourAbort() warn("Skipping file due to error: %s" % errormsg) return warn(errormsg) cnamer = Renamer(episode.fullpath) shouldRename = False if Config["move_files_only"]: newName = episode.fullfilename shouldRename = True else: newName = episode.generateFilename() if newName == episode.fullfilename: p("#" * 20) p("Existing filename is correct: %s" % episode.fullfilename) p("#" * 20) shouldRename = True else: p("#" * 20) p("Old filename: %s" % episode.fullfilename) if len(Config['output_filename_replacements']) > 0: # Show filename without replacements p("Before custom output replacements: %s" % (episode.generateFilename(preview_orig_filename=False))) p("New filename: %s" % newName) if Config['always_rename']: doRenameFile(cnamer, newName) if Config['move_files_enable']: if Config['move_files_destination_is_filepath']: doMoveFile(cnamer=cnamer, destFilepath=getMoveDestination(episode)) else: doMoveFile(cnamer=cnamer, destDir=getMoveDestination(episode)) return elif Config['dry_run']: p("%s will be renamed to %s" % (episode.fullfilename, newName)) if Config['move_files_enable']: p("%s will be moved to %s" % (newName, getMoveDestination(episode))) return ans = confirm("Rename?", options=['y', 'n', 'a', 'q'], default='y') if ans == "a": p("Always renaming") Config['always_rename'] = True shouldRename = True elif ans == "q": p("Quitting") raise UserAbort("User exited with q") elif ans == "y": p("Renaming") shouldRename = True elif ans == "n": p("Skipping") else: p("Invalid input, skipping") if shouldRename: doRenameFile(cnamer, newName) if shouldRename and Config['move_files_enable']: newPath = getMoveDestination(episode) if Config['dry_run']: p("%s will be moved to %s" % (newName, getMoveDestination(episode))) return if Config['move_files_destination_is_filepath']: doMoveFile(cnamer=cnamer, destFilepath=newPath, getPathPreview=True) else: doMoveFile(cnamer=cnamer, destDir=newPath, getPathPreview=True) if not Config['batch'] and Config['move_files_confirmation']: ans = confirm("Move file?", options=['y', 'n', 'q'], default='y') else: ans = 'y' if ans == 'y': p("Moving file") doMoveFile(cnamer, newPath) elif ans == 'q': p("Quitting") raise UserAbort("user exited with q")
def main(): """Parses command line arguments, displays errors from tvnamer in terminal """ opter = cliarg_parser.getCommandlineParser(defaults) opts, args = opter.parse_args() if opts.verbose: logging.basicConfig( level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") else: logging.basicConfig() # If a config is specified, load it, update the defaults using the loaded # values, then reparse the options with the updated defaults. default_configuration = os.path.expanduser("~/.tvnamer.json") if opts.loadconfig is not None: # Command line overrides loading ~/.tvnamer.json configToLoad = opts.loadconfig elif os.path.isfile(default_configuration): # No --config arg, so load default config if it exists configToLoad = default_configuration else: # No arg, nothing at default config location, don't load anything configToLoad = None if configToLoad is not None: p("Loading config: %s" % (configToLoad)) try: loadedConfig = json.load(open(os.path.expanduser(configToLoad))) except ValueError as e: p("Error loading config: %s" % e) opter.exit(1) else: # Config loaded, update optparser's defaults and reparse defaults.update(loadedConfig) opter = cliarg_parser.getCommandlineParser(defaults) opts, args = opter.parse_args() # Decode args using filesystem encoding (done after config loading # as the args are reparsed when the config is loaded) if PY2: args = [x.decode(sys.getfilesystemencoding()) for x in args] # Save config argument if opts.saveconfig is not None: p("Saving config: %s" % (opts.saveconfig)) configToSave = dict(opts.__dict__) del configToSave['saveconfig'] del configToSave['loadconfig'] del configToSave['showconfig'] json.dump(configToSave, open(os.path.expanduser(opts.saveconfig), "w+"), sort_keys=True, indent=4) opter.exit(0) # Show config argument if opts.showconfig: print(json.dumps(opts.__dict__, sort_keys=True, indent=2)) return # Process values if opts.batch: opts.select_first = True opts.always_rename = True # Update global config object Config.update(opts.__dict__) if Config["move_files_only"] and not Config["move_files_enable"]: p("#" * 20) p("Parameter move_files_enable cannot be set to false while parameter move_only is set to true." ) p("#" * 20) opter.exit(0) if Config['titlecase_filename'] and Config['lowercase_filename']: warnings.warn( "Setting 'lowercase_filename' clobbers 'titlecase_filename' option" ) if len(args) == 0: opter.error("No filenames or directories supplied") try: tvnamer(paths=sorted(args)) except NoValidFilesFoundError: opter.error("No valid files were supplied") except UserAbort as errormsg: opter.error(errormsg) except SkipBehaviourAbort as errormsg: opter.error(errormsg)
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() # register cleanup function atexit.register(shutil.rmtree, episodes_location) dummy_files = make_dummy_files(with_files, episodes_location) if with_config is not None: # insert correct version into config with_config = with_config.replace("{", """{"__version__": "%s",\n""" % __version__, 1) configfname = make_temp_config(with_config) # register cleanup function atexit.register(os.unlink, configfname) 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)) proc = subprocess.Popen( cmd, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, # All stderr to stdout stdin = subprocess.PIPE) proc.stdin.write(with_input) output, _ = proc.communicate() output = unicodify(output) created_files = [] for walkroot, walkdirs, walkfiles in os.walk(unicode(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) return { 'output': output, 'files': created_files, 'returncode': proc.returncode}