Skip to content

Instantly share code, notes, and snippets.

@codebycliff
Created December 23, 2013 05:18
Show Gist options
  • Save codebycliff/8092026 to your computer and use it in GitHub Desktop.
Save codebycliff/8092026 to your computer and use it in GitHub Desktop.
Some python code for manipulating and managing photos.
#!/usr/bin/env python
""" This module consists of several classes that help with image management
and image manipulation. It also contains several functions that together
make up complete program, and therefore this module can be executed as a
script itself. It also contains a work-in-progress shell for performing
many of the available operations interactively.
@author: Cliff Braton
@contact: [email protected]
"""
import sys, os.path, pyexiv2, shutil,commands
from optparse import OptionParser
META_TAGS_KEY = 'Iptc.Application2.Keywords'
""" @cvar META_TAGS_KEY: String key used by the pyexiv2.Image class for tags."""
EXPORT_LOG_NAME = "imageutils-export.log"
""" @cvar EXPORT_LOG_NAME: Name of exported log file used by command-line client. """
class Image:
""" Class that acts as a wrapper around the Image class from the pyexiv2
module. This class is primarily used by the ImageLibrary class
that is included in this module.
"""
__class__ = "pycc.imageutils.Image"
def __init__(self, imgpath):
""" Constructor for the Image class taking in the path to the image
for which this instance represents.
@param imgpath: Path to the image to represent.
"""
self._path = imgpath
self._image = pyexiv2.Image(self._path)
self._image.readMetadata()
if META_TAGS_KEY in self._image.iptcKeys():
self._tags = list(self._image[META_TAGS_KEY])
else:
self._tags = []
def __str__(self):
return self.filename();
def __writeTags(self):
""" Private helper method that encapsulates the writing of the metadata
to the images. After writing the metadata, the image is read back
in so that it contains the recent metadata changes.
"""
if len(self._tags) == 0:
self._image[META_TAGS_KEY] = tuple()
else:
self._image[META_TAGS_KEY] = tuple(self._tags)
self._image.writeMetadata()
self._image.readMetadata()
def caption(self):
""" Getter that returns the caption for the image.
@return: The caption of this image.
"""
return self._image.getCaption()
def comment(self):
""" Getter that returns the comment for the image.
@return: The comment for this image.
"""
return self._image.getComment()
def path(self):
""" Getter that returns the path to the image.
@return: The path to this image.
"""
return self._path
def filename(self):
""" Returns the filename for this image.
@return: The filename for the image.
"""
return os.path.basename(self._path);
def tags(self):
""" Getter that returns a list of tags for the image.
@return: List of tags/keywords associated with this image.
"""
return self._tags
def add_tag(self, tagname):
""" Method for adding a tag to the list of tags/keywords contained
within this image's metadata.
@param tagname: The tag to add to this image's metadata.
"""
self._tags.append(tagname)
print self._tags
self.__writeTags()
def remove_tag(self, tagname):
""" Method for removing a specific tag from the list of tags/keywords
contained within this image's metadata.
@param tagname: The name of tag to remove from this image's metadata.
"""
for tag in self._tags:
if tagname == tag:
self._tags.remove(tag)
self._writeTags()
return tag
return None
def clear_tags(self):
""" Method that clears (or removes) all the tags/keywords contained
within this image's metadata.
"""
self._tags = []
self.__writeTags()
class ImageLibrary:
""" Class representing a library or collection of images and provides
operations for querying and manipulating the images in the collection
either on a one by one basis or as the whole collection.
"""
__class__ = "pycc.imageutils.ImageLibrary"
def __init__(self, pathroot):
""" Constructor taking in the path to the root directory containing
the images to be added to the library.
@param pathroot: The root directory containing the images.
"""
self._rootdir = pathroot
self._images = []
self.file_patterns = [
".jpg",
".JPG",
".jpeg",
".JPEG",
".png",
".PNG",
".gif",
".GIF"
]
self.refresh()
def __getitem__(self, idx):
return self._images[idx]
def __iter__(self):
for img in self._images:
yield img
def __len__(self):
return len(self._images)
def refresh(self):
""" Method that walks the directory tree under the path passed into
the constructor recursively, searching for files that appaear
to be images and adds them to this instances list of images.
"""
for dirpath, dirs, files in os.walk(self._rootdir):
for f in files:
for pattern in self.file_patterns:
if f[0] == ".":
continue
elif pattern in f:
imgpath = os.path.join(dirpath,f)
try:
img = Image(imgpath)
self._images.append(img)
except Exception, e:
print "error creating image:" + imgpath
print e.message + "\n"
pass
def images_with_tag(self, tagname):
""" Method that returns a list containing all the images in the
current collection that have the tag specified in it's metadata.
@param tagname: The name of the tag to filter the return results by.
@return: List of images containing the specified tag.
"""
imglist = []
for i in self._images:
if tagname in i.tags():
imglist.append(i)
return imglist
def all_tags(self):
""" Method that returns a list of all tags known to the current
collection of images. This is found by taking a union of all
the images tags and returning the result.
@return: List of all known tags in library.
"""
imglist = set()
for i in self._images:
taglist = list(i.tags())
if len(taglist) == 0:
continue
elif len(taglist) == 1:
imglist.add(taglist)
else:
for t in taglist:
imglist.add(t)
return imglist
def filter_images(self, start=None, end=None, contains=None):
""" Method that filters out all images from that do not match the
specified filters. This method is destructive, in the sense that
once it is called, the image collection contains only those images
that matched the filter.
@param start: String filter that matches at the beginning of the file name.
@param end: String filter that matches at the end of the file name.
@param contains: String filter that matches anywhere within the file name.
"""
self._images = self.get_filtered_images(start, end, contains)
def get_filtered_images(self, start=None, end=None, contains=None):
""" Method that filters out all images from that do not match the
specified filters and returns a list containing those images that
matched the specified filters. This method leaves the entire
image collection intact.
@param start: String filter that matches at the beginning of the file name.
@param end: String filter that matches at the end of the file name.
@param contains: String filter that matches anywhere within the file name.
@return: List containing the images that matched the specified filters.
"""
filter_start = set(self._images)
filter_end = set(self._images)
filter_contains = set(self._images)
if start:
filter_start = set([i for i in self._images if str(i).startswith(start)])
if end:
filter_end = set([i for i in self._images if str(i).endswith(end)])
if contains:
filter_contains = set([i for i in self._images if contains in str(i)])
return filter_start | filter_end | filter_contains
def filter_images(src, start=None, end=None, contains=None):
""" Function providing a quick a dirty way to get a list of images from
a path that match specified filters. The source path is scanned for
all possible images and then filters out any images that don't match
start, end, or contains filter, if supplied
@param src: The path to the directory containing to the images to filter.
@param start: String filter that matches at the beginning of the file name.
@param end: String filter that matches at the end of the file name.
@param contains: String filter that matches anywhere within the file name.
@return: List of images that successfully match all supplied filters.
"""
imglib = ImageLibrary(src)
return imglib.get_filtered_images(start, end, contains)
def copy_images(images, dest):
""" Simple helper function that copies all images in the list passed in
to the destination directory specified.
@param images: List of images to copy.
@param dest: Path to the destination directory.
"""
for img in images:
destpath = os.path.join(dest, str(img))
shutil.copy(img.path(), destpath)
def move_images(images, dest):
""" Simple helper function that moves all images in the list passed in
to the destination directory specified.
@param images: List of images to move.
@param dest: Path to the destination directory.
"""
for img in images:
destpath = os.path.join(dest, str(img))
shutil.move(img.path(), destpath)
def export_images(images, dest):
""" Simple helper function that moves all images in the list passed in
to the destination directory specified and logs each move in a log
file in the same destination directory. This provides a way to easily
'undo' the export operation.
@param images: List of images to export.
@param dest: Path to the destination directory.
@see: import_images
"""
logfile_path = os.path.join(dest, EXPORT_LOG_NAME)
LOG = open(logfile_path, 'w')
for img in images:
print >> LOG, str(img) + "=" + img.path()
destpath = os.path.join(dest, str(img))
shutil.move(img.path(),destpath)
def import_images(path):
""" Provides a way to import files that were previously exported. The
path specified should correspond to previous exported directory, and
therefore, contain an export file. That export file, if found, is
read and all move operations previously performed are done in reverse,
restoring the exporting images to the original location and leaving
the directory specified by path empty, except for the log file.
@param path: Path to a previously exported-to directory.
"""
prev_logpath = os.path.join(path, EXPORT_LOG_NAME)
if not os.path.exists(prev_logpath):
raise Exception, "No log file found."
else:
LOG = open(prev_logpath, 'r')
for line in LOG:
line = line.rstrip()
src, dest = line.split('=')
srcfile = os.path.join(path, src)
if(os.path.exists(srcfile)):
shutil.move(srcfile,dest)
def tag_picture(path):
img = pyexiv2.Image(path);
img.readMetaData();
if 'Iptc.Application2.Kewords' in img.iptcKeys():
tags = list(img['Iptc.Application2.Keywords']);
tag = commands.getoutput("kdialog --input-box 'Enter tag'")
tags.append(tag)
img.writeMetadata()
def cli_main(argv):
""" Simple main function to run if this module is ran as a script. This
function handles option parsing and performing the actual operation
that the user requested.
@param argv: List of command-line arguments (directly from sys.argv)
"""
usage = "USAGE: imgutil command [options] src [dest]"
parser = OptionParser(usage)
parser.add_option("-s", "--starts-with", type="string", default=None,
metavar="PATTERN", dest="start_pattern",
help="Apply filter match at beginning of file names.")
parser.add_option("-e", "--ends-with", type="string", default=None,
metavar="PATTERN", dest="end_pattern",
help="Apply filter match at end of file names.")
parser.add_option("-c", "--name-contains", type="string", default=None,
metavar="PATTERN", dest="contains_pattern",
help="Apply filter regardless of where it appears in the file name")
parser.add_option("-M", action="store_true", dest="move_files",
help="Move the files instead of copying them")
parser.add_option("-E", action="store_true", dest="export_files",
help="Export images. Writes a log to the destination that can be used to import the images to their orignal location via the import options (-I)")
parser.add_option("-I", action="store_true", dest="import_files",
help="Look for export log and if found, use it to import files back to original location.")
parser.add_option("-L", action="store_true", dest="list_files",
help="List files that match the options provided")
(opts, args) = parser.parse_args()
source = args[0]
if opts.import_files:
try:
import_images(source)
sys.exit(0)
except Exception, e:
print "Error: Problem importing images: ", e.message
sys.exit(-1)
else:
matched_images = filter_images(source, opts.start_pattern,
opts.end_pattern, opts.contains_pattern)
if opts.list_files:
for img in matched_images:
print img
else:
dest = args[1]
if opts.export_files:
export_images(matched_images, dest)
elif opts.move_files:
move_images(matched_images, dest)
else:
copy_images(matched_images, dest)
if __name__ == "__main__":
cli_main(sys.argv);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment