Renumbering (and renaming) media files
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.
Filed under: code, media, software | 7 Comments
Tags: code, decrement, files, increment, media, python, rename, renumber, script, source
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
Thanks Tim, the \ vs / is an especially good catch. Looks like you need to upload and link to an improved version ;-)
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).
I’ve dropped the code into GitHub
Hi Chris.
I would like to test your code but as a neeewbee I am I do not know how to run it?
How to write the “commands”
You’ll need to download and install Python then make sure that python.exe and incdec.py are on your PATH.