1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
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.
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.
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/>.
23 from collections
import OrderedDict
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
31 assert a
in b
, "%r not in %r" % (a
, b
)
34 def test_exif_extraction():
36 Test EXIF extraction from a good image
38 result
= extract_exif(GOOD_JPG
)
39 clean
= clean_exif(result
)
40 useful
= get_useful(clean
)
41 gps
= get_gps_data(result
)
43 # Do we have the result?
44 assert len(result
) >= 50
46 # Do we have clean data?
47 assert len(clean
) >= 50
52 # Do we have the "useful" tags?
54 expected
= OrderedDict({'EXIF CVAPattern': {'field_length': 8,
55 'field_offset': 26224,
57 'printable': '[0, 2, 0, 2, 1, 2, 0, 1]',
59 'values': [0, 2, 0, 2, 1, 2, 0, 1]},
60 'EXIF ColorSpace': {'field_length': 2,
66 'EXIF ComponentsConfiguration': {'field_length': 4,
71 'values': [1, 2, 3, 0]},
72 'EXIF CompressedBitsPerPixel': {'field_length': 8,
78 'EXIF Contrast': {'field_length': 2,
84 'EXIF CustomRendered': {'field_length': 2,
87 'printable': u
'Normal',
90 'EXIF DateTimeDigitized': {'field_length': 20,
93 'printable': u
'2011:06:22 12:20:33',
95 'values': u
'2011:06:22 12:20:33'},
96 'EXIF DateTimeOriginal': {'field_length': 20,
99 'printable': u
'2011:06:22 12:20:33',
101 'values': u
'2011:06:22 12:20:33'},
102 'EXIF DigitalZoomRatio': {'field_length': 8,
103 'field_offset': 26232,
108 'EXIF ExifImageLength': {'field_length': 2,
111 'printable': u
'2592',
114 'EXIF ExifImageWidth': {'field_length': 2,
117 'printable': u
'3872',
120 'EXIF ExifVersion': {'field_length': 4,
123 'printable': u
'0221',
125 'values': [48, 50, 50, 49]},
126 'EXIF ExposureBiasValue': {'field_length': 8,
132 'EXIF ExposureMode': {'field_length': 2,
135 'printable': u
'Manual Exposure',
138 'EXIF ExposureProgram': {'field_length': 2,
141 'printable': u
'Manual',
144 'EXIF ExposureTime': {'field_length': 8,
147 'printable': u
'1/125',
149 'values': [[1, 125]]},
150 'EXIF FNumber': {'field_length': 8,
155 'values': [[10, 1]]},
156 'EXIF FileSource': {'field_length': 1,
159 'printable': u
'Digital Camera',
162 'EXIF Flash': {'field_length': 2,
165 'printable': u
'Flash did not fire',
168 'EXIF FlashPixVersion': {'field_length': 4,
171 'printable': u
'0100',
173 'values': [48, 49, 48, 48]},
174 'EXIF FocalLength': {'field_length': 8,
179 'values': [[18, 1]]},
180 'EXIF FocalLengthIn35mmFilm': {'field_length': 2,
186 'EXIF GainControl': {'field_length': 2,
189 'printable': u
'None',
192 'EXIF ISOSpeedRatings': {'field_length': 2,
198 'EXIF InteroperabilityOffset': {'field_length': 4,
201 'printable': u
'26240',
204 'EXIF LightSource': {'field_length': 2,
207 'printable': u
'Unknown',
210 'EXIF MaxApertureValue': {'field_length': 8,
213 'printable': u
'18/5',
215 'values': [[18, 5]]},
216 'EXIF MeteringMode': {'field_length': 2,
219 'printable': u
'Pattern',
222 'EXIF Saturation': {'field_length': 2,
225 'printable': u
'Normal',
228 'EXIF SceneCaptureType': {'field_length': 2,
231 'printable': u
'Standard',
234 'EXIF SceneType': {'field_length': 1,
237 'printable': u
'Directly Photographed',
240 'EXIF SensingMethod': {'field_length': 2,
243 'printable': u
'One-chip color area',
246 'EXIF Sharpness': {'field_length': 2,
249 'printable': u
'Normal',
252 'EXIF SubSecTime': {'field_length': 3,
258 'EXIF SubSecTimeDigitized': {'field_length': 3,
264 'EXIF SubSecTimeOriginal': {'field_length': 3,
270 'EXIF SubjectDistanceRange': {'field_length': 2,
276 'EXIF WhiteBalance': {'field_length': 2,
279 'printable': u
'Auto',
282 'Image DateTime': {'field_length': 20,
285 'printable': u
'2011:06:22 12:20:33',
287 'values': u
'2011:06:22 12:20:33'},
288 'Image ExifOffset': {'field_length': 4,
294 'Image Make': {'field_length': 18,
297 'printable': u
'NIKON CORPORATION',
299 'values': u
'NIKON CORPORATION'},
300 'Image Model': {'field_length': 10,
303 'printable': u
'NIKON D80',
305 'values': u
'NIKON D80'},
306 'Image Orientation': {'field_length': 2,
309 'printable': u
'Rotated 90 CW',
312 'Image ResolutionUnit': {'field_length': 2,
315 'printable': u
'Pixels/Inch',
318 'Image Software': {'field_length': 15,
321 'printable': u
'Shotwell 0.9.3',
323 'values': u
'Shotwell 0.9.3'},
324 'Image XResolution': {'field_length': 8,
329 'values': [[300, 1]]},
330 'Image YCbCrPositioning': {'field_length': 2,
333 'printable': u
'Co-sited',
336 'Image YResolution': {'field_length': 8,
341 'values': [[300, 1]]},
342 'Thumbnail Compression': {'field_length': 2,
343 'field_offset': 26280,
345 'printable': u
'JPEG (old-style)',
348 'Thumbnail ResolutionUnit': {'field_length': 2,
349 'field_offset': 26316,
351 'printable': u
'Pixels/Inch',
354 'Thumbnail XResolution': {'field_length': 8,
355 'field_offset': 26360,
359 'values': [[300, 1]]},
360 'Thumbnail YCbCrPositioning': {'field_length': 2,
361 'field_offset': 26352,
363 'printable': u
'Co-sited',
366 'Thumbnail YResolution': {'field_length': 8,
367 'field_offset': 26368,
371 'values': [[300, 1]]}})
373 for key
in expected
.keys():
374 assert useful
[key
] == expected
[key
]
377 def test_exif_image_orientation():
379 Test image reorientation based on EXIF data
381 result
= extract_exif(GOOD_JPG
)
383 image
= exif_fix_image_orientation(
384 Image
.open(GOOD_JPG
),
387 # Are the dimensions correct?
388 assert image
.size
in ((428, 640), (640, 428))
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))
398 def test_exif_no_exif():
400 Test an image without exif
402 result
= extract_exif(EMPTY_JPG
)
403 clean
= clean_exif(result
)
404 useful
= get_useful(clean
)
405 gps
= get_gps_data(result
)
413 def test_exif_bad_image():
415 Test EXIF extraction from a faithful, but bad image
417 result
= extract_exif(BAD_JPG
)
418 clean
= clean_exif(result
)
419 useful
= get_useful(clean
)
420 gps
= get_gps_data(result
)
428 def test_exif_gps_data():
430 Test extractiion of GPS data
432 result
= extract_exif(GPS_JPG
)
433 gps
= get_gps_data(result
)
436 'latitude': 59.336666666666666,
437 'direction': 25.674046740467404,
438 'altitude': 37.64365671641791,
439 'longitude': 18.016166666666667}