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