# GNU MediaGoblin -- federated, autonomous media hosting # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . import os try: from PIL import Image except ImportError: import Image from mediagoblin.tools.exif import exif_fix_image_orientation, \ extract_exif, clean_exif, get_gps_data, get_useful from .resources import GOOD_JPG, EMPTY_JPG, BAD_JPG, GPS_JPG def assert_in(a, b): assert a in b, "%r not in %r" % (a, b) def test_exif_extraction(): ''' Test EXIF extraction from a good image ''' result = extract_exif(GOOD_JPG) clean = clean_exif(result) useful = get_useful(clean) gps = get_gps_data(result) # Do we have the result? assert len(result) == 56 # Do we have clean data? assert len(clean) == 53 # GPS data? assert gps == {} # Do we have the "useful" tags? assert useful == {'EXIF CVAPattern': {'field_length': 8, 'field_offset': 26224, 'field_type': 7, 'printable': u'[0, 2, 0, 2, 1, 2, 0, 1]', 'tag': 41730, 'values': [0, 2, 0, 2, 1, 2, 0, 1]}, 'EXIF ColorSpace': {'field_length': 2, 'field_offset': 476, 'field_type': 3, 'printable': u'sRGB', 'tag': 40961, 'values': [1]}, 'EXIF ComponentsConfiguration': {'field_length': 4, 'field_offset': 308, 'field_type': 7, 'printable': u'YCbCr', 'tag': 37121, 'values': [1, 2, 3, 0]}, 'EXIF CompressedBitsPerPixel': {'field_length': 8, 'field_offset': 756, 'field_type': 5, 'printable': u'4', 'tag': 37122, 'values': [[4, 1]]}, 'EXIF Contrast': {'field_length': 2, 'field_offset': 656, 'field_type': 3, 'printable': u'Soft', 'tag': 41992, 'values': [1]}, 'EXIF CustomRendered': {'field_length': 2, 'field_offset': 572, 'field_type': 3, 'printable': u'Normal', 'tag': 41985, 'values': [0]}, 'EXIF DateTimeDigitized': {'field_length': 20, 'field_offset': 736, 'field_type': 2, 'printable': u'2011:06:22 12:20:33', 'tag': 36868, 'values': u'2011:06:22 12:20:33'}, 'EXIF DateTimeOriginal': {'field_length': 20, 'field_offset': 716, 'field_type': 2, 'printable': u'2011:06:22 12:20:33', 'tag': 36867, 'values': u'2011:06:22 12:20:33'}, 'EXIF DigitalZoomRatio': {'field_length': 8, 'field_offset': 26232, 'field_type': 5, 'printable': u'1', 'tag': 41988, 'values': [[1, 1]]}, 'EXIF ExifImageLength': {'field_length': 2, 'field_offset': 500, 'field_type': 3, 'printable': u'2592', 'tag': 40963, 'values': [2592]}, 'EXIF ExifImageWidth': {'field_length': 2, 'field_offset': 488, 'field_type': 3, 'printable': u'3872', 'tag': 40962, 'values': [3872]}, 'EXIF ExifVersion': {'field_length': 4, 'field_offset': 272, 'field_type': 7, 'printable': u'0221', 'tag': 36864, 'values': [48, 50, 50, 49]}, 'EXIF ExposureBiasValue': {'field_length': 8, 'field_offset': 764, 'field_type': 10, 'printable': u'0', 'tag': 37380, 'values': [[0, 1]]}, 'EXIF ExposureMode': {'field_length': 2, 'field_offset': 584, 'field_type': 3, 'printable': u'Manual Exposure', 'tag': 41986, 'values': [1]}, 'EXIF ExposureProgram': {'field_length': 2, 'field_offset': 248, 'field_type': 3, 'printable': u'Manual', 'tag': 34850, 'values': [1]}, 'EXIF ExposureTime': {'field_length': 8, 'field_offset': 700, 'field_type': 5, 'printable': u'1/125', 'tag': 33434, 'values': [[1, 125]]}, 'EXIF FNumber': {'field_length': 8, 'field_offset': 708, 'field_type': 5, 'printable': u'10', 'tag': 33437, 'values': [[10, 1]]}, 'EXIF FileSource': {'field_length': 1, 'field_offset': 536, 'field_type': 7, 'printable': u'Digital Camera', 'tag': 41728, 'values': [3]}, 'EXIF Flash': {'field_length': 2, 'field_offset': 380, 'field_type': 3, 'printable': u'Flash did not fire', 'tag': 37385, 'values': [0]}, 'EXIF FlashPixVersion': {'field_length': 4, 'field_offset': 464, 'field_type': 7, 'printable': u'0100', 'tag': 40960, 'values': [48, 49, 48, 48]}, 'EXIF FocalLength': {'field_length': 8, 'field_offset': 780, 'field_type': 5, 'printable': u'18', 'tag': 37386, 'values': [[18, 1]]}, 'EXIF FocalLengthIn35mmFilm': {'field_length': 2, 'field_offset': 620, 'field_type': 3, 'printable': u'27', 'tag': 41989, 'values': [27]}, 'EXIF GainControl': {'field_length': 2, 'field_offset': 644, 'field_type': 3, 'printable': u'None', 'tag': 41991, 'values': [0]}, 'EXIF ISOSpeedRatings': {'field_length': 2, 'field_offset': 260, 'field_type': 3, 'printable': u'100', 'tag': 34855, 'values': [100]}, 'EXIF InteroperabilityOffset': {'field_length': 4, 'field_offset': 512, 'field_type': 4, 'printable': u'26240', 'tag': 40965, 'values': [26240]}, 'EXIF LightSource': {'field_length': 2, 'field_offset': 368, 'field_type': 3, 'printable': u'Unknown', 'tag': 37384, 'values': [0]}, 'EXIF MaxApertureValue': {'field_length': 8, 'field_offset': 772, 'field_type': 5, 'printable': u'18/5', 'tag': 37381, 'values': [[18, 5]]}, 'EXIF MeteringMode': {'field_length': 2, 'field_offset': 356, 'field_type': 3, 'printable': u'Pattern', 'tag': 37383, 'values': [5]}, 'EXIF Saturation': {'field_length': 2, 'field_offset': 668, 'field_type': 3, 'printable': u'Normal', 'tag': 41993, 'values': [0]}, 'EXIF SceneCaptureType': {'field_length': 2, 'field_offset': 632, 'field_type': 3, 'printable': u'Standard', 'tag': 41990, 'values': [0]}, 'EXIF SceneType': {'field_length': 1, 'field_offset': 548, 'field_type': 7, 'printable': u'Directly Photographed', 'tag': 41729, 'values': [1]}, 'EXIF SensingMethod': {'field_length': 2, 'field_offset': 524, 'field_type': 3, 'printable': u'One-chip color area', 'tag': 41495, 'values': [2]}, 'EXIF Sharpness': {'field_length': 2, 'field_offset': 680, 'field_type': 3, 'printable': u'Normal', 'tag': 41994, 'values': [0]}, 'EXIF SubSecTime': {'field_length': 3, 'field_offset': 428, 'field_type': 2, 'printable': u'10', 'tag': 37520, 'values': u'10'}, 'EXIF SubSecTimeDigitized': {'field_length': 3, 'field_offset': 452, 'field_type': 2, 'printable': u'10', 'tag': 37522, 'values': u'10'}, 'EXIF SubSecTimeOriginal': {'field_length': 3, 'field_offset': 440, 'field_type': 2, 'printable': u'10', 'tag': 37521, 'values': u'10'}, 'EXIF SubjectDistanceRange': {'field_length': 2, 'field_offset': 692, 'field_type': 3, 'printable': u'0', 'tag': 41996, 'values': [0]}, 'EXIF WhiteBalance': {'field_length': 2, 'field_offset': 596, 'field_type': 3, 'printable': u'Auto', 'tag': 41987, 'values': [0]}, 'Image DateTime': {'field_length': 20, 'field_offset': 194, 'field_type': 2, 'printable': u'2011:06:22 12:20:33', 'tag': 306, 'values': u'2011:06:22 12:20:33'}, 'Image ExifOffset': {'field_length': 4, 'field_offset': 126, 'field_type': 4, 'printable': u'214', 'tag': 34665, 'values': [214]}, 'Image Make': {'field_length': 18, 'field_offset': 134, 'field_type': 2, 'printable': u'NIKON CORPORATION', 'tag': 271, 'values': u'NIKON CORPORATION'}, 'Image Model': {'field_length': 10, 'field_offset': 152, 'field_type': 2, 'printable': u'NIKON D80', 'tag': 272, 'values': u'NIKON D80'}, 'Image Orientation': {'field_length': 2, 'field_offset': 42, 'field_type': 3, 'printable': u'Rotated 90 CCW', 'tag': 274, 'values': [6]}, 'Image ResolutionUnit': {'field_length': 2, 'field_offset': 78, 'field_type': 3, 'printable': u'Pixels/Inch', 'tag': 296, 'values': [2]}, 'Image Software': {'field_length': 15, 'field_offset': 178, 'field_type': 2, 'printable': u'Shotwell 0.9.3', 'tag': 305, 'values': u'Shotwell 0.9.3'}, 'Image XResolution': {'field_length': 8, 'field_offset': 162, 'field_type': 5, 'printable': u'300', 'tag': 282, 'values': [[300, 1]]}, 'Image YCbCrPositioning': {'field_length': 2, 'field_offset': 114, 'field_type': 3, 'printable': u'Co-sited', 'tag': 531, 'values': [2]}, 'Image YResolution': {'field_length': 8, 'field_offset': 170, 'field_type': 5, 'printable': u'300', 'tag': 283, 'values': [[300, 1]]}, 'Thumbnail Compression': {'field_length': 2, 'field_offset': 26280, 'field_type': 3, 'printable': u'JPEG (old-style)', 'tag': 259, 'values': [6]}, 'Thumbnail ResolutionUnit': {'field_length': 2, 'field_offset': 26316, 'field_type': 3, 'printable': u'Pixels/Inch', 'tag': 296, 'values': [2]}, 'Thumbnail XResolution': {'field_length': 8, 'field_offset': 26360, 'field_type': 5, 'printable': u'300', 'tag': 282, 'values': [[300, 1]]}, 'Thumbnail YCbCrPositioning': {'field_length': 2, 'field_offset': 26352, 'field_type': 3, 'printable': u'Co-sited', 'tag': 531, 'values': [2]}, 'Thumbnail YResolution': {'field_length': 8, 'field_offset': 26368, 'field_type': 5, 'printable': u'300', 'tag': 283, 'values': [[300, 1]]}} def test_exif_image_orientation(): ''' Test image reorientation based on EXIF data ''' result = extract_exif(GOOD_JPG) image = exif_fix_image_orientation( Image.open(GOOD_JPG), result) # Are the dimensions correct? assert image.size == (428, 640) # If this pixel looks right, the rest of the image probably will too. assert_in(image.getdata()[10000], ((41, 28, 11), (43, 27, 11)) ) def test_exif_no_exif(): ''' Test an image without exif ''' result = extract_exif(EMPTY_JPG) clean = clean_exif(result) useful = get_useful(clean) gps = get_gps_data(result) assert result == {} assert clean == {} assert gps == {} assert useful == {} def test_exif_bad_image(): ''' Test EXIF extraction from a faithful, but bad image ''' result = extract_exif(BAD_JPG) clean = clean_exif(result) useful = get_useful(clean) gps = get_gps_data(result) assert result == {} assert clean == {} assert gps == {} assert useful == {} def test_exif_gps_data(): ''' Test extractiion of GPS data ''' result = extract_exif(GPS_JPG) gps = get_gps_data(result) assert gps == { 'latitude': 59.336666666666666, 'direction': 25.674046740467404, 'altitude': 37.64365671641791, 'longitude': 18.016166666666667}