Renumbering (and renaming) media files

05Oct10

I often find myself having to rename a bunch of media files. This would be easy if it was just a matter of finding ‘foo’ and replacing ‘bar’. Sadly though I regularly have a list of numbered ‘foo’s that I want to be a different numbered ‘bar’s (e.g. foo_04 -> bar_01, foo_05 -> bar_02).

Normally I just suck this up and deal with the problem by hand, but as I’ve been trying to learn Python I thought I’d dust off my text editor and knock up a little script.

Here’s the result – incdec.py:

"""
Author: Chris Swan
Date:   5 Oct 2010
Updated with suggestions from Tim Swan: 6 Oct 2010

with thanks to Matt Weber (http://www.mattweber.org/2007/03/04/python-script-renamepy/)

Increment or decrement file names
"""

import os
import sys
from optparse import OptionParser

def ProcessFiles(options):

    # Set the offset to increment or decrement
    if options.inc:
        offset=options.offset
    else:
        offset=0-options.offset

    # Get directory to work on
    if options.directory:
        path = options.directory[0]
    else:
        path = os.getcwd()

    # Create a list of files in the directory
    fileList = os.listdir(path)

    # Reverse the list so that we don't get file name collisions
    if offset > 0:
        fileList.reverse()

    # Iterate across the list of files
    for file in fileList:

        # Get filename and the extension
        name, ext = os.path.splitext(file)
        oldname = os.path.join( path, name+ext )

        # Replace - first step
        if options.replace:
            for vals in options.replace:
                name = name.replace(vals[0], "*")
                replace = vals[1]

        # Extract digits from filename
        ndigits = ''.join([letter for letter in name if letter.isdigit()])
        # and the residual letters
        nletters = ''.join([letter for letter in name if not letter.isdigit()])

        # Process the inc/dec
        if ndigits != '':
            #Decrement the digits
            newdigits = str(int(ndigits)+offset)
            # and replace any zeros that may have been stripped by the int operation
            zeropad=len(ndigits)-len(newdigits)
            while zeropad>0:
                newdigits = "0"+newdigits
                zeropad=zeropad-1
        else:
            newdigits=""

        # Replace - second step
        if options.replace:
            nletters = nletters.replace("*", replace)

        # Create the new name
        newname = os.path.join( path , nletters + newdigits + ext )
        try:
            # Check for verbose output
            if options.verbose:
                print(oldname + " -> " + newname)
            # Rename the file
            os.rename(oldname, newname)
        except (OSError):
            print >>sys.stderr, ("Error renaming "+file+" to "+newname)

if __name__ == "__main__":
    """
    Parses command line and renames the files passed in
    """
    # Create the options we want to parse
    usage = "usage: %prog [options]"
    optParser = OptionParser(usage=usage)
    optParser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="Use verbose output")
    optParser.add_option("-i", "--inc", action="store_true", dest="inc", default=False, help="Increment")
    optParser.add_option("-o", "--offset", type="int", dest="offset", default="1", help="Offset number")
    optParser.add_option("-d", "--dir", action="append", type="string", nargs=1, dest="directory", help="Directory to work on if not PWD")
    optParser.add_option("-r", "--replace", action="append", type="string", nargs=2, dest="replace", help="Replaces OLDVAL with NEWVAL in the filename", metavar="OLDVAL NEWVAL")
    (options, args) = optParser.parse_args()

    # Process files
    ProcessFiles(options)

    # exit successful
    sys.exit(0)

It runs in Python 3 (tested with 3.1.2). By default it will decrement the number on a filename by 1 in the present working directory. There are a bunch of options:

  • -i will increment numbers rather than decrement
  • -o will allow an offset other than 1
  • -d allows a directory other than the present working directory to be used
  • -r allows a text search/replace on the filename (I’ve been careful to allow this to contain numbers on both parts and not get in the way of the renumbering process)
  • -v gives verbose output

Use at your own peril.

Please be gentle in the comments – this is my first real Python program.

Update 6 Oct – I modified some of the code in light of Tim’s comments below (particularly to deal with directory delimiters on different OSes). I also spotted an issue with how I’d handled replace. My original effort will be maintained here for posterity.



5 Responses to “Renumbering (and renaming) media files”

  1. 1 Tim Swan

    If you’re learning python, some possible improvements:

    # Create the new name
    newname = path + “\\” + nletters + newdigits + ext

    don’t do that – use os.path.join( a,b,c,… ). the separator character will be a forward slash on non-windows operating systems.

    # Create the new name
    newname = os.path.join( path , nletters + newdigits + ext )

    python supports return tuple unpacking, so this:

    # Get filename without extension
    name = os.path.splitext(file)[0]
    # and the extension name
    ext = os.path.splitext(file)[1]
    oldname = path + “\\” + name + ext

    is best written like this:

    name, ext = os.path.splitext(file)
    oldname = os.path.join( path, name+ext )

    and a couple of more pythonic ways to do the digit manipulation:

    (this is using python 2.x formatting strings. should work in 3.x …)

    # this will give ‘%5d’ for a 5 digit ndigits value
    digit_formatter = ‘%%0%dd’ % len(ndigits)

    # now feed the value into this string formatter
    new_digits = digit_formatter % int(ndigits + offset)

    # or as a one-liner:
    newdigits = ‘\%0%dd’ % len(ndigits) % int(ndigits + offset)

    or, retain your length calculations and simply do:

    pad_len = len(ndigits) – len(newdigits)
    newdigits = (‘0’ * pad_len) + newdigits

  2. Thanks Tim, the \ vs / is an especially good catch. Looks like you need to upload and link to an improved version ;-)

  3. 3 Tim Swan

    that’s the kind of thing that I’d write in the python REPL rather than script up ;)

    normally my filenames are sensibly numbered starting at 01, and I’m doing a rename from something like “b5_s01_e03.avi’ to ‘babylon.5.s01e03.avi’

    in_files = [ x for x in os.listdir(root_loc) if ‘.avi’ in x ]
    out_files = [ ‘babylon.5.s01e’ + ‘%02d’ % i + ‘.avi’ for i,x in enumerate(in_files) ]
    [ os.rename(a,b) for a,b in zip(in_files, out_files) ]

    1 minute of typing.

    subsitute your string mangling of choice to rename.

    Did I tell you I love Python?

    It starts to get a bit creaky when you’re talking a few hundred modules and very occasionally the lack of static typing can get in the way, but the other 99% of the time it’s an utter joy to use.

    a couple of useful modules to learn: xml.etree.ElementTree, subprocess, re, multiprocessing, json, httplib, urllib2.

    after that, look at Google AppEngine or Django, and you’re off and running ;)

    • As I said at the beginning I wasn’t trying to do straight s/foo/bar type renaming. I could do that with sed from the command line, and my favourite file mangler Beyond Compare has features to deal with that.

      The challenge is when you’ve got a bunch of files like:

      VTS01-PGC02.VOB
      VTS01-PGC03.VOB

      VTS01-PGC07.VOB

      and you want them to be some_show_e01_s01..06 (and of course the next bunch of VOBs will probably also be numbered 02..07 but they need to become 07..12).

  4. I’ve dropped the code into GitHub


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: