Merge remote-tracking branch 'refs/remotes/tilly-q/ticket-874' into mergetest
[mediagoblin.git] / mediagoblin / tests / test_exif.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17 import os
18 try:
19 from PIL import Image
20 except ImportError:
21 import Image
22
23 from mediagoblin.tools.exif import exif_fix_image_orientation, \
24 extract_exif, clean_exif, get_gps_data, get_useful
25 from .resources import GOOD_JPG, EMPTY_JPG, BAD_JPG, GPS_JPG
26
27
28 def assert_in(a, b):
29 assert a in b, "%r not in %r" % (a, b)
30
31
32 def test_exif_extraction():
33 '''
34 Test EXIF extraction from a good image
35 '''
36 result = extract_exif(GOOD_JPG)
37 clean = clean_exif(result)
38 useful = get_useful(clean)
39 gps = get_gps_data(result)
40
41 # Do we have the result?
42 assert len(result) == 55
43
44 # Do we have clean data?
45 assert len(clean) == 53
46
47 # GPS data?
48 assert gps == {}
49
50 # Do we have the "useful" tags?
51 assert useful == {'EXIF CVAPattern': {'field_length': 8,
52 'field_offset': 26224,
53 'field_type': 7,
54 'printable': u'[0, 2, 0, 2, 1, 2, 0, 1]',
55 'tag': 41730,
56 'values': [0, 2, 0, 2, 1, 2, 0, 1]},
57 'EXIF ColorSpace': {'field_length': 2,
58 'field_offset': 476,
59 'field_type': 3,
60 'printable': u'sRGB',
61 'tag': 40961,
62 'values': [1]},
63 'EXIF ComponentsConfiguration': {'field_length': 4,
64 'field_offset': 308,
65 'field_type': 7,
66 'printable': u'YCbCr',
67 'tag': 37121,
68 'values': [1, 2, 3, 0]},
69 'EXIF CompressedBitsPerPixel': {'field_length': 8,
70 'field_offset': 756,
71 'field_type': 5,
72 'printable': u'4',
73 'tag': 37122,
74 'values': [[4, 1]]},
75 'EXIF Contrast': {'field_length': 2,
76 'field_offset': 656,
77 'field_type': 3,
78 'printable': u'Soft',
79 'tag': 41992,
80 'values': [1]},
81 'EXIF CustomRendered': {'field_length': 2,
82 'field_offset': 572,
83 'field_type': 3,
84 'printable': u'Normal',
85 'tag': 41985,
86 'values': [0]},
87 'EXIF DateTimeDigitized': {'field_length': 20,
88 'field_offset': 736,
89 'field_type': 2,
90 'printable': u'2011:06:22 12:20:33',
91 'tag': 36868,
92 'values': u'2011:06:22 12:20:33'},
93 'EXIF DateTimeOriginal': {'field_length': 20,
94 'field_offset': 716,
95 'field_type': 2,
96 'printable': u'2011:06:22 12:20:33',
97 'tag': 36867,
98 'values': u'2011:06:22 12:20:33'},
99 'EXIF DigitalZoomRatio': {'field_length': 8,
100 'field_offset': 26232,
101 'field_type': 5,
102 'printable': u'1',
103 'tag': 41988,
104 'values': [[1, 1]]},
105 'EXIF ExifImageLength': {'field_length': 2,
106 'field_offset': 500,
107 'field_type': 3,
108 'printable': u'2592',
109 'tag': 40963,
110 'values': [2592]},
111 'EXIF ExifImageWidth': {'field_length': 2,
112 'field_offset': 488,
113 'field_type': 3,
114 'printable': u'3872',
115 'tag': 40962,
116 'values': [3872]},
117 'EXIF ExifVersion': {'field_length': 4,
118 'field_offset': 272,
119 'field_type': 7,
120 'printable': u'0221',
121 'tag': 36864,
122 'values': [48, 50, 50, 49]},
123 'EXIF ExposureBiasValue': {'field_length': 8,
124 'field_offset': 764,
125 'field_type': 10,
126 'printable': u'0',
127 'tag': 37380,
128 'values': [[0, 1]]},
129 'EXIF ExposureMode': {'field_length': 2,
130 'field_offset': 584,
131 'field_type': 3,
132 'printable': u'Manual Exposure',
133 'tag': 41986,
134 'values': [1]},
135 'EXIF ExposureProgram': {'field_length': 2,
136 'field_offset': 248,
137 'field_type': 3,
138 'printable': u'Manual',
139 'tag': 34850,
140 'values': [1]},
141 'EXIF ExposureTime': {'field_length': 8,
142 'field_offset': 700,
143 'field_type': 5,
144 'printable': u'1/125',
145 'tag': 33434,
146 'values': [[1, 125]]},
147 'EXIF FNumber': {'field_length': 8,
148 'field_offset': 708,
149 'field_type': 5,
150 'printable': u'10',
151 'tag': 33437,
152 'values': [[10, 1]]},
153 'EXIF FileSource': {'field_length': 1,
154 'field_offset': 536,
155 'field_type': 7,
156 'printable': u'Digital Camera',
157 'tag': 41728,
158 'values': [3]},
159 'EXIF Flash': {'field_length': 2,
160 'field_offset': 380,
161 'field_type': 3,
162 'printable': u'Flash did not fire',
163 'tag': 37385,
164 'values': [0]},
165 'EXIF FlashPixVersion': {'field_length': 4,
166 'field_offset': 464,
167 'field_type': 7,
168 'printable': u'0100',
169 'tag': 40960,
170 'values': [48, 49, 48, 48]},
171 'EXIF FocalLength': {'field_length': 8,
172 'field_offset': 780,
173 'field_type': 5,
174 'printable': u'18',
175 'tag': 37386,
176 'values': [[18, 1]]},
177 'EXIF FocalLengthIn35mmFilm': {'field_length': 2,
178 'field_offset': 620,
179 'field_type': 3,
180 'printable': u'27',
181 'tag': 41989,
182 'values': [27]},
183 'EXIF GainControl': {'field_length': 2,
184 'field_offset': 644,
185 'field_type': 3,
186 'printable': u'None',
187 'tag': 41991,
188 'values': [0]},
189 'EXIF ISOSpeedRatings': {'field_length': 2,
190 'field_offset': 260,
191 'field_type': 3,
192 'printable': u'100',
193 'tag': 34855,
194 'values': [100]},
195 'EXIF InteroperabilityOffset': {'field_length': 4,
196 'field_offset': 512,
197 'field_type': 4,
198 'printable': u'26240',
199 'tag': 40965,
200 'values': [26240]},
201 'EXIF LightSource': {'field_length': 2,
202 'field_offset': 368,
203 'field_type': 3,
204 'printable': u'Unknown',
205 'tag': 37384,
206 'values': [0]},
207 'EXIF MaxApertureValue': {'field_length': 8,
208 'field_offset': 772,
209 'field_type': 5,
210 'printable': u'18/5',
211 'tag': 37381,
212 'values': [[18, 5]]},
213 'EXIF MeteringMode': {'field_length': 2,
214 'field_offset': 356,
215 'field_type': 3,
216 'printable': u'Pattern',
217 'tag': 37383,
218 'values': [5]},
219 'EXIF Saturation': {'field_length': 2,
220 'field_offset': 668,
221 'field_type': 3,
222 'printable': u'Normal',
223 'tag': 41993,
224 'values': [0]},
225 'EXIF SceneCaptureType': {'field_length': 2,
226 'field_offset': 632,
227 'field_type': 3,
228 'printable': u'Standard',
229 'tag': 41990,
230 'values': [0]},
231 'EXIF SceneType': {'field_length': 1,
232 'field_offset': 548,
233 'field_type': 7,
234 'printable': u'Directly Photographed',
235 'tag': 41729,
236 'values': [1]},
237 'EXIF SensingMethod': {'field_length': 2,
238 'field_offset': 524,
239 'field_type': 3,
240 'printable': u'One-chip color area',
241 'tag': 41495,
242 'values': [2]},
243 'EXIF Sharpness': {'field_length': 2,
244 'field_offset': 680,
245 'field_type': 3,
246 'printable': u'Normal',
247 'tag': 41994,
248 'values': [0]},
249 'EXIF SubSecTime': {'field_length': 3,
250 'field_offset': 428,
251 'field_type': 2,
252 'printable': u'10',
253 'tag': 37520,
254 'values': u'10'},
255 'EXIF SubSecTimeDigitized': {'field_length': 3,
256 'field_offset': 452,
257 'field_type': 2,
258 'printable': u'10',
259 'tag': 37522,
260 'values': u'10'},
261 'EXIF SubSecTimeOriginal': {'field_length': 3,
262 'field_offset': 440,
263 'field_type': 2,
264 'printable': u'10',
265 'tag': 37521,
266 'values': u'10'},
267 'EXIF SubjectDistanceRange': {'field_length': 2,
268 'field_offset': 692,
269 'field_type': 3,
270 'printable': u'0',
271 'tag': 41996,
272 'values': [0]},
273 'EXIF WhiteBalance': {'field_length': 2,
274 'field_offset': 596,
275 'field_type': 3,
276 'printable': u'Auto',
277 'tag': 41987,
278 'values': [0]},
279 'Image DateTime': {'field_length': 20,
280 'field_offset': 194,
281 'field_type': 2,
282 'printable': u'2011:06:22 12:20:33',
283 'tag': 306,
284 'values': u'2011:06:22 12:20:33'},
285 'Image ExifOffset': {'field_length': 4,
286 'field_offset': 126,
287 'field_type': 4,
288 'printable': u'214',
289 'tag': 34665,
290 'values': [214]},
291 'Image Make': {'field_length': 18,
292 'field_offset': 134,
293 'field_type': 2,
294 'printable': u'NIKON CORPORATION',
295 'tag': 271,
296 'values': u'NIKON CORPORATION'},
297 'Image Model': {'field_length': 10,
298 'field_offset': 152,
299 'field_type': 2,
300 'printable': u'NIKON D80',
301 'tag': 272,
302 'values': u'NIKON D80'},
303 'Image Orientation': {'field_length': 2,
304 'field_offset': 42,
305 'field_type': 3,
306 'printable': u'Rotated 90 CCW',
307 'tag': 274,
308 'values': [6]},
309 'Image ResolutionUnit': {'field_length': 2,
310 'field_offset': 78,
311 'field_type': 3,
312 'printable': u'Pixels/Inch',
313 'tag': 296,
314 'values': [2]},
315 'Image Software': {'field_length': 15,
316 'field_offset': 178,
317 'field_type': 2,
318 'printable': u'Shotwell 0.9.3',
319 'tag': 305,
320 'values': u'Shotwell 0.9.3'},
321 'Image XResolution': {'field_length': 8,
322 'field_offset': 162,
323 'field_type': 5,
324 'printable': u'300',
325 'tag': 282,
326 'values': [[300, 1]]},
327 'Image YCbCrPositioning': {'field_length': 2,
328 'field_offset': 114,
329 'field_type': 3,
330 'printable': u'Co-sited',
331 'tag': 531,
332 'values': [2]},
333 'Image YResolution': {'field_length': 8,
334 'field_offset': 170,
335 'field_type': 5,
336 'printable': u'300',
337 'tag': 283,
338 'values': [[300, 1]]},
339 'Thumbnail Compression': {'field_length': 2,
340 'field_offset': 26280,
341 'field_type': 3,
342 'printable': u'JPEG (old-style)',
343 'tag': 259,
344 'values': [6]},
345 'Thumbnail ResolutionUnit': {'field_length': 2,
346 'field_offset': 26316,
347 'field_type': 3,
348 'printable': u'Pixels/Inch',
349 'tag': 296,
350 'values': [2]},
351 'Thumbnail XResolution': {'field_length': 8,
352 'field_offset': 26360,
353 'field_type': 5,
354 'printable': u'300',
355 'tag': 282,
356 'values': [[300, 1]]},
357 'Thumbnail YCbCrPositioning': {'field_length': 2,
358 'field_offset': 26352,
359 'field_type': 3,
360 'printable': u'Co-sited',
361 'tag': 531,
362 'values': [2]},
363 'Thumbnail YResolution': {'field_length': 8,
364 'field_offset': 26368,
365 'field_type': 5,
366 'printable': u'300',
367 'tag': 283,
368 'values': [[300, 1]]}}
369
370
371 def test_exif_image_orientation():
372 '''
373 Test image reorientation based on EXIF data
374 '''
375 result = extract_exif(GOOD_JPG)
376
377 image = exif_fix_image_orientation(
378 Image.open(GOOD_JPG),
379 result)
380
381 # Are the dimensions correct?
382 assert image.size == (428, 640)
383
384 # If this pixel looks right, the rest of the image probably will too.
385 assert_in(image.getdata()[10000],
386 ((41, 28, 11), (43, 27, 11))
387 )
388
389
390 def test_exif_no_exif():
391 '''
392 Test an image without exif
393 '''
394 result = extract_exif(EMPTY_JPG)
395 clean = clean_exif(result)
396 useful = get_useful(clean)
397 gps = get_gps_data(result)
398
399 assert result == {}
400 assert clean == {}
401 assert gps == {}
402 assert useful == {}
403
404
405 def test_exif_bad_image():
406 '''
407 Test EXIF extraction from a faithful, but bad image
408 '''
409 result = extract_exif(BAD_JPG)
410 clean = clean_exif(result)
411 useful = get_useful(clean)
412 gps = get_gps_data(result)
413
414 assert result == {}
415 assert clean == {}
416 assert gps == {}
417 assert useful == {}
418
419
420 def test_exif_gps_data():
421 '''
422 Test extractiion of GPS data
423 '''
424 result = extract_exif(GPS_JPG)
425 gps = get_gps_data(result)
426
427 assert gps == {
428 'latitude': 59.336666666666666,
429 'direction': 25.674046740467404,
430 'altitude': 37.64365671641791,
431 'longitude': 18.016166666666667}