Fix another tests.
[mediagoblin: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) == 55
45
46     # Do we have clean data?
47     assert len(clean) == 53
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 CCW',
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 k, v in useful.items():
374         assert v == expected[k]
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     assert_in(image.getdata()[10000],
392               ((41, 28, 11), (43, 27, 11))
393               )
394
395
396 def test_exif_no_exif():
397     '''
398     Test an image without exif
399     '''
400     result = extract_exif(EMPTY_JPG)
401     clean = clean_exif(result)
402     useful = get_useful(clean)
403     gps = get_gps_data(result)
404
405     assert result == {}
406     assert clean == {}
407     assert gps == {}
408     assert useful == {}
409
410
411 def test_exif_bad_image():
412     '''
413     Test EXIF extraction from a faithful, but bad image
414     '''
415     result = extract_exif(BAD_JPG)
416     clean = clean_exif(result)
417     useful = get_useful(clean)
418     gps = get_gps_data(result)
419
420     assert result == {}
421     assert clean == {}
422     assert gps == {}
423     assert useful == {}
424
425
426 def test_exif_gps_data():
427     '''
428     Test extractiion of GPS data
429     '''
430     result = extract_exif(GPS_JPG)
431     gps = get_gps_data(result)
432
433     assert gps == {
434         'latitude': 59.336666666666666,
435         'direction': 25.674046740467404,
436         'altitude': 37.64365671641791,
437         'longitude': 18.016166666666667}