--- a/degal/exif.py Sat Jun 13 20:59:53 2009 +0300
+++ b/degal/exif.py Sat Jun 13 22:22:04 2009 +0300
@@ -143,6 +143,25 @@
else :
return None
+ def is_subifd (self) :
+ """
+ Tests if this Tag is of a IFDTag type
+ """
+
+ return self.tag_data and isinstance(self.tag_data, exif_data.IFDTag)
+
+ @lazy_load
+ def subifd (self) :
+ """
+ Load the sub-IFD for this tag
+ """
+
+ # the tag_dict to use
+ tag_dict = self.tag_data.ifd_tags or self.ifd.tag_dict
+
+ # construct, return
+ return self.ifd.exif._load_subifd(self, tag_dict)
+
def process_values (self, raw_values) :
"""
Process the given raw values unpacked from the file.
@@ -179,7 +198,7 @@
Represents an IFD (Image file directory) region in EXIF data.
"""
- def __init__ (self, buffer, tag_dict, **buffer_opts) :
+ def __init__ (self, exif, buffer, tag_dict, **buffer_opts) :
"""
Access the IFD data from the given bufferable object with given buffer opts.
@@ -190,6 +209,7 @@
super(IFD, self).__init__(buffer, **buffer_opts)
# store
+ self.exif = exif
self.tag_dict = tag_dict
# read header
@@ -210,18 +230,31 @@
tag, type, count, data_raw = self.pread_struct(offset, 'HHI4s')
# yield the new Tag
- yield Tag(self, offset, tag, type, count, data_raw)
+ yield Tag(self, self.offset + offset, tag, type, count, data_raw)
+
+ def get_tags (self, filter=None) :
+ """
+ Yield a series of tag objects for this IFD and all sub-IFDs.
+ """
+
+ for tag in self.tags :
+ if tag.is_subifd() :
+ # recurse
+ for subtag in tag.subifd.get_tags(filter=filter) :
+ yield subtag
+
+ else :
+ # normal tag
+ yield tag
class EXIF (Buffer) :
"""
Represents the EXIF data embedded in some image file in the form of a Region.
"""
- def __init__ (self, buffer, tags=None, **buffer_opts) :
+ def __init__ (self, buffer, **buffer_opts) :
"""
Access the EXIF data from the given bufferable object with the given buffer options.
-
- `tags`, if given, specifies that only the given named tags should be loaded.
"""
# init Buffer
@@ -241,7 +274,7 @@
while offset :
# create and read the IFD, operating on the right sub-buffer
- ifd = IFD(self.buf, exif_data.EXIF_TAGS, offset=offset)
+ ifd = IFD(self, self.buf, exif_data.EXIF_TAGS, offset=offset)
# yield it
yield ifd
@@ -249,11 +282,17 @@
# skip to next offset
offset = ifd.next_offset
- def iter_all_ifds (self) :
+ def _load_subifd (self, tag, tag_dict) :
"""
- Iterate over all of the IFDs contained within this EXIF, or within other IFDs.
+ Creates and returns a sub-IFD for the given tag.
"""
-
+
+ # locate it
+ offset, = self.tag_values_raw(tag)
+
+ # construct the new IFD
+ return IFD(self, self.buf, tag_dict, offset=offset)
+
def tag_data_info (self, tag) :
"""
Calculate the location, format and size of the given tag's data.
@@ -329,6 +368,21 @@
# return as comma-separated formatted string, yes
return tag.readable_value(values)
+
+ def get_main_tags (self, **opts) :
+ """
+ Get the tags for the main image's IFD as a dict.
+ """
+
+ if not self.ifds :
+ # weird case
+ raise Exception("No IFD for main image found")
+
+ # the main IFD is always the first one
+ main_ifd = self.ifds[0]
+
+ # do it
+ return dict((tag.name, self.tag_value(tag)) for tag in main_ifd.get_tags(**opts))
# mapping from two-byte TIFF byte order marker to struct prefix
TIFF_BYTE_ORDER = {
@@ -492,6 +546,63 @@
# try and load it
return func(file, **opts)
+def dump_tag (exif, i, tag, indent=2) :
+ """
+ Dump the given tag
+ """
+
+ data_info = exif.tag_data_info(tag)
+
+ if data_info :
+ data_fmt, data_offset, data_size = data_info
+
+ else :
+ data_fmt = data_offset = data_size = None
+
+ print "%sTag:%d offset=%#04x(%#08x), tag=%d/%s, type=%d/%s, count=%d, fmt=%s, offset=%#04x, size=%s, is_subifd=%s:" % (
+ '\t'*indent,
+ i,
+ tag.offset, tag.offset + exif.offset,
+ tag.tag, tag.name or '???',
+ tag.type, tag.type_name if tag.type_data else '???',
+ tag.count,
+ data_fmt, data_offset, data_size,
+ tag.is_subifd(),
+ )
+
+ if tag.is_subifd() :
+ # recurse
+ dump_ifd(exif, 0, tag.subifd, indent + 1)
+
+ else :
+ # dump each value
+ values = exif.tag_values(tag)
+
+ for i, value in enumerate(values) :
+ print "%s\t%02d: %.120r" % ('\t'*indent, i, value)
+
+ # and then the readable one
+ print "%s\t-> %.120s" % ('\t'*indent, tag.readable_value(values), )
+
+
+def dump_ifd (exif, i, ifd, indent=1) :
+ """
+ Dump the given IFD, recursively
+ """
+
+ print "%sIFD:%d offset=%#04x(%#08x), count=%d, next=%d:" % (
+ '\t'*indent,
+ i,
+ ifd.offset, ifd.offset + exif.offset,
+ ifd.count,
+ ifd.next_offset
+ )
+
+ for i, tag in enumerate(ifd.tags) :
+ # dump
+ dump_tag(exif, i, tag, indent + 1)
+
+
def dump_exif (exif) :
"""
Dump all tags from the given EXIF object to stdout
@@ -500,39 +611,19 @@
print "EXIF offset=%#08x, size=%d:" % (exif.offset, exif.size)
for i, ifd in enumerate(exif.ifds) :
- print "\tIFD:%d offset=%#04x(%#08x), count=%d, next=%d:" % (
- i,
- ifd.offset, ifd.offset + exif.offset,
- ifd.count,
- ifd.next_offset
- )
-
- for i, tag in enumerate(ifd.tags) :
- data_info = exif.tag_data_info(tag)
-
- if data_info :
- data_fmt, data_offset, data_size = data_info
-
- else :
- data_fmt = data_offset = data_size = None
+ # dump
+ dump_ifd(exif, i, ifd)
- print "\t\tTag:%d offset=%#04x(%#08x), tag=%d/%s, type=%d/%s, count=%d, fmt=%s, offset=%#04x, size=%s:" % (
- i,
- tag.offset, tag.offset + exif.offset,
- tag.tag, tag.name or '???',
- tag.type, tag.type_name if tag.type_data else '???',
- tag.count,
- data_fmt, data_offset, data_size,
- )
- values = exif.tag_values(tag)
-
- for i, value in enumerate(values) :
- print "\t\t\t%02d: %r" % (i, value)
+def list_tags (exif) :
+ """
+ Print a neat listing of tags to stdout
+ """
- print "\t\t\t-> %s" % (tag.readable_value(values), )
+ for k, v in exif.get_main_tags().iteritems() :
+ print "%30s: %s" % (k, v)
-def main (path, quiet=False) :
+def main (path, debug=False) :
"""
Load and dump EXIF data from the given path
"""
@@ -543,15 +634,19 @@
if not exif :
raise Exception("No EXIF data found")
- if not quiet :
+ if debug :
# dump it
print "%s: " % path
print
dump_exif(exif)
+
+ else :
+ # list them
+ list_tags(exif)
if __name__ == '__main__' :
from sys import argv
- main(argv[1], '-q' in argv)
+ main(argv[1], '-d' in argv)
--- a/degal/exif_data.py Sat Jun 13 20:59:53 2009 +0300
+++ b/degal/exif_data.py Sat Jun 13 22:22:04 2009 +0300
@@ -151,6 +151,21 @@
return self.values_func(values)
+class IFDTag (Tag) :
+ """
+ A tag that references another IFD
+ """
+
+ def __init__ (self, name, ifd_tags=None) :
+ """
+ A tag that points to another IFD block. `ifd_tags`, if given, lists the tags for that block, otherwise,
+ the same tags as for the current block are used.
+ """
+
+ super(IFDTag, self).__init__(name)
+
+ self.ifd_tags = ifd_tags
+
USER_COMMENT_CHARSETS = {
'ASCII': ('ascii', 'replace' ),
'JIS': ('jis', 'error' ),
@@ -191,6 +206,48 @@
# XXX: or does it?
# SUB_IFD_MAGIC signifies that this IFD points to
# otherwise, the value is left as-is.
+# interoperability tags
+INTR_TAGS = {
+ 0x0001: Tag('InteroperabilityIndex'),
+ 0x0002: Tag('InteroperabilityVersion'),
+ 0x1000: Tag('RelatedImageFileFormat'),
+ 0x1001: Tag('RelatedImageWidth'),
+ 0x1002: Tag('RelatedImageLength'),
+ }
+
+# GPS tags (not used yet, haven't seen camera with GPS)
+GPS_TAGS = {
+ 0x0000: Tag('GPSVersionID'),
+ 0x0001: Tag('GPSLatitudeRef'),
+ 0x0002: Tag('GPSLatitude'),
+ 0x0003: Tag('GPSLongitudeRef'),
+ 0x0004: Tag('GPSLongitude'),
+ 0x0005: Tag('GPSAltitudeRef'),
+ 0x0006: Tag('GPSAltitude'),
+ 0x0007: Tag('GPSTimeStamp'),
+ 0x0008: Tag('GPSSatellites'),
+ 0x0009: Tag('GPSStatus'),
+ 0x000A: Tag('GPSMeasureMode'),
+ 0x000B: Tag('GPSDOP'),
+ 0x000C: Tag('GPSSpeedRef'),
+ 0x000D: Tag('GPSSpeed'),
+ 0x000E: Tag('GPSTrackRef'),
+ 0x000F: Tag('GPSTrack'),
+ 0x0010: Tag('GPSImgDirectionRef'),
+ 0x0011: Tag('GPSImgDirection'),
+ 0x0012: Tag('GPSMapDatum'),
+ 0x0013: Tag('GPSDestLatitudeRef'),
+ 0x0014: Tag('GPSDestLatitude'),
+ 0x0015: Tag('GPSDestLongitudeRef'),
+ 0x0016: Tag('GPSDestLongitude'),
+ 0x0017: Tag('GPSDestBearingRef'),
+ 0x0018: Tag('GPSDestBearing'),
+ 0x0019: Tag('GPSDestDistanceRef'),
+ 0x001A: Tag('GPSDestDistance'),
+ 0x001D: Tag('GPSDate'),
+ }
+
+
EXIF_TAGS = {
0x0100: Tag('ImageWidth'),
0x0101: Tag('ImageLength'),
@@ -280,7 +337,7 @@
0x829A: Tag('ExposureTime'),
0x829D: Tag('FNumber'),
0x83BB: Tag('IPTC/NAA'),
- 0x8769: Tag('ExifOffset'),
+ 0x8769: IFDTag('ExifOffset', None),
0x8773: Tag('InterColorProfile'),
0x8822: TagDict('ExposureProgram',
{0: 'Unidentified',
@@ -293,7 +350,7 @@
7: 'Portrait Mode',
8: 'Landscape Mode'}),
0x8824: Tag('SpectralSensitivity'),
- 0x8825: Tag('GPSInfo'),
+ 0x8825: IFDTag('GPSInfo', GPS_TAGS),
0x8827: Tag('ISOSpeedRatings'),
0x8828: Tag('OECF'),
0x9000: Tag('ExifVersion'),
@@ -376,7 +433,7 @@
65535: 'Uncalibrated'}),
0xA002: Tag('ExifImageWidth'),
0xA003: Tag('ExifImageLength'),
- 0xA005: Tag('InteroperabilityOffset'),
+ 0xA005: IFDTag('InteroperabilityOffset', INTR_TAGS),
0xA20B: Tag('FlashEnergy'), # 0x920B in TIFF/EP
0xA20C: Tag('SpatialFrequencyResponse'), # 0x920C
0xA20E: Tag('FocalPlaneXResolution'), # 0x920E
@@ -441,47 +498,6 @@
0xEA1C: ('Padding', None),
}
-# interoperability tags
-INTR_TAGS = {
- 0x0001: Tag('InteroperabilityIndex'),
- 0x0002: Tag('InteroperabilityVersion'),
- 0x1000: Tag('RelatedImageFileFormat'),
- 0x1001: Tag('RelatedImageWidth'),
- 0x1002: Tag('RelatedImageLength'),
- }
-
-# GPS tags (not used yet, haven't seen camera with GPS)
-GPS_TAGS = {
- 0x0000: Tag('GPSVersionID'),
- 0x0001: Tag('GPSLatitudeRef'),
- 0x0002: Tag('GPSLatitude'),
- 0x0003: Tag('GPSLongitudeRef'),
- 0x0004: Tag('GPSLongitude'),
- 0x0005: Tag('GPSAltitudeRef'),
- 0x0006: Tag('GPSAltitude'),
- 0x0007: Tag('GPSTimeStamp'),
- 0x0008: Tag('GPSSatellites'),
- 0x0009: Tag('GPSStatus'),
- 0x000A: Tag('GPSMeasureMode'),
- 0x000B: Tag('GPSDOP'),
- 0x000C: Tag('GPSSpeedRef'),
- 0x000D: Tag('GPSSpeed'),
- 0x000E: Tag('GPSTrackRef'),
- 0x000F: Tag('GPSTrack'),
- 0x0010: Tag('GPSImgDirectionRef'),
- 0x0011: Tag('GPSImgDirection'),
- 0x0012: Tag('GPSMapDatum'),
- 0x0013: Tag('GPSDestLatitudeRef'),
- 0x0014: Tag('GPSDestLatitude'),
- 0x0015: Tag('GPSDestLongitudeRef'),
- 0x0016: Tag('GPSDestLongitude'),
- 0x0017: Tag('GPSDestBearingRef'),
- 0x0018: Tag('GPSDestBearing'),
- 0x0019: Tag('GPSDestDistanceRef'),
- 0x001A: Tag('GPSDestDistance'),
- 0x001D: Tag('GPSDate'),
- }
-
# http://tomtia.plala.jp/DigitalCamera/MakerNote/index.asp
def nikon_ev_bias (seq) :
"""