Created
December 23, 2013 05:18
-
-
Save codebycliff/8092026 to your computer and use it in GitHub Desktop.
Some python code for manipulating and managing photos.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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