mirror of
https://github.com/jlengrand/Ivolution.git
synced 2026-03-10 15:50:41 +00:00
Data are now hosted as a subfolder of the code, and all the source files are grouped into the same package. Bin folder has been removed, scripts are now directly placed in the root location. Main package has been renamed to ivolution, this is a first step towards github renaming and full renaming of the project
676 lines
20 KiB
Python
Executable File
676 lines
20 KiB
Python
Executable File
## ===========================================================================
|
|
## NAME: exif
|
|
## TYPE: python script
|
|
## CONTENT: library for parsing EXIF headers
|
|
## ===========================================================================
|
|
## AUTHORS: rft Robert F. Tobler
|
|
## ===========================================================================
|
|
## HISTORY:
|
|
##
|
|
## 10-Aug-01 11:14:20 rft last modification
|
|
## 09-Aug-01 16:51:05 rft created
|
|
## ===========================================================================
|
|
|
|
import string
|
|
|
|
ASCII = 0
|
|
BINARY = 1
|
|
|
|
## ---------------------------------------------------------------------------
|
|
## 'Tiff'
|
|
## This class provides the Exif header as a file-like object and hides
|
|
## endian-specific data access.
|
|
## ---------------------------------------------------------------------------
|
|
|
|
class Tiff:
|
|
def __init__(self, data, file = None):
|
|
self.data = data
|
|
|
|
self.file = file
|
|
self.endpos = len(data)
|
|
|
|
self.pos = 0
|
|
if self.data[0:2] == "MM":
|
|
self.S0 = 1 ; self.S1 = 0
|
|
self.L0 = 3 ; self.L1 = 2 ; self.L2 = 1 ; self.L3 = 0
|
|
else:
|
|
self.S0 = 0 ; self.S1 = 1
|
|
self.L0 = 0 ; self.L1 = 1 ; self.L2 = 2 ; self.L3 = 3
|
|
|
|
def seek(self, pos):
|
|
self.pos = pos
|
|
if self.pos > self.endpos:
|
|
self.data += self.file.read( self.endpos - self.pos )
|
|
|
|
def tell(self):
|
|
return self.pos
|
|
|
|
def read(self, len):
|
|
old_pos = self.pos
|
|
self.pos = self.pos + len
|
|
if self.pos > self.endpos:
|
|
self.data += self.file.read( self.endpos - self.pos )
|
|
return self.data[old_pos:self.pos]
|
|
|
|
def byte(self, signed = 0):
|
|
pos = self.pos
|
|
self.pos = pos + 1
|
|
if self.pos > self.endpos:
|
|
self.data += self.file.read( self.endpos - self.pos )
|
|
hi = ord(self.data[pos])
|
|
if hi > 127 and signed: hi = hi - 256
|
|
return hi
|
|
|
|
def short(self, signed = 0):
|
|
pos = self.pos
|
|
self.pos = pos + 2
|
|
if self.pos > self.endpos:
|
|
self.data += self.file.read( self.endpos - self.pos )
|
|
hi = ord(self.data[pos+self.S1])
|
|
if hi > 127 and signed: hi = hi - 256
|
|
return (hi<<8)|ord(self.data[pos+self.S0])
|
|
|
|
def long(self, signed = 0):
|
|
pos = self.pos
|
|
self.pos = pos + 4
|
|
if self.pos > self.endpos:
|
|
self.data += self.file.read( self.endpos - self.pos )
|
|
hi = ord(self.data[pos+self.L3])
|
|
if hi > 127 and not signed: hi = long(hi)
|
|
return (hi<<24) | (ord(self.data[pos+self.L2])<<16) \
|
|
| (ord(self.data[pos+self.L1])<<8) | ord(self.data[pos+self.L0])
|
|
|
|
## ---------------------------------------------------------------------------
|
|
## 'Type', 'Type...'
|
|
## A small hierarchy of objects that knows how to read each type of tag
|
|
## field from a tiff file, and how to pretty-print each type of tag.
|
|
##
|
|
## The method 'read' is used to read a tag with a given count from the
|
|
## supplied tiff file.
|
|
##
|
|
## The method 'str_table' is used to pretty-print the value table of a
|
|
## tag if no special format for this tag is present.
|
|
## ---------------------------------------------------------------------------
|
|
|
|
class Type:
|
|
def str_table(self, table):
|
|
result = []
|
|
for val in table: result.append(self.str_value(val))
|
|
return string.join(result, ", ")
|
|
def str_value(self, val):
|
|
return str(val)
|
|
|
|
class TypeByte(Type):
|
|
def __init__(self): self.name = "BYTE" ; self.len = 1
|
|
def read(self, tiff, count):
|
|
result = []
|
|
for i in range(0, count): table.append(tiff.byte())
|
|
return table
|
|
|
|
class TypeAscii:
|
|
def __init__(self): self.name = "ASCII" ; self.len = 1
|
|
def read(self, tiff, count):
|
|
return tiff.read(count-1)
|
|
def str_table(self, table):
|
|
return string.strip(table)
|
|
|
|
class TypeShort(Type):
|
|
def __init__(self): self.name = "SHORT" ; self.len = 2
|
|
def read(self, tiff, count):
|
|
table = []
|
|
for i in range(0, count): table.append(tiff.short())
|
|
return table
|
|
|
|
class TypeLong(Type):
|
|
def __init__(self): self.name = "LONG" ; self.len = 4
|
|
def read(self, tiff, count):
|
|
table = []
|
|
for i in range(0, count): table.append(tiff.long())
|
|
return table
|
|
|
|
class TypeRatio(Type):
|
|
def __init__(self): self.name = "RATIO" ; self.len = 8
|
|
def read(self, tiff, count):
|
|
table = []
|
|
for i in range(0, count): table.append((tiff.long(), tiff.long()))
|
|
return table
|
|
def str_value(self, val):
|
|
return "%d/%d" %(val[0], val[1])
|
|
|
|
class TypeSByte(Type):
|
|
def __init__(self): self.name = "SBYTE" ; self.len = 1
|
|
def read(self, tiff, count):
|
|
table = []
|
|
for i in range(0, count): table.append(tiff.byte(signed=1))
|
|
return table
|
|
|
|
class TypeUndef(TypeByte):
|
|
def __init__(self): self.name = "UNDEF" ; self.len = 1
|
|
def read(self, tiff, count):
|
|
return tiff.read(count)
|
|
def str_table(self, table):
|
|
result = map( lambda x: str(ord(x)), table )
|
|
# this next line is somehow much more efficient than using str()
|
|
return '[ ' + string.join( result, ',' ) + ' ]'
|
|
|
|
class TypeSShort(Type):
|
|
def __init__(self): self.name = "SSHORT" ; self.len = 2
|
|
def read(self, tiff, count):
|
|
table = []
|
|
for i in range(0, count): table.append(tiff.short(signed=1))
|
|
return table
|
|
|
|
class TypeSLong(Type):
|
|
def __init__(self): self.name = "SLONG" ; self.len = 4
|
|
def read(self, tiff, count):
|
|
table = []
|
|
for i in range(0, count): table.append(tiff.short(signed=1))
|
|
return table
|
|
|
|
class TypeSRatio(TypeRatio):
|
|
def __init__(self): self.name = "SRATIO" ; self.len = 8
|
|
def read(self, tiff, count):
|
|
table = []
|
|
for i in range(0, count):
|
|
table.append((tiff.long(signed=1), tiff.long(signed=1)))
|
|
return table
|
|
|
|
class TypeFloat:
|
|
def __init__(self): self.name = "FLOAT" ; self.len = 4
|
|
def read(self, tiff, count):
|
|
return tiff.read(4 * count)
|
|
|
|
class TypeDouble:
|
|
def __init__(self): self.name = "DOUBLE" ; self.len = 8
|
|
def read(self, tiff, count):
|
|
return tiff.read(8 * count)
|
|
|
|
TYPE_MAP = {
|
|
1: TypeByte(),
|
|
2: TypeAscii(),
|
|
3: TypeShort(),
|
|
4: TypeLong(),
|
|
5: TypeRatio(),
|
|
6: TypeSByte(),
|
|
7: TypeUndef(),
|
|
8: TypeSShort(),
|
|
9: TypeSLong(),
|
|
10: TypeSRatio(),
|
|
11: TypeFloat(),
|
|
12: TypeDouble(),
|
|
}
|
|
|
|
## ---------------------------------------------------------------------------
|
|
## 'Tag'
|
|
## A tag knows about its name and an optional format.
|
|
## ---------------------------------------------------------------------------
|
|
|
|
class Tag:
|
|
def __init__(self, name, format = None):
|
|
self.name = name
|
|
self.format = format
|
|
|
|
## ---------------------------------------------------------------------------
|
|
## 'Format', 'Format...'
|
|
## A small hierarchy of objects that provide special formats for certain
|
|
## tags in the EXIF standard.
|
|
##
|
|
## The method 'str_table' is used to pretty-print the value table of a
|
|
## tag. It gets the table of tags that have already been parsed as a
|
|
## parameter in order to handle vendor specific extensions.
|
|
## ---------------------------------------------------------------------------
|
|
|
|
class Format:
|
|
def str_table(self, table, value_map):
|
|
result = []
|
|
for val in table: result.append(self.str_value(val))
|
|
return string.join(result, ", ")
|
|
|
|
class FormatMap:
|
|
def __init__(self, map, make_ext = {}):
|
|
self.map = map
|
|
self.make_ext = make_ext
|
|
def str_table(self, table, value_map):
|
|
if len(table) == 1:
|
|
key = table[0]
|
|
else:
|
|
key = table
|
|
value = self.map.get(key)
|
|
if not value:
|
|
make = value_map.get("Make")
|
|
if make: value = self.make_ext.get(make,{}).get(key)
|
|
if not value: value = `key`
|
|
return value
|
|
|
|
class FormatRatioAsFloat(Format):
|
|
def str_value(self, val):
|
|
if val[1] == 0: return "0.0"
|
|
return "%g" % (val[0]/float(val[1]))
|
|
|
|
class FormatRatioAsBias(Format):
|
|
def str_value(self, val):
|
|
if val[1] == 0: return "0.0"
|
|
if val[0] > 0: return "+%3.1f" % (val[0]/float(val[1]))
|
|
if val[0] < 0: return "-%3.1f" % (-val[0]/float(val[1]))
|
|
return "0.0"
|
|
|
|
def format_time(t):
|
|
if t > 0.5: return "%g" % t
|
|
if t > 0.1: return "1/%g" % (0.1*int(10/t+0.5))
|
|
return "1/%d" % int(1/t+0.5)
|
|
|
|
class FormatRatioAsTime(Format):
|
|
def str_value(self, val):
|
|
if val[1] == 0: return "0.0"
|
|
return format_time(val[0]/float(val[1]))
|
|
|
|
class FormatRatioAsApexTime(Format):
|
|
def str_value(self, val):
|
|
if val[1] == 0: return "0.0"
|
|
return format_time(pow(0.5, val[0]/float(val[1])))
|
|
|
|
## ---------------------------------------------------------------------------
|
|
## The EXIF parser is completely table driven.
|
|
## ---------------------------------------------------------------------------
|
|
|
|
## ---------------------------------------------------------------------------
|
|
## Nikon 99x MakerNote Tags http://members.tripod.com/~tawba/990exif.htm
|
|
## ---------------------------------------------------------------------------
|
|
NIKON_99x_MAKERNOTE_TAG_MAP = {
|
|
0x0001: Tag('MN_0x0001'),
|
|
0x0002: Tag('MN_ISOSetting'),
|
|
0x0003: Tag('MN_ColorMode'),
|
|
0x0004: Tag('MN_Quality'),
|
|
0x0005: Tag('MN_Whitebalance'),
|
|
0x0006: Tag('MN_ImageSharpening'),
|
|
0x0007: Tag('MN_FocusMode'),
|
|
0x0008: Tag('MN_FlashSetting'),
|
|
0x000A: Tag('MN_0x000A'),
|
|
0x000F: Tag('MN_ISOSelection'),
|
|
0x0080: Tag('MN_ImageAdjustment'),
|
|
0x0082: Tag('MN_AuxiliaryLens'),
|
|
0x0085: Tag('MN_ManualFocusDistance', FormatRatioAsFloat() ),
|
|
0x0086: Tag('MN_DigitalZoomFactor', FormatRatioAsFloat() ),
|
|
0x0088: Tag('MN_AFFocusPosition',
|
|
FormatMap({
|
|
'\00\00\00\00': 'Center',
|
|
'\00\01\00\00': 'Top',
|
|
'\00\02\00\00': 'Bottom',
|
|
'\00\03\00\00': 'Left',
|
|
'\00\04\00\00': 'Right',
|
|
})),
|
|
0x008f: Tag('MN_0x008f'),
|
|
0x0094: Tag('MN_Saturation',
|
|
FormatMap({
|
|
0: '0',
|
|
1: '1',
|
|
2: '2',
|
|
-3: 'B&W',
|
|
-2: '-2',
|
|
-1: '-1',
|
|
})),
|
|
0x0095: Tag('MN_NoiseReduction'),
|
|
0x0010: Tag('MN_DataDump'),
|
|
0x0011: Tag('MN_0x0011'),
|
|
0x0e00: Tag('MN_0x0e00'),
|
|
}
|
|
|
|
## ---------------------------------------------------------------------------
|
|
## 'MakerNote...'
|
|
## This currently only parses Nikon E990, and Nikon E995 MakerNote tags.
|
|
## Additional objects with a 'parse' function can be placed here to add
|
|
## support for other cameras. This function adds the pretty-printed
|
|
## information in the MakerNote to the 'value_map' that is supplied.
|
|
## ---------------------------------------------------------------------------
|
|
|
|
class MakerNoteTags:
|
|
def __init__(self, tag_map):
|
|
self.tag_map = tag_map
|
|
def parse(self, tiff, mode, tag_len, value_map):
|
|
num_entries = tiff.short()
|
|
if verbose_opt: print num_entries, 'tags'
|
|
for field in range(0, num_entries):
|
|
parse_tag(tiff, mode, value_map, self.tag_map)
|
|
|
|
NIKON_99x_MAKERNOTE = MakerNoteTags(NIKON_99x_MAKERNOTE_TAG_MAP)
|
|
|
|
## ---------------------------------------------------------------------------
|
|
## 'MAKERNOTE_MAP'
|
|
## Interpretation of the MakerNote tag indexed by 'Make', 'Model' pairs.
|
|
## ---------------------------------------------------------------------------
|
|
|
|
MAKERNOTE_MAP = {
|
|
('NIKON', 'E990'): NIKON_99x_MAKERNOTE,
|
|
('NIKON', 'E995'): NIKON_99x_MAKERNOTE,
|
|
}
|
|
|
|
## ---------------------------------------------------------------------------
|
|
## 'TAG_MAP'
|
|
## This is the map of tags that drives the parser.
|
|
## ---------------------------------------------------------------------------
|
|
|
|
TAG_MAP = {
|
|
0x00fe: Tag('NewSubFileType'),
|
|
0x0100: Tag('ImageWidth'),
|
|
0x0101: Tag('ImageLength'),
|
|
0x0102: Tag('BitsPerSample'),
|
|
0x0103: Tag('Compression'),
|
|
0x0106: Tag('PhotometricInterpretation'),
|
|
0x010a: Tag('FillOrder'),
|
|
0x010d: Tag('DocumentName'),
|
|
0x010e: Tag('ImageDescription'),
|
|
0x010f: Tag('Make'),
|
|
0x0110: Tag('Model'),
|
|
0x0111: Tag('StripOffsets'),
|
|
0x0112: Tag('Orientation'),
|
|
0x0115: Tag('SamplesPerPixel'),
|
|
0x0116: Tag('RowsPerStrip'),
|
|
0x0117: Tag('StripByteCounts'),
|
|
0x011a: Tag('XResolution'),
|
|
0x011b: Tag('YResolution'),
|
|
0x011c: Tag('PlanarConfiguration'),
|
|
0x0128: Tag('ResolutionUnit',
|
|
FormatMap({
|
|
1: 'Not Absoulute',
|
|
2: 'Inch',
|
|
3: 'Centimeter'
|
|
})),
|
|
0x012d: Tag('TransferFunction'),
|
|
0x0131: Tag('Software'),
|
|
0x0132: Tag('DateTime'),
|
|
0x013b: Tag('Artist'),
|
|
0x013e: Tag('WhitePoint'),
|
|
0x013f: Tag('PrimaryChromaticities'),
|
|
0x0142: Tag('TileWidth'),
|
|
0x0143: Tag('TileLength'),
|
|
0x0144: Tag('TileOffsets'),
|
|
0x0145: Tag('TileByteCounts'),
|
|
0x014a: Tag('SubIFDs'),
|
|
0x0156: Tag('TransferRange'),
|
|
0x015b: Tag('JPEGTables'),
|
|
0x0201: Tag('JPEGInterchangeFormat'),
|
|
0x0202: Tag('JPEGInterchangeFormatLength'),
|
|
0x0211: Tag('YCbCrCoefficients'),
|
|
0x0212: Tag('YCbCrSubSampling'),
|
|
0x0213: Tag('YCbCrPositioning'),
|
|
0x0214: Tag('ReferenceBlackWhite'),
|
|
0x828d: Tag('CFARepeatPatternDim'),
|
|
0x828e: Tag('CFAPattern'),
|
|
0x828f: Tag('BatteryLevel'),
|
|
0x8298: Tag('Copyright'),
|
|
0x829a: Tag('ExposureTime', FormatRatioAsTime() ),
|
|
0x829d: Tag('FNumber', FormatRatioAsFloat() ),
|
|
0x83bb: Tag('IPTC_NAA'),
|
|
0x8773: Tag('InterColorProfile'),
|
|
0x8822: Tag('ExposureProgram',
|
|
FormatMap({
|
|
0: 'Unidentified',
|
|
1: 'Manual',
|
|
2: 'Program Normal',
|
|
3: 'Aperture Priority',
|
|
4: 'Shutter Priority',
|
|
5: 'Program Creative',
|
|
6: 'Program Action',
|
|
7: 'Portrait Mode',
|
|
8: 'Landscape Mode',
|
|
})),
|
|
0x8824: Tag('SpectralSensitivity'),
|
|
0x8825: Tag('GPSInfo'),
|
|
0x8827: Tag('ISOSpeedRatings'),
|
|
0x8828: Tag('OECF'),
|
|
0x8829: Tag('Interlace'),
|
|
0x882a: Tag('TimeZoneOffset'),
|
|
0x882b: Tag('SelfTimerMode'),
|
|
0x8769: Tag('ExifOffset'),
|
|
0x9000: Tag('ExifVersion'),
|
|
0x9003: Tag('DateTimeOriginal'),
|
|
0x9004: Tag('DateTimeDigitized'),
|
|
0x9101: Tag('ComponentsConfiguration'),
|
|
0x9102: Tag('CompressedBitsPerPixel'),
|
|
0x9201: Tag('ShutterSpeedValue', FormatRatioAsApexTime() ),
|
|
0x9202: Tag('ApertureValue', FormatRatioAsFloat() ),
|
|
0x9203: Tag('BrightnessValue'),
|
|
0x9204: Tag('ExposureBiasValue', FormatRatioAsBias() ),
|
|
0x9205: Tag('MaxApertureValue', FormatRatioAsFloat() ),
|
|
0x9206: Tag('SubjectDistance'),
|
|
0x9207: Tag('MeteringMode',
|
|
FormatMap({
|
|
0: 'Unidentified',
|
|
1: 'Average',
|
|
2: 'CenterWeightedAverage',
|
|
3: 'Spot',
|
|
4: 'MultiSpot',
|
|
},
|
|
make_ext = {
|
|
'NIKON': { 5: 'Matrix' },
|
|
})),
|
|
0x9208: Tag('LightSource',
|
|
FormatMap({
|
|
0: 'Unknown',
|
|
1: 'Daylight',
|
|
2: 'Fluorescent',
|
|
3: 'Tungsten',
|
|
10: 'Flash',
|
|
17: 'Standard light A',
|
|
18: 'Standard light B',
|
|
19: 'Standard light C',
|
|
20: 'D55',
|
|
21: 'D65',
|
|
22: 'D75',
|
|
255: 'Other'
|
|
})),
|
|
0x9209: Tag('Flash',
|
|
FormatMap({
|
|
0: 'no',
|
|
1: 'fired',
|
|
5: 'fired (?)', # no return sensed
|
|
7: 'fired (!)', # return sensed
|
|
9: 'fill fired',
|
|
13: 'fill fired (?)',
|
|
15: 'fill fired (!)',
|
|
16: 'off',
|
|
24: 'auto off',
|
|
25: 'auto fired',
|
|
29: 'auto fired (?)',
|
|
31: 'auto fired (!)',
|
|
32: 'not available'
|
|
})),
|
|
0x920a: Tag('FocalLength', FormatRatioAsFloat()),
|
|
0x920b: Tag('FlashEnergy'),
|
|
0x920c: Tag('SpatialFrequencyResponse'),
|
|
0x920d: Tag('Noise'),
|
|
0x920e: Tag('FocalPlaneXResolution'),
|
|
0x920f: Tag('FocalPlaneYResolution'),
|
|
0x9210: Tag('FocalPlaneResolutionUnit',
|
|
FormatMap({
|
|
1: 'Inch',
|
|
2: 'Meter',
|
|
3: 'Centimeter',
|
|
4: 'Millimeter',
|
|
5: 'Micrometer',
|
|
})),
|
|
0x9211: Tag('ImageNumber'),
|
|
0x9212: Tag('SecurityClassification'),
|
|
0x9213: Tag('ImageHistory'),
|
|
0x9214: Tag('SubjectLocation'),
|
|
0x9215: Tag('ExposureIndex'),
|
|
0x9216: Tag('TIFF_EPStandardID'),
|
|
0x9217: Tag('SensingMethod'),
|
|
0x927c: Tag('MakerNote'),
|
|
0xa001: Tag('ColorSpace'),
|
|
0xa002: Tag('ExifImageWidth'),
|
|
0xa003: Tag('ExifImageHeight'),
|
|
0xa005: Tag('Interoperability_IFD_Pointer'),
|
|
}
|
|
|
|
def parse_tag(tiff, mode, value_map, tag_map):
|
|
tag_id = tiff.short()
|
|
type_no = tiff.short()
|
|
count = tiff.long()
|
|
|
|
tag = tag_map.get(tag_id)
|
|
if not tag: tag = Tag("Tag0x%x" % tag_id)
|
|
|
|
type = TYPE_MAP[type_no]
|
|
|
|
if verbose_opt:
|
|
print "%30s:" % tag.name,
|
|
if verbose_opt > 1: print "%6s %3d" % (type.name, count),
|
|
|
|
pos = tiff.tell()
|
|
tag_len = type.len * count
|
|
if tag_len > 4:
|
|
tag_offset = tiff.long()
|
|
tiff.seek(tag_offset)
|
|
if verbose_opt > 1: print "@%03x :" % tag_offset,
|
|
else:
|
|
if verbose_opt > 1: print " :",
|
|
|
|
if tag.name == 'MakerNote':
|
|
makernote = MAKERNOTE_MAP.get((value_map['Make'],value_map['Model']))
|
|
if makernote:
|
|
makernote.parse(tiff, mode, tag_len, value_map)
|
|
value_table = None
|
|
else:
|
|
value_table = type.read(tiff, count)
|
|
else:
|
|
value_table = type.read(tiff, count)
|
|
|
|
if value_table:
|
|
if mode == ASCII:
|
|
if tag.format:
|
|
val = tag.format.str_table(value_table, value_map)
|
|
else:
|
|
val = type.str_table(value_table)
|
|
else:
|
|
val = value_table
|
|
value_map[tag.name] = val
|
|
if verbose_opt:
|
|
if value_map.has_key(tag.name): print val,
|
|
print
|
|
|
|
tiff.seek(pos+4)
|
|
|
|
def parse_ifd(tiff, mode, offset, value_map):
|
|
tiff.seek(offset)
|
|
num_entries = tiff.short()
|
|
if verbose_opt > 1:
|
|
print "%30s: %3d @%03x" % ("IFD", num_entries, offset)
|
|
for field in range(0, num_entries):
|
|
parse_tag(tiff, mode, value_map, TAG_MAP)
|
|
offset = tiff.long()
|
|
return offset
|
|
|
|
def parse_tiff(tiff, mode):
|
|
value_map = {}
|
|
order = tiff.read(2)
|
|
if tiff.short() == 42:
|
|
offset = tiff.long()
|
|
while offset > 0:
|
|
offset = parse_ifd(tiff, mode, offset, value_map)
|
|
|
|
if offset == 0: # special handling to get
|
|
if value_map.has_key('ExifOffset'): # next EXIF IFD
|
|
offset = value_map['ExifOffset']
|
|
if mode == ASCII:
|
|
offset = int(offset)
|
|
else:
|
|
offset = offset[0]
|
|
del value_map['ExifOffset']
|
|
return value_map
|
|
|
|
|
|
def parse_tiff_fortiff(tiff, mode):
|
|
|
|
"""Parse a real tiff file, not an EXIF tiff file."""
|
|
|
|
value_map = {}
|
|
order = tiff.read(2)
|
|
if tiff.short() == 42:
|
|
offset = tiff.long()
|
|
|
|
# build a list of small tags, we don't want to parse the huge stuff
|
|
stags = []
|
|
while offset > 0:
|
|
tiff.seek(offset)
|
|
num_entries = tiff.short()
|
|
|
|
if verbose_opt > 1:
|
|
print "%30s: %3d @%03x" % ("IFD", num_entries, offset)
|
|
|
|
for field in range(0, num_entries):
|
|
pos = tiff.tell()
|
|
|
|
tag_id = tiff.short()
|
|
type_no = tiff.short()
|
|
length = tiff.long()
|
|
valoff = tiff.long()
|
|
#print TAG_MAP[ tag_id ].name, length
|
|
|
|
if tag_id == 0x8769:
|
|
if mode == ASCII:
|
|
valoff = int(valoff)
|
|
else:
|
|
valoff = valoff[0]
|
|
stags += [ (tag_id, valoff) ]
|
|
|
|
elif length < 1024:
|
|
stags += [ (tag_id, pos) ]
|
|
|
|
offset = tiff.long()
|
|
|
|
# IMPORTANT: we read the 0st ifd only for this.
|
|
# The second is reserved for the thumbnail, whatever is in there
|
|
# we ignore.
|
|
break
|
|
|
|
for p in stags:
|
|
(tag_id, pos) = p
|
|
|
|
if tag_id == 0x8769:
|
|
parse_ifd(tiff, mode, pos, value_map)
|
|
else:
|
|
tiff.seek( pos )
|
|
parse_tag(tiff, mode, value_map, TAG_MAP)
|
|
|
|
return value_map
|
|
|
|
|
|
## ---------------------------------------------------------------------------
|
|
## 'parse'
|
|
## This is the function for parsing the EXIF structure in a file given
|
|
## the path of the file.
|
|
## The function returns a map which contains all the exif tags that
|
|
## were found, indexed by the name of the tag. The value of each tag
|
|
## is already converted to a nicely formatted string.
|
|
## ---------------------------------------------------------------------------
|
|
def parse(path_name, verbose = 0, mode = 0):
|
|
global verbose_opt
|
|
verbose_opt = verbose
|
|
try:
|
|
file = open(path_name, "rb")
|
|
data = file.read(12)
|
|
if data[0:4] == '\377\330\377\341' and data[6:10] == 'Exif':
|
|
# JPEG
|
|
length = ord(data[4]) * 256 + ord(data[5])
|
|
if verbose > 1:
|
|
print '%30s: %d' % ("EXIF header length",length)
|
|
tiff = Tiff(file.read(length-8))
|
|
value_map = parse_tiff(tiff, mode)
|
|
elif data[0:2] in [ 'II', 'MM' ] and ord(data[2]) == 42:
|
|
# Tiff
|
|
tiff = Tiff(data,file)
|
|
tiff.seek(0)
|
|
value_map = parse_tiff_fortiff(tiff, mode)
|
|
else:
|
|
# Some other file format, sorry.
|
|
value_map = {}
|
|
|
|
file.close()
|
|
except IOError:
|
|
value_map = {}
|
|
|
|
return value_map
|
|
|
|
## ===========================================================================
|