rename.py is a script that I use to automatically rename files in my music library. I wrote an early version of this a very long time ago, and have made many small improvements since.
The main advantages over the perl rename script that comes with ubuntu are:
-n
option, it will rename them to 001, 002 through 100To see more info about the available options run the script with -h
or --help
.
Here's the script:
#!/usr/bin/env python3 # # Copyright © 2015 Hans Alves <halves@localhost> # This work is free. You can redistribute it and/or modify it under the # terms of the Do What The Fuck You Want To Public License, Version 2, # as published by Sam Hocevar. See http://www.wtfpl.net/ for more details. # import os import re import unicodedata import argparse description='''Rename files: First the substitution given by the -s an -p parameters is performed. Then, if the -c parameter is given the case of the filenames is adjusted accordingly. Thirdly, if the -a parameter is given accents like é ï à ø and æ among others are removed. These characters are replaced by e i a o and ae. After that the default replacement is done, unless the --no-default parameter is given. The default replacement consists of several steps. 1) Quotes are removed. 2) All characters except alphanumeric characters . _ and - are replaced by underscores. 3) _-_ is replaced by a dash. 4) Groups of more than one consecutive _ or - are replaced by one underscore. 5) Underscores and dashes at the start of the filename or before the filename extension are removed. Finally, if the -n parameter is provided, the names of numbered files may be fixed by prepending zeros to the start of the filename so all numbers have the same length. ''' nr_exp = re.compile(r'^(\d*)') def query_replace(oldname, newname): answer = input("WARNING: `{newname}' already exists, really rename "\\ "`{oldname}' to `{newname}'? This will cause `{newname}' to be "\\ "lost! Yes/No? ".format(oldname=oldname, newname=newname)) while answer not in ('Yes', 'No'): answer = input('Only Yes/No please ') return answer == 'Yes' def rename(args, path): directory = os.listdir(path) if not directory: # the directory is empty return nr_size = max(len(nr_exp.match(f).group(1)) for f in directory) for file in directory: fname = os.path.join(path, file) if args.ignore is None or not re.match(args.ignore, file): if os.path.isdir(fname) and args.recurse: rename(args, fname) renamefile(args, path, file, nr_size) def remove_accents(name): name = name.replace('ø', 'o') name = name.replace('Ø', 'O') name = name.replace('æ', 'ae') name = name.replace('Æ', 'AE') name = name.replace('œ', 'oe') name = name.replace('Œ', 'OE') name = name.replace('ł', 'l') name = name.replace('Ł', 'L') name = unicodedata.normalize('NFKD', name).encode('ASCII', 'ignore').decode() return name def default(name): name = name.replace("'", '') # remove quotes name = name.replace('"', '') # remove quotes name = name.replace('`', '') # remove quotes #name = re.sub('_([Ss])_', r'\1_', name) # replace weird characters by _ name = re.sub('[^\w.-]+', '_', name) name = name.replace('_-_', '-') # replace more than one of _ or - by _ name = re.sub('[_-]{2,}', '_', name) # remove _ or - before the file extention name = re.sub(r'[_-]+((\.[a-zA-Z0-9]+)?)$', r'\1', name) # remove _ and - from the start of the name name = re.sub('^[_-]+', '', name) idx = name.rfind('.') if idx > 0: name = (name[:idx] + name[idx:].lower()) # extention to lowercase return name def normalcase(m): return m.group(1).upper() + m.group(2).lower() def renamefile(args, path, name, nr_size): oldname = os.path.join(path, name) if args.substitute is not None: name = re.sub(args.substitute, args.replacement, name) if args.case == 'lower': name = name.lower() if args.case == 'upper': name = name.upper() if args.case == 'normal': name = re.sub(r'([A-Za-z])([A-Za-z]+)', normalcase, name) if args.remove_accents: name = remove_accents(name) if not args.no_default: name = default(name) # fix numbered files if args.nr and nr_exp.match(name).group(1): pattern = '{{0:0{size}d}}'.format(size=nr_size) name = nr_exp.sub(lambda m: pattern.format(int(m.group(1))), name) if not name: print('WARNING: cannot rename {oldname} new name would be '\\ 'empty'.format(oldname=oldname)) return newname = os.path.join(path, name) if oldname != newname: if args.verbose or args.fake: print('{0:s} --> {1:s}'.format(oldname, newname)) if not args.fake: if ((not os.path.exists(newname)) or args.yes_to_all or query_replace(oldname, newname)): os.rename(oldname, newname) def main(): parser = argparse.ArgumentParser(description=description, formatter_class=argparse.RawDescriptionHelpFormatter,) parser.add_argument('-r', '--recursive', dest='recurse', action='store_true', default=False, help='Recursively process subdirectories.') parser.add_argument('-s', '--substitute', dest='substitute', action='store', metavar='PATTERN', help='Substitute pattern from filenames by the ' 'pattern given to -p or remove them if no ' 'replacement is provided.') parser.add_argument('-p', '--replacement', dest='replacement', action='store', metavar='PATTERN', default='', help='Replace substituted pattern from --remove ' 'with pattern, only useful in conjunction ' 'with -s, --substitute.') parser.add_argument('-c', '--case', dest='case', action='store', choices=('lower', 'upper', 'normal'), help='Convert the case of all letters. Possible ' 'choices are lower, upper and normal. If normal ' 'is used the first letter of each word will be ' 'made uppercase, the rest lowercase.') parser.add_argument('-a', '--remove-accents', dest='remove_accents', action='store_true', default=False, help="Remove accents like é or ï. These characters " "will be replaced by e and i.") parser.add_argument('--no-default', dest='no_default', action='store_true', default=False, help="Don't do the default replacement.") parser.add_argument('-n', '--numbers', dest='nr', action='store_true', default=False, help='fix numbered files.') parser.add_argument('-f', '--fake', dest='fake', action='store_true', default=False, help='Do not rename files, only print actions that ' 'would be executed.') parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, help="Show how files are renamed.") parser.add_argument('-i', '--ignore', dest='ignore', action='store', metavar='PATTERN', help='Ignore filenames that match PATTERN.') parser.add_argument('-y', '--yes-to-all', dest='yes_to_all', action='store_true', default=False, help="Automatically answer Yes to all replacements. " "WARNING: this may cause files to be overwritten" " and lost.") parser.add_argument('-V', '--version', action='version', version='rename.py 2.0') parser.add_argument('directory', nargs='?', default='.', help="Rename files in directory (default `.').") args = parser.parse_args() if args.replacement and args.substitute is None: parser.error('The -p option cannot be used without -s.') rename(args, args.directory) if __name__ == '__main__': main()