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.