X-Git-Url: https://scm.cri.minesparis.psl.eu/git/Photo.git/blobdiff_plain/b0a7e10b4f32cf74864bb53268ca4d3080f23bc0..6c41809185e322ce2d30e98234f71144f78f06c0:/exif.py?ds=sidebyside diff --git a/exif.py b/exif.py deleted file mode 100755 index 8120852..0000000 --- a/exif.py +++ /dev/null @@ -1,374 +0,0 @@ -# -*- coding: utf-8 -*- -####################################################################################### -# Photo is a part of Plinn - http://plinn.org # -# Copyright © 2008 Benoît PIN # -# # -# 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 2 # -# 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, write to the Free Software # -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # -####################################################################################### -""" Exif version 2.2 read/write module. - - - -""" - -TYPES_SIZES = { - 1: 1 # BYTE An 8-bit unsigned integer., - , 2: 1 # ASCII An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL., - , 3: 2 # SHORT A 16-bit (2-byte) unsigned integer, - , 4: 4 # LONG A 32-bit (4-byte) unsigned integer, - , 5: 8 # RATIONAL Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator., - , 7: 1 # UNDEFINED An 8-bit byte that can take any value depending on the field definition, - , 9: 4 # SLONG A 32-bit (4-byte) signed integer (2's complement notation), - , 10 : 8 # SRATIONAL Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator. -} - -# tags for parsing metadata -Exif_IFD_POINTER = 0x8769 -GPS_INFO_IFD_POINTER = 0x8825 -INTEROPERABILITY_IFD_POINTER = 0xA005 - -# tags to get thumbnail -COMPRESSION_SCHEME = 0x103 -COMPRESSION_SCHEME_TYPES = {1:'image/bmp', 6:'image/jpeg'} -OFFSET_TO_JPEG_SOI = 0x201 -BYTES_OF_JPEG_DATA = 0x202 -STRIPOFFSETS = 0x111 -STRIPBYTECOUNTS = 0x117 - -# constants for writing -INTEROPERABILITY_FIELD_LENGTH = 12 -POINTER_TAGS = { Exif_IFD_POINTER:True - , GPS_INFO_IFD_POINTER:True - , INTEROPERABILITY_IFD_POINTER:True} - - -class Exif(dict) : - - def __init__(self, f) : - # File Headers are 8 bytes as defined in the TIFF standard. - self.f = f - - byteOrder = f.read(2) - self.byteOrder = byteOrder - - if byteOrder == 'MM' : - r16 = self.r16 = lambda:ib16(f.read(2)) - r32 = self.r32 = lambda:ib32(f.read(4)) - elif byteOrder == 'II' : - r16 = self.r16 = lambda:il16(f.read(2)) - r32 = self.r32 = lambda:il32(f.read(4)) - else : - raise ValueError, "Unkwnown byte order: %r" % byteOrder - - assert r16() == 0x002A, "Incorrect exif header" - - self.tagReaders = { - 1: lambda c : [ord(f.read(1)) for i in xrange(c)] - , 2: lambda c : f.read(c) - , 3: lambda c : [r16() for i in xrange(c)] - , 4: lambda c : [r32() for i in xrange(c)] - , 5: lambda c : [(r32(), r32()) for i in xrange(c)] - , 7: lambda c : f.read(c) - , 9: lambda c : [r32() for i in xrange(c)] - , 10: lambda c : [(r32(), r32()) for i in xrange(c)] - } - - self.tagInfos = {} - self.mergedTagInfos = {} - self.gpsTagInfos = {} - - ifd0Offset = r32() - - ifd1Offset = self._loadTagsInfo(ifd0Offset, 'IFD0') - others = [(lambda:self[Exif_IFD_POINTER], 'Exif'), - (lambda:self.get(GPS_INFO_IFD_POINTER), 'GPS'), - (lambda:self.get(INTEROPERABILITY_IFD_POINTER), 'Interoperability'), - (lambda:ifd1Offset, 'IFD1')] - - self.ifdnames = ['IFD0'] - - for startfunc, ifdname in others : - start = startfunc() - if start : - ret = self._loadTagsInfo(start, ifdname) - assert ret == 0 - self.ifdnames.append(ifdname) - - - def _loadTagsInfo(self, start, ifdname) : - r16, r32 = self.r16, self.r32 - - self.f.seek(start) - - numberOfFields = r16() - ifdInfos = self.tagInfos[ifdname] = {} - - for i in xrange(numberOfFields) : - # 12 bytes of the field Interoperability - tag = r16() - typ = r16() - count = r32() - - ts = TYPES_SIZES[typ] - size = ts * count - - # In cases where the value fits in 4 bytes, - # the value itself is recorded. - # If the value is smaller than 4 bytes, the value is - # stored in the 4-byte area starting from the left. - if size <= 4 : - offsetIsValue = True - offset = self.tagReaders[typ](count) - if count == 1: - offset = offset[0] - noise = self.f.read(4 - size) - else : - offsetIsValue = False - offset = r32() - - ifdInfos[tag] = (typ, count, offset, offsetIsValue) - - if ifdname == 'GPS' : - self.gpsTagInfos.update(ifdInfos) - else : - self.mergedTagInfos.update(ifdInfos) - - # return nexf ifd offset - return r32() - - def getThumbnail(self) : - if hasattr(self, 'ifd1Offset') : - comp = self[COMPRESSION_SCHEME] - if comp == 6 : - # TODO : handle uncompressed thumbnails - mime = COMPRESSION_SCHEME_TYPES.get(comp, 'unknown') - start = self[OFFSET_TO_JPEG_SOI] - count = self[BYTES_OF_JPEG_DATA] - f = self.f - f.seek(start) - data = f.read(count) - return data, mime - else : - return None - else : - return None - - - - # - # dict interface - # - def keys(self) : - return self.mergedTagInfos.keys() - - def has_key(self, key) : - return self.mergedTagInfos.has_key(key) - - __contains__ = has_key # necessary ? - - def __getitem__(self, key) : - typ, count, offset, offsetIsValue = self.mergedTagInfos[key] - if offsetIsValue : - return offset - else : - self.f.seek(offset) - value = self.tagReaders[typ](count) - if count == 1: - return value[0] - else : - return value - - def get(self, key) : - if self.has_key(key): - return self[key] - else : - return None - - def getIFDNames(self) : - return self.ifdnames - - - def getIFDTags(self, name) : - tags = [tag for tag in self.tagInfos[name].keys()] - tags.sort() - return tags - - - def save(self, out) : - byteOrder = self.byteOrder - - if byteOrder == 'MM' : - w16 = self.w16 = lambda i : out.write(ob16(i)) - w32 = self.w32 = lambda i : out.write(ob32(i)) - elif byteOrder == 'II' : - w16 = self.w16 = lambda i : out.write(ol16(i)) - w32 = self.w32 = lambda i : out.write(ol32(i)) - - tagWriters = { - 1: lambda l : [out.write(chr(i)) for i in l] - , 2: lambda l : out.write(l) - , 3: lambda l : [w16(i) for i in l] - , 4: lambda l : [w32(i) for i in l] - , 5: lambda l : [(w32(i[0]), w32(i[1])) for i in l] - , 7: lambda l : out.write(l) - , 9: lambda l : [w32(i) for i in l] - , 10: lambda l : [(w32(i[0]), w32(i[1])) for i in l] - } - - - # tiff header - out.write(self.byteOrder) - w16(0x002A) - tags = self.keys() - r32(8) # offset of IFD0 - ifdStarts = {} - pointerTags = [] - isPtrTag = POINTER_TAGS.has_key - - for ifdname in self.getIFDName() : - ifdInfos = self.tagInfos[name] - tags = ifdInfos.keys() - tags.sort() - - ifdStarts[ifdname] = out.tell() - - tiffOffset = ifdStarts[ifdname] + INTEROPERABILITY_FIELD_LENGTH * len(tags) + 4 - moreThan4bytesValuesTags = [] - - for tag, info in ifdInfos.items() : - if isPtrTag(tag) : - pointerTags.append((tag, out.tell())) - typ, count, offset, offsetIsValue = info - - w16(tag) - w16(typ) - w32(count) - - ts = TYPES_SIZES[typ] - size = ts * count - - if size <= 4 : - if count == 1 : offset = [offset] - tagWriters[typ](offset) - - # padding - for i in range(4 - size) : out.write('\0') - else : - w32(tiffOffset) - tiffOffset += size - moreThan4bytesValuesTags.append(tag) - - for tag in moreThan4bytesValuesTags : - typ, count, offset, offsetIsValue = ifdInfos[tag] - self.f.seek(offset) - size = TYPES_SIZES[typ] * count - out.write(self.f.read(size)) - - # write place-holder for next ifd offset (updated later) - r32(0) - - -def ib16(c): - return ord(c[1]) + (ord(c[0])<<8) -def ob16(i) : - return chr(i >> 8 & 255) + chr(i & 255) - -def ib32(c): - return ord(c[3]) + (ord(c[2])<<8) + (ord(c[1])<<16) + (ord(c[0])<<24) -def ob32(c): - return chr(i >> 24 & 0xff) + chr(i >> 16 & 0xff) + chr(i >> 8 & 0xff) + chr(i & 0xff) - - -def il16(c): - return ord(c[0]) + (ord(c[1])<<8) -def ol16(i): - return chr(i&255) + chr(i>>8&255) - -def il32(c): - return ord(c[0]) + (ord(c[1])<<8) + (ord(c[2])<<16) + (ord(c[3])<<24) -def ol32(i): - return chr(i&255) + chr(i>>8&255) + chr(i>>16&255) + chr(i>>24&255) - - - - -def testRead(*paths) : - from PIL.Image import open as imgopen - from standards.exif import TAGS - from cStringIO import StringIO - - import os - paths = list(paths) - paths.extend(['testimages/%s'%name for name in os.listdir('testimages') \ - if name.endswith('.jpg') and \ - not name.endswith('_thumb.jpg')]) - - for path in paths : - print '------------' - print path - print '------------' - im = imgopen(path) - applist = im.applist - exifBlock = [a[1] for a in applist if a[0] == 'APP1' and a[1].startswith("Exif\x00\x00")][0] - exif = exifBlock[6:] - sio = StringIO(exif) - - e = Exif(sio) - for name in e.getIFDNames() : - print '%s: ' %name - for tag in e.getIFDTags(name) : - print hex(tag), TAGS.get(tag), e[tag] - print - - thumb = e.getThumbnail() - if thumb is not None : - data, mime = thumb - out = open('%s_thumb.jpg' % path[:-4], 'w') - out.write(data) - out.close() - -def testWrite(*paths) : - from PIL.Image import open as imgopen - from standards.exif import TAGS - from cStringIO import StringIO - -# import os -# paths = list(paths) -# paths.extend(['testimages/%s'%name for name in os.listdir('testimages') \ -# if name.endswith('.jpg') and \ -# not name.endswith('_thumb.jpg')]) - - for path in paths : - print '------------' - print path - print '------------' - im = imgopen(path) - applist = im.applist - exifBlock = [a[1] for a in applist if a[0] == 'APP1' and a[1].startswith("Exif\x00\x00")][0] - exif = exifBlock[6:] - from cStringIO import StringIO - sio = StringIO(exif) - - e = Exif(sio) - - out = StringIO() - e.save(out) - out.seek(0) - print '%r' % out.read() - - -if __name__ == '__main__' : - testRead('testMM.jpg', 'testII.jpg') - #testWrite('testMM.jpg', 'testII.jpg')