#!/usr/bin/env python # # Copyright (c) 2006-2007, Hans Meine # All rights reserved. # # This is licensed according to the new BSD license. # Please send patches / comments, I would be happy about any feedback. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # * Neither the name of the University of Hamburg nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # # (Update: I am not happy with the program's structure anymore, the # useMime-code makes the rest quite ugly actually. :-( ) import sys, os, stat, select mediaExtensions = [ "jpg", "jpeg", "bmp", "png", "tif", "tiff", "jpe", "mpeg", "mpg", "avi", "mpe", "mov", "wmv", "mp3", "wav", "ogg", "aac", "mp2", "mpc", "wma", "pdf", "ps", "txt", "gz", "bz2", "zip", "arj", "doc", "xls", "ppt"] from optparse import OptionParser op = OptionParser( usage = "%prog [options] ...", description = "Renames files with uppercase filenames and removes executable bits from files with known media extensions.") op.add_option("-v", "--verbose", action = "store_true", default = True, dest = "verbosity", help = "verbose output (default)") op.add_option("-q", "--quiet", action = "store_false", dest = "verbosity", help = "quite mode (only errors)") op.add_option("-m", "--mime", action = "store_true", default = False, dest = "useMime", help = "use mime-output capable 'file' implementation for checking media files (default: list of extensions)") op.add_option("-f", "--force", action = "store_true", default = False, dest = "forceMode", help = "force overwriting existing files") op.add_option("-p", "--pretend", action = "store_true", default = False, dest = "pretend", help = "show actions, but do not perform them") op.add_option("-r", "--recursive", action = "store_true", default = False, dest = "recursive", help = "recurse into subdirectories") op.add_option("-e", "--deexecall", action = "store_true", default = False, dest = "deexecall", help = "remove exec bits from all regular files") op.add_option("-a", "--renameall", action = "store_true", dest = "renameAll", default = False, help = "rename files without extensions, too (default: only UPPERCASE.JPG, not README for example)") op.add_option("-i", "--irreversible", action = "store_true", dest = "irreversible", default = False, help = "rename really all files (even MixedCase, implies --renameall)") options, args = op.parse_args() if not len(args): sys.stderr.write("No files given - nothing to do!\n\n") op.print_help() def verbose(str): if options.verbosity: sys.stdout.write(str) # for performance reasons, "file" is called maximally once (if -m # switch was given), on all files with exec bits: # FIXME: check for os.sysconf("SC_ARG_MAX") if options.useMime: fileargs = ["file", "-i", "-N"] for fn in args: s = os.stat(fn) mode = stat.S_IMODE(s[stat.ST_MODE]) if stat.S_ISREG(s[stat.ST_MODE]) and mode & 0111: fileargs.append(fn) fileIsMedia = {} if len(fileargs) > 3: rd, wr = os.pipe() pid = os.fork() if not pid: os.close(rd) os.dup2(wr, sys.stdout.fileno()) os.close(wr) os.execvp("file", fileargs) sys.exit(0) os.close(wr) fileInfo = "" while rd: select.select([rd], [], []) fileOutput = os.read(rd, 20480) if not fileOutput: break fileInfo += fileOutput res = os.waitpid(pid, 0) if res[1]: sys.stderr.write("Sub-process for 'file' with PID %d returned %d (signal %d)\n" % (res[0], res[1] / 256, res[1] & 255)) sys.stderr.write(" Parameters were: %s\n" % (" ".join(fileargs), )) sys.exit(1) for fileInfoLine in fileInfo.split("\n"): fields = fileInfoLine.split(":") if len(fields) > 1: if fields[1].startswith(" image/") or \ fields[1].startswith(" video/") or \ fields[1].startswith(" text/") or \ fields[1].startswith(" audio/"): fileIsMedia[fields[0]] = mode def lowerIfUpper(str): if str == str.upper(): return str.lower() return str def checkExecMediaFile(filename): if options.useMime: return fileIsMedia.has_key(filename) and fileIsMedia[filename] s = os.stat(filename) if not stat.S_ISREG(s[stat.ST_MODE]): return False mode = stat.S_IMODE(s[stat.ST_MODE]) # AND chmod'able bits if mode & 0111: if options.deexecall: return mode basepath, ext = os.path.splitext(fn) if ext[1:].lower() in mediaExtensions: return mode return False chmodCount = 0 renameCount = 0 errorCount = 0 for fn in args: if os.path.isdir(fn): mode = False else: try: mode = checkExecMediaFile(fn) except OSError, e: if e.errno == 2: # "no such file or directory" on broken links continue raise # what else could happen? dir, filename = os.path.split(fn) basename, ext = os.path.splitext(filename) if options.irreversible: newFilename = filename.lower() elif not (len(ext) or options.renameAll): # handle exceptions like README, CHANGES, TODO, NEWS, LICENSE, INSTALL: newFilename = filename else: newFilename = lowerIfUpper(filename) if newFilename == filename: newFilename = basename + lowerIfUpper(ext) # verbose output if mode or (newFilename != filename): msg = "'%s'" % (fn, ) if mode: msg += " (-exec)" if newFilename != filename: msg += " -> '%s'" % (newFilename, ) verbose(msg + "\n") if mode: try: if not options.pretend: os.chmod(fn, mode & 07666) chmodCount += 1 except OSError, e: sys.stderr.write(str(e)+"\n") errorCount += 1 if newFilename != filename: lfn = os.path.join(dir, newFilename) if os.path.exists(lfn) and not options.forceMode: sys.stderr.write("WARNING: target '%s' exists! (skipping, use --force to overwrite)\n" % (lfn, )) else: try: if not options.pretend: os.rename(fn, lfn) fn = lfn renameCount += 1 except OSError, e: sys.stderr.write(str(e)+"\n") errorCount += 1 if os.path.isdir(fn) and options.recursive: for name in os.listdir(fn): args.append(os.path.join(fn, name)) prefix = "" if options.pretend: prefix = "[PRETEND MODE] " if chmodCount: verbose(prefix + "Removed executable bits from %d files.\n" % (chmodCount, )) if renameCount: verbose(prefix + "Renamed %d files.\n" % (renameCount, )) if errorCount: verbose("%d errors occured.\n" % (errorCount, ))