#!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = "Markus Stumpf " __source__ = "http://software.maexotic.de/python/cleanthumbs/>" __version = "1.0" __license__ = """Copyright (c) 2010 Markus Stumpf. All rights reserved. See http://software.maexotic.de/python/cleanthumbs/license.txt for the full text of the license.""" import os import sys import getopt import Image from urlparse import urlparse from urllib import unquote # known software that creates the thumbnails - currently unused KNOWN_SOFTWARE = ( 'GNOME::ThumbnailFactory', 'GIMP 2.6.8' ) # directories with thumbnails to check dirs_to_check = ( ".thumbnails", ) # default controls term_color = { "bold" : "\033[1m", "norm" : "\033[m", } # options with defaults opts = { "always-remove" : None, "dry-run" : False, "stderr-terminal" : True, "keep-nonlocal" : False, "verbose" : 1, } # holds the number of thumbnails that where chacked / deleted thumbs_checked = 0 thumbs_removed = 0 # ------------------------------------------------------------------------ def help(): hmsg = """ options are: --always-remove=PATH always remove thumbnails of local images whose location starts with PATH. Be sure to add a trailing "/" if you want a directory. May be specified more than once. Eg. --always-remove=/media/cdrom/ -h, --help display this message -k, --keep-nonlocal don't delete thumbnails that are not associated with local files (eg. http://..) -q, --quiet suppress non-error messages -n, --dry-run trial run, don't delete any thumbnails. Most useful with increased verbosity -v, --verbose increase verbosity. May be specified more than once (more than 2 currently useless). """ sys.stdout.write(hmsg) sys.stdout.flush() sys.exit(0) # ------------------------------------------------------------------------ def warn(m): """write messages to stderr eventually highlighting them""" if opts['stderr-terminal']: sys.stderr.write(term_color["bold"]) sys.stderr.write(m.rstrip("\r\n")) if opts['stderr-terminal']: sys.stderr.write(term_color["norm"]) sys.stderr.write("\n") sys.stderr.flush() return # ------------------------------------------------------------------------ def termsetup(): """try to setup enhanced output mode for highlighting messages""" # if stdout and stderr are both to a terminal use # terminal control codes to make warnings bold try: import curses except Exception, e: opts["stderr-terminal"] = False if 1 < opts["verbose"]: warn("Warning: %s: disabling enhanced terminal support" % e) return # got curses, now try to setup the terminal and get control codes try: curses.setupterm() term_color["bold"] = curses.tigetstr("bold") term_color["norm"] = curses.tigetstr("sgr0") except Exception, e: opts["stderr-terminal"] = False if 1 < opts["verbose"]: warn("Warning: %s: disabling enhanced terminal support" % e) warn("Warning: %s: disabling enhanced terminal support" % e) return # check if stderr is to a terminal # if it is not, don't use codes try: os.ttyname(file.fileno(sys.stderr)) opts["stderr-terminal"] = True except OSError: opts["stderr-terminal"] = False return # ok, stderr is to a terminal # now check if stdout is also to a terminal # if stdout is different from stderr, don't use codes try: os.ttyname(file.fileno(sys.stdout)) except OSError: opts["stderr-terminal"] = False return # ------------------------------------------------------------------------ def parse_options(): longopts = [ "always-remove=", "dry-run", "help", "keep-nonlocal", "quiet", "verbose=" ] try: gopts, cmds = getopt.getopt(sys.argv[1:], 'hknqv', longopts) except getopt.GetoptError, m: sys.stderr.write("Error: %s\n" % m) sys.stderr.flush() sys.exit(1) if cmds: sys.stderr.write("Error: extraneous parameter(s): %s\n" % (", ".join(cmds))) sys.stderr.flush() sys.exit(1) for o, a in gopts: if o == "--always-remove": if None is opts["always-remove"]: opts["always-remove"] = [ a ] else: opts["always-remove"].append(a) elif o == "-h" or o == "--help": help() elif o == "-k" or o == "--keep-nonlocal": opts["keep-nonlocal"] = True elif o == "-n" or o == "--dry-run": opts["dry-run"] = True elif o == "-q" or o == "--quiet": opts["verbose"] = 0 elif o == "-v": opts["verbose"] += 1 elif o == "--verbose": opts["verbose"] = int(a) else: sys.stderr.write("Internal error: cannot handle option '%s'\n" % o) sys.stderr.flush() sys.exit(1) return # ------------------------------------------------------------------------ def have_imagefile(uri): """parse the URI found in the PNG file and check if image exists""" (scheme, netloc, path, params, query, fragment) = urlparse(unquote(uri)) # check if the local image still exists if "file" == scheme: if opts["always-remove"]: for p in opts["always-remove"]: if path.startswith(p): return(False) return os.path.isfile(path) # schemes that are not "file" (like "http") cannot be checked easily return opts["keep-nonlocal"] # ------------------------------------------------------------------------ def handle_dir(dir): global thumbs_removed global thumbs_checked if 1 < opts["verbose"]: print "cleaning directory '%s'" % dir for fn in os.listdir(dir): ifn = os.path.join(dir, fn) if 2 < opts["verbose"]: print "checking '%s'" % ifn if not os.path.isfile(ifn): handle_dir(ifn) continue thumbs_checked += 1 if not ifn.endswith(".png"): warn("Warning: does not end with .png: '%s'" % ifn) continue try: img = Image.open(ifn) except Exception, e: warn("Warning: Image.open() failed: %s" % e) warn(" for file '%s'" % ifn) continue # if img.info.has_key("Software"): # software = img.info["Software"] # if "GNOME::ThumbnailFactory" != software: # print "Info: creator '%s'" % software # can only do the job if there is info about # the image the thumb is created from if img.info.has_key("Thumb::URI"): uri = img.info["Thumb::URI"] un_uri = unquote(uri) if 2 < opts["verbose"]: print " uri '%s'" % uri if not have_imagefile(uri): if not opts["dry-run"]: if 1 < opts["verbose"]: print "removing '%s' (%s)" % (ifn, un_uri) try: os.unlink(ifn) thumbs_removed += 1 except Exception, e: warn("Error: %s: file '%s'" % (e, ifn)) else: if 1 < opts["verbose"]: print "dry-run: not removing '%s' (%s)" % (ifn, un_uri) else: warn("Warning: Thumb::URI not found in '%s'" % ifn) # ------------------------------------------------------------------------ if __name__ == "__main__": parse_options() termsetup() homedir = os.path.expanduser("~") for d in dirs_to_check: dir = os.path.join(homedir, d) if not os.path.exists(dir): warn("Warning: no such file or directory: '%s'" % dir) continue if not os.path.isdir(dir): warn("Warning: not a directory: '%s'" % dir) continue handle_dir(dir) if 0 < opts["verbose"]: print "%d of %d thumbnail image(s) have been removed." % \ (thumbs_removed, thumbs_checked) sys.exit(0)