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:

To 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()