1 """ |
1 """ |
2 A custom EXIF parsing module, aimed at high performance. |
2 A custom EXIF parsing module, aimed at high performance. |
3 """ |
3 """ |
4 |
4 |
5 import struct, mmap, os |
5 import struct, mmap, os |
|
6 |
|
7 from utils import lazy_load, lazy_load_iter |
6 |
8 |
7 def read_struct (file, fmt) : |
9 def read_struct (file, fmt) : |
8 """ |
10 """ |
9 Utility function to read data from the a file using struct |
11 Utility function to read data from the a file using struct |
10 """ |
12 """ |
105 class Tag (object) : |
107 class Tag (object) : |
106 """ |
108 """ |
107 Represents a single Tag in an IFD |
109 Represents a single Tag in an IFD |
108 """ |
110 """ |
109 |
111 |
110 def __init__ (self, offset, tag, type, count, data_raw) : |
112 def __init__ (self, ifd, offset, tag, type, count, data_raw) : |
111 """ |
113 """ |
112 Build a Tag with the given binary items from the IFD entry |
114 Build a Tag with the given binary items from the IFD entry |
113 """ |
115 """ |
114 |
116 |
|
117 self.ifd = ifd |
115 self.offset = offset |
118 self.offset = offset |
116 self.tag = tag |
119 self.tag = tag |
117 self.type = type |
120 self.type = type |
118 self.count = count |
121 self.count = count |
119 self.data_raw = data_raw |
122 self.data_raw = data_raw |
124 # unpack it |
127 # unpack it |
125 if self.type_data : |
128 if self.type_data : |
126 self.type_format, self.type_name, self.type_func = self.type_data |
129 self.type_format, self.type_name, self.type_func = self.type_data |
127 |
130 |
128 # lookup the tag data for this tag |
131 # lookup the tag data for this tag |
129 self.tag_data = exif_data.EXIF_TAGS.get(tag) |
132 self.tag_data = self.ifd.tag_dict.get(tag) |
130 |
133 |
131 # unpack it |
|
132 if self.tag_data : |
|
133 # the EXIF tag name |
|
134 self.tag_name, self.tag_value_spec = self.tag_data |
|
135 |
|
136 @property |
134 @property |
137 def name (self) : |
135 def name (self) : |
138 """ |
136 """ |
139 Lookup the name of this tag via its code, returns None if unknown. |
137 Lookup the name of this tag via its code, returns None if unknown. |
140 """ |
138 """ |
141 |
139 |
142 if self.tag_data : |
140 if self.tag_data : |
143 return self.tag_name |
141 return self.tag_data.name |
144 |
142 |
145 else : |
143 else : |
146 return None |
144 return None |
147 |
145 |
148 def process_values (self, raw_values) : |
146 def process_values (self, raw_values) : |
164 |
162 |
165 Returns the comma-separated values by default. |
163 Returns the comma-separated values by default. |
166 """ |
164 """ |
167 |
165 |
168 if self.tag_data : |
166 if self.tag_data : |
169 spec = self.tag_value_spec |
167 # map it |
|
168 return self.tag_data.map_values(values) |
170 |
169 |
171 else : |
170 else : |
172 # fallback to default |
171 # default value-mapping |
173 spec = None |
172 return ", ".join(str(value) for value in values) |
174 |
|
175 # map it |
|
176 return exif_data.map_values(spec, values) |
|
177 |
173 |
178 # size of an IFD entry in bytes |
174 # size of an IFD entry in bytes |
179 IFD_ENTRY_SIZE = 12 |
175 IFD_ENTRY_SIZE = 12 |
180 |
176 |
181 class IFD (Buffer) : |
177 class IFD (Buffer) : |
182 """ |
178 """ |
183 Represents an IFD (Image file directory) region in EXIF data. |
179 Represents an IFD (Image file directory) region in EXIF data. |
184 """ |
180 """ |
185 |
181 |
186 def __init__ (self, buffer, **buffer_opts) : |
182 def __init__ (self, buffer, tag_dict, **buffer_opts) : |
187 """ |
183 """ |
188 Access the IFD data from the given bufferable object with given buffer opts. |
184 Access the IFD data from the given bufferable object with given buffer opts. |
189 |
185 |
190 This will read the `count` and `next_offset` values. |
186 This will read the `count` and `next_offset` values. |
191 """ |
187 """ |
192 |
188 |
193 # init |
189 # init |
194 super(IFD, self).__init__(buffer, **buffer_opts) |
190 super(IFD, self).__init__(buffer, **buffer_opts) |
|
191 |
|
192 # store |
|
193 self.tag_dict = tag_dict |
195 |
194 |
196 # read header |
195 # read header |
197 self.count = self.pread_item(0, 'H') |
196 self.count = self.pread_item(0, 'H') |
198 |
197 |
199 # read next-offset |
198 # read next-offset |
200 self.next_offset = self.pread_item(0x02 + self.count * IFD_ENTRY_SIZE, 'I') |
199 self.next_offset = self.pread_item(0x02 + self.count * IFD_ENTRY_SIZE, 'I') |
201 |
200 |
202 def iter_tags (self) : |
201 @lazy_load_iter |
|
202 def tags (self) : |
203 """ |
203 """ |
204 Iterate over all the Tag objects in this IFD |
204 Iterate over all the Tag objects in this IFD |
205 """ |
205 """ |
206 |
206 |
207 # read each tag |
207 # read each tag |
208 for offset in self.iter_offsets(self.count, IFD_ENTRY_SIZE, 0x02) : |
208 for offset in self.iter_offsets(self.count, IFD_ENTRY_SIZE, 0x02) : |
209 # read the tag data |
209 # read the tag data |
210 tag, type, count, data_raw = self.pread_struct(offset, 'HHI4s') |
210 tag, type, count, data_raw = self.pread_struct(offset, 'HHI4s') |
211 |
211 |
212 # yield the new Tag |
212 # yield the new Tag |
213 yield Tag(offset, tag, type, count, data_raw) |
213 yield Tag(self, offset, tag, type, count, data_raw) |
214 |
214 |
215 class EXIF (Buffer) : |
215 class EXIF (Buffer) : |
216 """ |
216 """ |
217 Represents the EXIF data embedded in some image file in the form of a Region. |
217 Represents the EXIF data embedded in some image file in the form of a Region. |
218 """ |
218 """ |
228 super(EXIF, self).__init__(buffer, **buffer_opts) |
228 super(EXIF, self).__init__(buffer, **buffer_opts) |
229 |
229 |
230 # store |
230 # store |
231 self.buffer = buffer |
231 self.buffer = buffer |
232 |
232 |
233 def iter_ifds (self) : |
233 @lazy_load_iter |
|
234 def ifds (self) : |
234 """ |
235 """ |
235 Iterate over the primary IFDs in this EXIF. |
236 Iterate over the primary IFDs in this EXIF. |
236 """ |
237 """ |
237 |
238 |
238 # starting offset |
239 # starting offset |
239 offset = self.pread_item(0x04, 'I') |
240 offset = self.pread_item(0x04, 'I') |
240 |
241 |
241 while offset : |
242 while offset : |
242 # create and read the IFD, operating on the right sub-buffer |
243 # create and read the IFD, operating on the right sub-buffer |
243 ifd = IFD(self.buf, offset=offset) |
244 ifd = IFD(self.buf, exif_data.EXIF_TAGS, offset=offset) |
244 |
245 |
245 # yield it |
246 # yield it |
246 yield ifd |
247 yield ifd |
247 |
248 |
248 # skip to next offset |
249 # skip to next offset |
498 Dump all tags from the given EXIF object to stdout |
497 Dump all tags from the given EXIF object to stdout |
499 """ |
498 """ |
500 |
499 |
501 print "EXIF offset=%#08x, size=%d:" % (exif.offset, exif.size) |
500 print "EXIF offset=%#08x, size=%d:" % (exif.offset, exif.size) |
502 |
501 |
503 for i, ifd in enumerate(exif.iter_ifds()) : |
502 for i, ifd in enumerate(exif.ifds) : |
504 print "\tIFD:%d offset=%#04x(%#08x), count=%d, next=%d:" % ( |
503 print "\tIFD:%d offset=%#04x(%#08x), count=%d, next=%d:" % ( |
505 i, |
504 i, |
506 ifd.offset, ifd.offset + exif.offset, |
505 ifd.offset, ifd.offset + exif.offset, |
507 ifd.count, |
506 ifd.count, |
508 ifd.next_offset |
507 ifd.next_offset |
509 ) |
508 ) |
510 |
509 |
511 for i, tag in enumerate(ifd.iter_tags()) : |
510 for i, tag in enumerate(ifd.tags) : |
512 data_info = exif.tag_data_info(tag) |
511 data_info = exif.tag_data_info(tag) |
513 |
512 |
514 if data_info : |
513 if data_info : |
515 data_fmt, data_offset, data_size = data_info |
514 data_fmt, data_offset, data_size = data_info |
516 |
515 |