diff --git a/exif-header.py b/exif-header.py
new file mode 100644
index 0000000..5e0a2fa
--- /dev/null
+++ b/exif-header.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+#-*- coding: utf-8 -*-
+
+# Stéganô - Stéganô is a basic Python Steganography module.
+# Copyright (C) 2010-2011 Cédric Bonhomme - http://cedricbonhomme.org/
+#
+# For more information : http://bitbucket.org/cedricbonhomme/stegano/
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see
+
+__author__ = "Cedric Bonhomme"
+__version__ = "$Revision: 0.1 $"
+__date__ = "$Date: 2010/03/24 $"
+__license__ = "GPLv3"
+
+# Thanks to: http://www.julesberman.info/spec2img.htm
+
+
+def hide(img, img_enc, copyright="http://bitbucket.org/cedricbonhomme/stegano"):
+ """
+ """
+ import shutil
+ import datetime
+ from exif.minimal_exif_writer import MinimalExifWriter
+
+ file = open("lorem_ipsum.txt", "r")
+ text = "\nImage annotation date: "
+ text = text + str(datetime.date.today())
+ text = text + "\nImage description:\n"
+ text = text + file.read()
+ file.close()
+
+ try:
+ shutil.copy(img, img_enc)
+ except Exception as e:
+ print("Impossible to copy image:", e)
+ return
+
+ f = MinimalExifWriter(img_enc)
+ f.removeExif()
+ f.newImageDescription(text)
+ f.newCopyright(copyright, addYear = 1)
+ f.process()
+
+
+def reveal(img):
+ """
+ """
+ from exif.minimal_exif_reader import MinimalExifReader
+ try:
+ g = MinimalExifReader(img)
+ except:
+ print("Impossible to read description.")
+ return
+ print(g.imageDescription())
+ print(("Copyright " + g.copyright()))
+ #print g.dateTimeOriginal()s
+
+
+if __name__ == "__main__":
+ hide(img='./pictures/Elisha-Cuthbert.jpg', img_enc='./pictures/Elisha-Cuthbert_enc.jpg')
+ reveal(img='./pictures/Elisha-Cuthbert_enc.jpg')
\ No newline at end of file
diff --git a/exif/minimal_exif_reader.py b/exif/minimal_exif_reader.py
new file mode 100644
index 0000000..5f5794a
--- /dev/null
+++ b/exif/minimal_exif_reader.py
@@ -0,0 +1,197 @@
+"""
+This module offers one class, MinimalExifReader. Pass jpg filename
+to the constructor. Will read minimal exif info from the file. Three
+"public" functions available:
+imageDescription()--returns Exif ImageDescription tag (0x010e) contents,
+ or '' if not found.
+copyright()--returns Exif copyright tag (0x8298) contents, or '' if not
+ found.
+dateTimeOriginal()--returns Exif DateTimeOriginal tag (0x9003) contents,
+ or '' if not found. If found, the trailing nul char
+ is stripped. This function also takes an optional
+ format string to apply time.strftime-style formatting
+ to the date time.
+
+Brought to you by Megabyte Rodeo Software.
+"""
+
+# Written by Chris Stromberger, 10/2004. Public Domain.
+# Much is owed to Thierry Bousch's exifdump.py:
+# http://topo.math.u-psud.fr/~bousch/exifdump.py
+
+#---------------------------------------------------------------------
+class ExifFormatException(Exception):
+ pass
+
+#---------------------------------------------------------------------
+class MinimalExifReader:
+ IMAGE_DESCRIPTION_TAG = 0x010e
+ COPYRIGHT_TAG = 0x8298
+ EXIF_SUBIFD_TAG = 0x8769
+ DATE_TIME_ORIGINAL_TAG = 0x9003
+
+ #---------------------------------------
+ def __init__(self, filename):
+ """Pass in jpg exif file name to process. Will attempt to find tags
+ of interest."""
+
+ self.tagsToFind = {self.IMAGE_DESCRIPTION_TAG:'',
+ self.COPYRIGHT_TAG:'',
+ self.DATE_TIME_ORIGINAL_TAG:''}
+
+ # Read first bit of file to see if exif file.
+ f = open(filename, 'rb')
+ firstTwoBytes = f.read(2)
+ if firstTwoBytes != '\xff\xd8':
+ f.close()
+ raise ExifFormatException("Missing SOI marker")
+
+ appMarker = f.read(2)
+ # See if there's an APP0 section, which sometimes appears.
+ if appMarker == '\xff\xe0':
+ #print "Skipping app0"
+ # Yes, we have app0. Skip over it.
+ app0DataLength = ord(f.read(1)) * 256 + ord(f.read(1))
+ app0 = f.read(app0DataLength - 2)
+ appMarker = f.read(2)
+
+ if appMarker != '\xff\xe1':
+ raise ExifFormatException("Can't find APP1 marker")
+
+ exifHeader = f.read(8)
+ #import binascii
+ #print binascii.hexlify(exifHeader)
+ if (exifHeader[2:6] != 'Exif' or
+ exifHeader[6:8] != '\x00\x00'):
+ f.close()
+ raise ExifFormatException("Malformed APP1")
+
+ app1DataLength = ord(exifHeader[0]) * 256 + ord(exifHeader[1])
+ #print app1DataLength
+
+ # Read exif info starting at the beginning of the self.tiff section.
+ # This is 8 bytes into the app1 section, so subtract 8 from
+ # app1 length.
+ self.tiff = f.read(app1DataLength - 8)
+ f.close()
+
+ self.endian = self.tiff[0]
+ if self.endian not in ('I', 'M'):
+ raise ExifFormatException("Invalid endianess found: %s" % self.endian)
+
+ # Now navigate to the items of interest and get them.
+ ifdStart = self.getValueAtLocation(4, 4)
+ self.ifdSearch(ifdStart)
+
+ #---------------------------------------
+ def imageDescription(self):
+ """Return image description tag contents or '' if not found."""
+
+ return self.tagsToFind[self.IMAGE_DESCRIPTION_TAG].strip('\x20\x00')
+
+ #---------------------------------------
+ def copyright(self):
+ """Return copyright tag contents or '' if not found."""
+
+ return self.tagsToFind[self.COPYRIGHT_TAG].strip('\x20\x00')
+
+ #---------------------------------------
+ def dateTimeOriginal(self, formatString = None):
+ """Pass in optional format string to get time.strftime style formatting,
+ else get default exif format for date time string (without trailing nul).
+ Returns '' if tag not found."""
+
+ # The datetime should end in nul, get rid of it.
+ if formatString is None or not self.tagsToFind[self.DATE_TIME_ORIGINAL_TAG]:
+ return self.tagsToFind[self.DATE_TIME_ORIGINAL_TAG].strip('\x20\x00')
+ else:
+ # This will only work if the datetime string is in the standard exif format (i.e., hasn't been altered).
+ try:
+ import time
+ return time.strftime(formatString, time.strptime(self.tagsToFind[self.DATE_TIME_ORIGINAL_TAG].strip('\x20\x00'), '%Y:%m:%d %H:%M:%S'))
+ except:
+ return self.tagsToFind[self.DATE_TIME_ORIGINAL_TAG].strip('\x20\x00')
+
+
+ #---------------------------------------
+ def ifdSearch(self, ifdStart):
+ numIfdEntries = self.getValueAtLocation(ifdStart, 2)
+ tagsStart = ifdStart + 2
+ for entryNum in range(numIfdEntries):
+ # For my purposes, all files will have either no tags, or
+ # only our tags of interest, so no need to waste time trying to
+ # break out of the loop early.
+ thisTagStart = tagsStart + 12 * entryNum
+ tagId = self.getValueAtLocation(thisTagStart, 2)
+ if tagId == self.EXIF_SUBIFD_TAG:
+ # This is a special tag that points to another ifd. Our
+ # date time original tag is in the sub ifd.
+ self.ifdSearch(self.getTagValue(thisTagStart))
+ elif tagId in self.tagsToFind:
+ assert(not self.tagsToFind[tagId])
+ self.tagsToFind[tagId] = self.getTagValue(thisTagStart)
+
+ #---------------------------------------
+ def getValueAtLocation(self, offset, length):
+ slice = self.tiff[offset:offset + length]
+ if self.endian == 'I':
+ val = self.s2n_intel(slice)
+ else:
+ val = self.s2n_motorola(slice)
+ return val
+
+ #---------------------------------------
+ def s2n_motorola(self, str):
+ x = 0
+ for c in str:
+ x = (x << 8) | ord(c)
+ return x
+
+ #---------------------------------------
+ def s2n_intel(self, str):
+ x = 0
+ y = 0
+ for c in str:
+ x = x | (ord(c) << y)
+ y = y + 8
+ return x
+
+ #---------------------------------------
+ def getTagValue(self, thisTagStart):
+ datatype = self.getValueAtLocation(thisTagStart + 2, 2)
+ numBytes = [ 1, 1, 2, 4, 8, 1, 1, 2, 4, 8 ] [datatype-1] * self.getValueAtLocation(thisTagStart + 4, 4)
+ if numBytes > 4:
+ offsetToValue = self.getValueAtLocation(thisTagStart + 8, 4)
+ return self.tiff[offsetToValue:offsetToValue + numBytes]
+ else:
+ if datatype == 2 or datatype == 1 or datatype == 7:
+ return self.tiff[thisTagStart + 8:thisTagStart + 8 + numBytes]
+ else:
+ return self.getValueAtLocation(thisTagStart + 8, numBytes)
+
+ #---------------------------------------
+ def __str__(self):
+ return str(self.tagsToFind)
+
+#---------------------------------------------------------------------
+if __name__ == '__main__':
+ import sys
+ if len(sys.argv) == 1:
+ print "Pass jpgs to process."
+ sys.exit(1)
+
+
+ for filename in sys.argv[1:]:
+ try:
+ f = MinimalExifReader(filename)
+ print filename
+ print "description: '%s'" % f.imageDescription()
+ print "copyright: '%s'" % f.copyright()
+ print "dateTimeOriginal: '%s'" % f.dateTimeOriginal()
+ print "dateTimeOriginal: '%s'" % f.dateTimeOriginal('%B %d, %Y %I:%M:%S %p')
+ print
+ except ExifFormatException, ex:
+ sys.stderr.write("Exif format error: %s\n" % ex)
+ except:
+ sys.stderr.write("Unable to process %s\n" % filename)
+
diff --git a/exif/minimal_exif_writer.py b/exif/minimal_exif_writer.py
new file mode 100644
index 0000000..11c8be3
--- /dev/null
+++ b/exif/minimal_exif_writer.py
@@ -0,0 +1,457 @@
+"""
+Offers one class, MinimalExifWriter, which takes a jpg filename
+in the constructor. Allows you to: remove exif section, add
+image description, add copyright. Typical usage:
+
+f = MinimalExifWriter('xyz.jpg')
+f.newImageDescription('This is a photo of something very interesting!')
+f.newCopyright('Jose Blow, All Rights Reserved', addCopyrightYear = 1)
+f.process()
+
+Class methods:
+newImageDescription(description)--will add Exif ImageDescription to file.
+
+newCopyright(copyright, addSymbol = 0, addYear = 0)--will add Exif Copyright to file.
+ Will optionally prepend copyright symbol, or copyright symbol and current year.
+
+removeExif()--will obliterate existing exif section.
+
+process()--call after calling one or more of the above. Will remove existing exif
+ section, optionally saving some existing tags (see below), and insert a new exif
+ section with only three tags at most: description, copyright and date time original.
+ If removeExif() not called, existing description (or new description if newDescription()
+ called), existing copyright (or new copyright if newCopyright() called) and existing
+ "DateTimeOriginal" (date/time picture taken) tags will be rewritten to the new
+ minimal exif section.
+
+Run at comand line with no args to see command line usage.
+
+Does not work on unix due to differences in mmap. Not sure what's up there--
+don't need it on unix!
+
+Brought to you by Megabyte Rodeo Software.
+http://www.fetidcascade.com/pyexif.html
+"""
+
+# Written by Chris Stromberger, 10/2004. Public Domain.
+# Last updated: 12/3/2004.
+
+DUMP_TIFF = 0
+VERBOSE = 0
+if VERBOSE:
+ import binascii
+
+import mmap
+import sys
+import minimal_exif_reader
+
+#---------------------------------------------------------------------
+class ExifFormatException(Exception):
+ pass
+
+#---------------------------------------------------------------------------
+class MinimalExifWriter:
+ SOI_MARKER = '\xff\xd8'
+ APP0_MARKER = '\xff\xe0'
+ APP1_MARKER = '\xff\xe1'
+
+ # Standard app0 segment that will work for all files. We hope.
+ # Based on http://www.funducode.com/freec/Fileformats/format3/format3b.htm.
+ APP0 = '\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00'
+
+ def __init__(self, filename):
+ self.filename = filename
+ self.removeExifSection = 0
+ self.description = None
+ self.copyright = None
+ self.dateTimeOriginal = None
+
+ #---------------------------------------------
+ def newImageDescription(self, description):
+ self.description = description
+
+ #---------------------------------------------
+ def newCopyright(self, copyright, addSymbol = 0, addYear = 0):
+ if addYear:
+ import time
+ year = time.localtime()[0]
+ self.copyright = "\xa9 %s %s" % (year, copyright)
+ elif addSymbol:
+ self.copyright = "\xa9 %s" % copyright
+ else:
+ self.copyright = copyright
+
+ #---------------------------------------------
+ def removeExif(self):
+ self.removeExifSection = 1
+
+ #---------------------------------------------
+ def process(self):
+ if not self.removeExifSection:
+ self.getExistingExifInfo()
+
+ if VERBOSE:
+ print self
+
+ import os
+ try:
+ fd = os.open(self.filename, os.O_RDWR)
+ except:
+ sys.stderr.write('Unable to open "%s"\n' % filename)
+ return
+
+ self.m = mmap.mmap(fd, 0)
+ os.close(fd)
+
+ # We only add app0 if all we're doing is removing the exif section.
+ justRemovingExif = self.description is None and self.copyright is None and self.removeExifSection
+ if VERBOSE: print 'justRemovingExif=%s' % justRemovingExif
+ self.removeExifInfo(addApp0 = justRemovingExif)
+ if justRemovingExif:
+ self.m.close()
+ return
+
+ # Get here means we are adding new description and/or copyright.
+ self.removeApp0()
+
+ totalTagsToBeAdded = len(filter(None, (self.description, self.copyright, self.dateTimeOriginal)))
+ assert(totalTagsToBeAdded > 0)
+
+ # Layout will be: firstifd|description|copyright|exififd|datetime.
+ # First ifd will have tags: desc|copyright|subifd tag.
+ ifd = [self.twoBytesHexIntel(totalTagsToBeAdded)]
+ ifdEnd = ['\x00\x00\x00\x00']
+ NUM_TAGS_LEN = 2
+ TAG_LEN = 12
+ NEXT_IFD_OFFSET_LEN = 4
+ TIFF_HEADER_LENGTH = 8
+ ifdLength = NUM_TAGS_LEN + TAG_LEN * totalTagsToBeAdded + NEXT_IFD_OFFSET_LEN
+
+ # Subifd only has one tag.
+ SUBIFD_LENGTH = NUM_TAGS_LEN + TAG_LEN + NEXT_IFD_OFFSET_LEN
+
+ offsetToEndOfData = ifdLength + TIFF_HEADER_LENGTH
+
+ if self.description:
+ ifd.append(self.descriptionTag(len(self.description), offsetToEndOfData))
+ ifdEnd.append(self.description)
+ offsetToEndOfData += len(self.description)
+
+ if self.copyright:
+ ifd.append(self.copyrightTag(len(self.copyright), offsetToEndOfData))
+ ifdEnd.append(self.copyright)
+ offsetToEndOfData += len(self.copyright)
+
+ if self.dateTimeOriginal:
+ ifd.append(self.subIfdTag(offsetToEndOfData))
+ offsetToEndOfData += SUBIFD_LENGTH
+ ifdEnd.append(self.buildSubIfd(len(self.dateTimeOriginal), offsetToEndOfData))
+ ifdEnd.append(self.dateTimeOriginal)
+
+ app1 = self.buildApp1Section(ifd, ifdEnd)
+
+ self.addApp1(app1)
+
+ self.m.close()
+
+ #---------------------------------------------
+ # Build exif subifd with one tag for datetime (0x9003).
+ # Type is ascii (0x0002).
+ def buildSubIfd(self, lenDateTime, offsetToEndOfData):
+ return '\x01\x00\x03\x90\x02\x00%s%s\x00\x00\x00\x00' % (self.fourBytesHexIntel(lenDateTime), self.fourBytesHexIntel(offsetToEndOfData))
+
+ #---------------------------------------------
+ def getExistingExifInfo(self):
+ # Save off the old stuff.
+ try:
+ f = minimal_exif_reader.MinimalExifReader(self.filename)
+ except:
+ # Assume no existing exif info in the file. We
+ # don't care.
+ return
+
+ if not self.description:
+ self.description = f.imageDescription()
+
+ if not self.copyright:
+ self.copyright = f.copyright()
+
+ self.dateTimeOriginal = f.dateTimeOriginal()
+ if self.dateTimeOriginal:
+ # Restore ending nul.
+ if self.dateTimeOriginal[-1] != '\x00':
+ self.dateTimeOriginal += '\x00'
+
+ #---------------------------------------------------------------------------
+ def removeExifInfo(self, addApp0 = 1):
+ """Remove the app1 section of the jpg. This removes all exif info and the exif
+ thumbnail. addApp0 should be 1 to add a minimal app0 section right after soi
+ to make it a legitimate jpg, I think (various image programs can read the file
+ without app0, but I think the standard requires one).
+ """
+ # Read first bit of file to see if exif file.
+ self.m.seek(0)
+ if self.m.read(2) != self.SOI_MARKER:
+ self.m.close()
+ raise ExifFormatException("Missing SOI marker")
+
+ app0DataLength = 0
+ appMarker = self.m.read(2)
+ # See if there's an APP0 section, which sometimes appears.
+ if appMarker == self.APP0_MARKER:
+ if VERBOSE: print 'app0 found'
+ app0DataLength = ord(self.m.read(1)) * 256 + ord(self.m.read(1))
+ if VERBOSE: print 'app0DataLength: %s' % app0DataLength
+ # Back up 2 bytes to get the length bytes.
+ self.m.seek(-2, 1)
+ existingApp0 = self.m.read(app0DataLength)
+ appMarker = self.m.read(2)
+
+ if appMarker != self.APP1_MARKER:
+ # We don't care, we'll add our minimal app1 later.
+ return
+
+ exifHeader = self.m.read(8)
+ if VERBOSE: print 'exif header: %s' % binascii.hexlify(exifHeader)
+ if (exifHeader[2:6] != 'Exif' or
+ exifHeader[6:8] != '\x00\x00'):
+ self.m.close()
+ raise ExifFormatException("Malformed APP1")
+
+ app1Length = ord(exifHeader[0]) * 256 + ord(exifHeader[1])
+ if VERBOSE: print 'app1Length: %s' % app1Length
+
+ originalFileSize = self.m.size()
+
+ # Shift stuff just past app1 to overwrite app1.
+ # Start at app1 length bytes in + other bytes not incl in app1 length.
+ src = app1Length + len(self.SOI_MARKER) + len(self.APP1_MARKER)
+ if app0DataLength:
+ src += app0DataLength + len(self.APP0_MARKER)
+ dest = len(self.SOI_MARKER)
+ if addApp0:
+ if app0DataLength != 0:
+ # We'll re-add the existing app0.
+ dest += app0DataLength + len(self.APP0_MARKER)
+ else:
+ # Add our generic app0.
+ dest += len(self.APP0)
+ count = originalFileSize - app1Length - len(self.SOI_MARKER) - len(self.APP1_MARKER)
+ if app0DataLength:
+ count -= app0DataLength + len(self.APP0_MARKER)
+
+ if VERBOSE: print 'self.m.move(%s, %s, %s)' % (dest, src, count)
+ self.m.move(dest, src, count)
+
+ if addApp0:
+ if app0DataLength != 0:
+ self.m.resize(originalFileSize - app1Length - len(self.APP1_MARKER))
+ else:
+ self.m.seek(len(self.SOI_MARKER))
+ self.m.write(self.APP0)
+ self.m.resize(originalFileSize - app1Length - len(self.APP1_MARKER) + len(self.APP0))
+ else:
+ self.m.resize(originalFileSize - app1Length - len(self.APP1_MARKER))
+
+ #---------------------------------------------------------------------------
+ def removeApp0(self):
+ self.m.seek(0)
+ header = self.m.read(6)
+ if (header[0:2] != self.SOI_MARKER or
+ header[2:4] != self.APP0_MARKER):
+ if VERBOSE: print 'no app0 found: %s' % binascii.hexlify(header)
+ return
+
+ originalFileSize = self.m.size()
+
+ app0Length = ord(header[4]) * 256 + ord(header[5])
+ if VERBOSE: print 'app0Length:', app0Length
+
+ # Shift stuff to overwrite app0.
+ # Start at app0 length bytes in + other bytes not incl in app0 length.
+ src = app0Length + len(self.SOI_MARKER) + len(self.APP0_MARKER)
+ dest = len(self.SOI_MARKER)
+ count = originalFileSize - app0Length - len(self.SOI_MARKER) - len(self.APP0_MARKER)
+ self.m.move(dest, src, count)
+ if VERBOSE: print 'm.move(%s, %s, %s)' % (dest, src, count)
+ self.m.resize(originalFileSize - app0Length - len(self.APP0_MARKER))
+
+ #---------------------------------------------------------------------------
+ def addApp1(self, app1):
+ originalFileSize = self.m.size()
+
+ # Insert app1 section.
+ self.m.resize(originalFileSize + len(app1))
+ src = len(self.SOI_MARKER)
+ dest = len(app1) + len(self.SOI_MARKER)
+ count = originalFileSize - len(self.SOI_MARKER)
+ self.m.move(dest, src, count)
+ self.m.seek(len(self.SOI_MARKER))
+ self.m.write(app1)
+
+ #---------------------------------------------------------------------------
+ def fourBytesHexIntel(self, number):
+ return '%s%s%s%s' % (chr(number & 0x000000ff),
+ chr((number >> 8) & 0x000000ff),
+ chr((number >> 16) & 0x000000ff),
+ chr((number >> 24) & 0x000000ff))
+
+ #---------------------------------------------------------------------------
+ def twoBytesHexIntel(self, number):
+ return '%s%s' % (chr(number & 0x00ff),
+ chr((number >> 8) & 0x00ff))
+
+ #---------------------------------------------------------------------------
+ def descriptionTag(self, numChars, loc):
+ return self.asciiTag('\x0e\x01', numChars, loc)
+
+ #---------------------------------------------------------------------------
+ def copyrightTag(self, numChars, loc):
+ return self.asciiTag('\x98\x82', numChars, loc)
+
+ #---------------------------------------------------------------------------
+ def subIfdTag(self, loc):
+ return '\x69\x87\x04\x00\x01\x00\x00\x00%s' % self.fourBytesHexIntel(loc)
+
+ #---------------------------------------------------------------------------
+ def asciiTag(self, tag, numChars, loc):
+ """Create ascii tag. Assumes description > 4 chars long."""
+
+ return '%s\x02\x00%s%s' % (tag, self.fourBytesHexIntel(numChars), self.fourBytesHexIntel(loc))
+
+ #---------------------------------------------------------------------------
+ def buildApp1Section(self, ifdPieces, ifdEndPieces):
+ """Create the APP1 section of an exif jpg. Consists of exif header plus
+ tiff header + ifd and associated data."""
+
+ # Intel byte order, offset to first ifd will be 8.
+ tiff = 'II\x2a\x00\x08\x00\x00\x00%s%s' % (''.join(ifdPieces), ''.join(ifdEndPieces))
+ if DUMP_TIFF:
+ f = open('tiff.dump', 'wb')
+ f.write(tiff)
+ f.close()
+ app1Length = len(tiff) + 8
+ return '\xff\xe1%s%sExif\x00\x00%s' % (chr((app1Length >> 8) & 0x00ff), chr(app1Length & 0x00ff), tiff)
+
+ #---------------------------------------------------------------------------
+ def __str__(self):
+ return """filename: %(filename)s
+removeExifSection: %(removeExifSection)s
+description: %(description)s
+copyright: %(copyright)s
+dateTimeOriginal: %(dateTimeOriginal)s
+""" % self.__dict__
+
+#---------------------------------------------------------------------------
+def usage(error = None):
+ """Print command line usage and exit"""
+
+ if error:
+ print error
+ print
+
+ print """This program will remove exif info from an exif jpg, and can optionally
+add the ImageDescription exif tag and/or the Copyright tag. But it will always remove
+some or all existing exif info (depending on options--see below)!
+So don't run this on your original images without a backup.
+
+Options:
+ -h: shows this message.
+ -f : jpg to process (required).
+ -x: remove exif info (including thumbnail).
+ -d : remove exif info (including thumbnail) and then add exif
+ ImageDescription. Will save the existing copyright tag if present,
+ as well as the date time original tag (date & time photo taken),
+ unless -x also passed (-x always means remove all exif info).
+ It will attempt to open whatever is passed on the
+ command line as a file; if successful, the contents of the file
+ are added as the description, else the literal text on the
+ command line is used as the description.
+ -c : remove exif info (including thumbnail) and then add exif
+ Copyright tag. Will save the existing image description tag if present,
+ as well as the date time original tag (date & time photo taken),
+ unless -x also passed (-x always means remove all exif info).
+ It will attempt to open whatever is passed on the command line as a file;
+ if successful, the contents of the file are added as the copyright,
+ else the literal text on the command line is used as the copyright.
+ -s: prepend copyright symbol to copyright.
+ -y: prepend copyright symbol and current year to copyright.
+
+ The image description and copyright must be > 4 characters long.
+
+ This software courtesy of Megabyte Rodeo Software."""
+
+ sys.exit(1)
+
+#---------------------------------------------------------------------------
+def parseArgs(args_):
+ import getopt
+ try:
+ opts, args = getopt.getopt(args_, "yshxd:f:c:")
+ except getopt.GetoptError:
+ usage()
+
+ filename = None
+ description = ''
+ copyright = ''
+ addCopyrightSymbol = 0
+ addCopyrightYear = 0
+ removeExif = 0
+
+ for o, a in opts:
+ if o == "-h":
+ usage()
+ if o == "-f":
+ filename = a
+ if o == "-d":
+ try:
+ f = open(a)
+ description = f.read()
+ f.close()
+ except:
+ description = a
+ if o == "-c":
+ try:
+ f = open(a)
+ copyright = f.read()
+ f.close()
+ except:
+ copyright = a
+ if o == '-x':
+ removeExif = 1
+ if o == '-s':
+ addCopyrightSymbol = 1
+ if o == '-y':
+ addCopyrightYear = 1
+
+ if filename is None:
+ usage('Missing jpg filename')
+ if description and (len(description) <= 4 or len(description) > 60000):
+ usage('Description too short or too long')
+ if copyright and (len(copyright) <= 4 or len(copyright) > 60000):
+ usage('Copyright too short or too long')
+ if not description and not copyright and not removeExif:
+ usage('Nothing to do!')
+
+ return filename, description, copyright, removeExif, addCopyrightSymbol, addCopyrightYear
+
+#---------------------------------------------------------------------------
+if __name__ == '__main__':
+ try:
+ filename, description, copyright, removeExif, addCopyrightSymbol, addCopyrightYear = parseArgs(sys.argv[1:])
+ f = MinimalExifWriter(filename)
+ if description:
+ f.newImageDescription(description)
+ if copyright:
+ f.newCopyright(copyright, addCopyrightSymbol, addCopyrightYear)
+ if removeExif:
+ f.removeExif()
+
+ f.process()
+ except ExifFormatException, ex:
+ sys.stderr.write("Exif format error: %s\n" % ex)
+ except SystemExit:
+ pass
+ except:
+ sys.stderr.write("Unable to process %s\n" % filename)
+ raise
diff --git a/pictures/Elisha-Cuthbert.jpg b/pictures/Elisha-Cuthbert.jpg
new file mode 100644
index 0000000..ffaeb90
Binary files /dev/null and b/pictures/Elisha-Cuthbert.jpg differ