1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
|
import logging
from .classes import *
from .tags import *
__version__ = '1.4.2'
logger = logging.getLogger('exifread')
def increment_base(data, base):
return ord(data[base+2]) * 256 + ord(data[base+3]) + 2
def process_file(f, stop_tag=DEFAULT_STOP_TAG, details=True, strict=False, debug=False):
"""
Process an image file (expects an open file object).
This is the function that has to deal with all the arbitrary nasty bits
of the EXIF standard.
"""
# by default do not fake an EXIF beginning
fake_exif = 0
# determine whether it's a JPEG or TIFF
data = f.read(12)
if data[0:4] in ['II*\x00', 'MM\x00*']:
# it's a TIFF file
logger.debug("TIFF format recognized in data[0:4]")
f.seek(0)
endian = f.read(1)
f.read(1)
offset = 0
elif data[0:2] == '\xFF\xD8':
# it's a JPEG file
logger.debug("JPEG format recognized data[0:2]=0x%X%X", ord(data[0]), ord(data[1]))
base = 2
logger.debug("data[2]=0x%X data[3]=0x%X data[6:10]=%s",
ord(data[2]), ord(data[3]), data[6:10])
while data[2] == '\xFF' and data[6:10] in ('JFIF', 'JFXX', 'OLYM', 'Phot'):
length = ord(data[4]) * 256 + ord(data[5])
logger.debug(" Length offset is %s", length)
f.read(length-8)
# fake an EXIF beginning of file
# I don't think this is used. --gd
data = '\xFF\x00' + f.read(10)
fake_exif = 1
if base > 2:
logger.debug(" Added to base")
base = base + length + 4 -2
else:
logger.debug(" Added to zero")
base = length + 4
logger.debug(" Set segment base to 0x%X", base)
# Big ugly patch to deal with APP2 (or other) data coming before APP1
f.seek(0)
# in theory, this could be insufficient since 64K is the maximum size--gd
data = f.read(base + 4000)
# base = 2
while 1:
logger.debug(" Segment base 0x%X", base)
if data[base:base+2] == '\xFF\xE1':
# APP1
logger.debug(" APP1 at base 0x%X", base)
logger.debug(" Length: 0x%X 0x%X", ord(data[base+2]),
ord(data[base+3]))
logger.debug(" Code: %s", data[base+4:base+8])
if data[base+4:base+8] == "Exif":
logger.debug(" Decrement base by 2 to get to pre-segment header (for compatibility with later code)")
base = base-2
break
increment = increment_base(data, base)
logger.debug(" Increment base by %s", increment)
base = base + increment
elif data[base:base+2] == '\xFF\xE0':
# APP0
logger.debug(" APP0 at base 0x%X", base)
logger.debug(" Length: 0x%X 0x%X", ord(data[base+2]),
ord(data[base+3]))
logger.debug(" Code: %s", data[base+4:base+8])
increment = increment_base(data, base)
logger.debug(" Increment base by %s", increment)
base = base + increment
elif data[base:base+2] == '\xFF\xE2':
# APP2
logger.debug(" APP2 at base 0x%X", base)
logger.debug(" Length: 0x%X 0x%X", ord(data[base+2]),
ord(data[base+3]))
logger.debug(" Code: %s", data[base+4:base+8])
increment = increment_base(data, base)
logger.debug(" Increment base by %s", increment)
base = base + increment
elif data[base:base+2] == '\xFF\xEE':
# APP14
logger.debug(" APP14 Adobe segment at base 0x%X", base)
logger.debug(" Length: 0x%X 0x%X", ord(data[base+2]),
ord(data[base+3]))
logger.debug(" Code: %s", data[base+4:base+8])
increment = increment_base(data, base)
logger.debug(" Increment base by %s", increment)
base = base + increment
logger.debug(" There is useful EXIF-like data here, but we have no parser for it.")
elif data[base:base+2] == '\xFF\xDB':
logger.debug(" JPEG image data at base 0x%X No more segments are expected.",
base)
break
elif data[base:base+2] == '\xFF\xD8':
# APP12
logger.debug(" FFD8 segment at base 0x%X", base)
logger.debug(" Got 0x%X 0x%X and %s instead",
ord(data[base]),
ord(data[base+1]),
data[4+base:10+base])
logger.debug(" Length: 0x%X 0x%X", ord(data[base+2]),
ord(data[base+3]))
logger.debug(" Code: %s", data[base+4:base+8])
increment = increment_base(data, base)
logger.debug(" Increment base by %s", increment)
base = base + increment
elif data[base:base+2] == '\xFF\xEC':
# APP12
logger.debug(" APP12 XMP (Ducky) or Pictureinfo segment at base 0x%X",
base)
logger.debug(" Got 0x%X and 0x%X instead", ord(data[base]),
ord(data[base+1]))
logger.debug(" Length: 0x%X 0x%X",
ord(data[base+2]),
ord(data[base+3]))
logger.debug("Code: %s", data[base+4:base+8])
increment = increment_base(data, base)
logger.debug(" Increment base by %s", increment)
base = base + increment
logger.debug(" There is useful EXIF-like data here (quality, comment, copyright), but we have no parser for it.")
else:
try:
increment = increment_base(data, base)
logger.debug(" Got 0x%X and 0x%X instead",
ord(data[base]),
ord(data[base+1]))
except:
logger.debug(" Unexpected/unhandled segment type or file content.")
return {}
else:
logger.debug(" Increment base by %s", increment)
base = base + increment
f.seek(base + 12)
if data[2+base] == '\xFF' and data[6+base:10+base] == 'Exif':
# detected EXIF header
offset = f.tell()
endian = f.read(1)
#HACK TEST: endian = 'M'
elif data[2+base] == '\xFF' and data[6+base:10+base+1] == 'Ducky':
# detected Ducky header.
logger.debug("EXIF-like header (normally 0xFF and code): 0x%X and %s",
ord(data[2+base]) , data[6+base:10+base+1])
offset = f.tell()
endian = f.read(1)
elif data[2+base] == '\xFF' and data[6+base:10+base+1] == 'Adobe':
# detected APP14 (Adobe)
logger.debug("EXIF-like header (normally 0xFF and code): 0x%X and %s",
ord(data[2+base]) , data[6+base:10+base+1])
offset = f.tell()
endian = f.read(1)
else:
# no EXIF information
logger.debug("No EXIF header expected data[2+base]==0xFF and data[6+base:10+base]===Exif (or Duck)")
logger.debug("Did get 0x%X and %s",
ord(data[2+base]), data[6+base:10+base+1])
return {}
else:
# file format not recognized
logger.debug("File format not recognized.")
return {}
# deal with the EXIF info we found
logger.debug("Endian format is %s (%s)", endian, {
'I': 'Intel',
'M': 'Motorola',
'\x01':'Adobe Ducky',
'd':'XMP/Adobe unknown'
}[endian])
hdr = ExifHeader(f, endian, offset, fake_exif, strict, debug, details)
ifd_list = hdr.list_IFDs()
thumb_ifd = False
ctr = 0
for ifd in ifd_list:
if ctr == 0:
ifd_name = 'Image'
elif ctr == 1:
ifd_name = 'Thumbnail'
thumb_ifd = ifd
else:
ifd_name = 'IFD %d' % ctr
logger.debug('IFD %d (%s) at offset %d:', ctr, ifd_name, ifd)
hdr.dump_IFD(ifd, ifd_name, stop_tag=stop_tag)
# EXIF IFD
exif_off = hdr.tags.get(ifd_name + ' ExifOffset')
if exif_off:
logger.debug(' Exif SubIFD at offset %d:', exif_off.values[0])
hdr.dump_IFD(exif_off.values[0], 'EXIF', stop_tag=stop_tag)
# Interoperability IFD contained in EXIF IFD
intr_off = hdr.tags.get('EXIF SubIFD InteroperabilityOffset')
if intr_off:
logger.debug(' EXIF Interoperability SubSubIFD at offset %d:',
intr_off.values[0])
hdr.dump_IFD(intr_off.values[0], 'EXIF Interoperability',
tag_dict=INTR_TAGS, stop_tag=stop_tag)
# GPS IFD
gps_off = hdr.tags.get(ifd_name+' GPSInfo')
if gps_off:
logger.debug(' GPS SubIFD at offset %d:', gps_off.values[0])
hdr.dump_IFD(gps_off.values[0], 'GPS', tag_dict=GPS_TAGS, stop_tag=stop_tag)
ctr += 1
# deal with MakerNote contained in EXIF IFD
# (Some apps use MakerNote tags but do not use a format for which we
# have a description, do not process these).
if details and 'EXIF MakerNote' in hdr.tags and 'Image Make' in hdr.tags:
hdr.decode_maker_note()
# extract thumbnails
if details and thumb_ifd:
hdr.extract_tiff_thumbnail(thumb_ifd)
hdr.extract_jpeg_thumbnail()
return hdr.tags
|