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}
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, }
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 process_image(proc_state): """Code to process an image. Will be run by celery. A Workbench() represents a local tempory dir. It is automatically cleaned up when this function exits. """ entry = proc_state.entry workbench = proc_state.workbench # Conversions subdirectory to avoid collisions conversions_subdir = os.path.join( workbench.dir, 'conversions') os.mkdir(conversions_subdir) queued_filename = proc_state.get_queued_filename() name_builder = FilenameBuilder(queued_filename) # EXIF extraction exif_tags = extract_exif(queued_filename) gps_data = get_gps_data(exif_tags) # Always create a small thumbnail resize_tool(proc_state, True, 'thumb', name_builder.fill('{basename}.thumbnail{ext}'), conversions_subdir, exif_tags) # Possibly create a medium resize_tool(proc_state, False, 'medium', name_builder.fill('{basename}.medium{ext}'), conversions_subdir, exif_tags) # Copy our queued local workbench to its final destination proc_state.copy_original(name_builder.fill('{basename}{ext}')) # Remove queued media file from storage and database proc_state.delete_queue_file() # Insert exif data into database exif_all = clean_exif(exif_tags) if len(exif_all): entry.media_data_init(exif_all=exif_all) if len(gps_data): for key in list(gps_data.keys()): gps_data['gps_' + key] = gps_data.pop(key) entry.media_data_init(**gps_data)
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 in ((428, 640), (640, 428)) # If this pixel looks right, the rest of the image probably will too. # It seems different values are being seen on different platforms/systems # as of ccca39f1 it seems we're adding to the list those which are seen. assert_in(image.getdata()[10000], ((37, 23, 14), (41, 28, 11), (43, 27, 11)) )
def common_setup(self): """ Set up the workbench directory and pull down the original file """ self.image_config = mgg.global_config[ 'media_type:mediagoblin.media_types.image'] ## @@: Should this be two functions? # Conversions subdirectory to avoid collisions self.conversions_subdir = os.path.join( self.workbench.dir, 'convirsions') os.mkdir(self.conversions_subdir) # Pull down and set up the processing file self.process_filename = get_process_filename( self.entry, self.workbench, self.acceptable_files) self.name_builder = FilenameBuilder(self.process_filename) # Exif extraction self.exif_tags = extract_exif(self.process_filename)
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 Flash': { 'field_type': 3, 'printable': u'Flash did not fire', 'field_offset': 380, 'tag': 37385, 'values': [0], 'field_length': 2}, 'EXIF ExposureTime': { 'field_type': 5, 'printable': '1/125', 'field_offset': 700, 'tag': 33434, 'values': [[1, 125]], 'field_length': 8}, 'EXIF FocalLength': { 'field_type': 5, 'printable': '18', 'field_offset': 780, 'tag': 37386, 'values': [[18, 1]], 'field_length': 8}, 'Image Model': { 'field_type': 2, 'printable': 'NIKON D80', 'field_offset': 152, 'tag': 272, 'values': 'NIKON D80', 'field_length': 10}, 'Image Make': { 'field_type': 2, 'printable': 'NIKON CORPORATION', 'field_offset': 134, 'tag': 271, 'values': 'NIKON CORPORATION', 'field_length': 18}, 'EXIF ExposureMode': { 'field_type': 3, 'printable': 'Manual Exposure', 'field_offset': 584, 'tag': 41986, 'values': [1], 'field_length': 2}, 'EXIF ISOSpeedRatings': { 'field_type': 3, 'printable': '100', 'field_offset': 260, 'tag': 34855, 'values': [100], 'field_length': 2}, 'EXIF FNumber': { 'field_type': 5, 'printable': '10', 'field_offset': 708, 'tag': 33437, 'values': [[10, 1]], 'field_length': 8}}
proc_state.copy_original(name_builder.fill('{basename}{ext}')) # Remove queued media file from storage and database proc_state.delete_queue_file() # Insert exif data into database exif_all = clean_exif(exif_tags) if len(exif_all): entry.media_data_init(exif_all=exif_all) if len(gps_data): for key in list(gps_data.keys()): gps_data['gps_' + key] = gps_data.pop(key) entry.media_data_init(**gps_data) if __name__ == '__main__': import sys import pprint pp = pprint.PrettyPrinter() result = extract_exif(sys.argv[1]) gps = get_gps_data(result) clean = clean_exif(result) useful = get_useful(clean) print pp.pprint( clean)
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 Flash": { "field_type": 3, "printable": u"Flash did not fire", "field_offset": 380, "tag": 37385, "values": [0], "field_length": 2, }, "EXIF ExposureTime": { "field_type": 5, "printable": "1/125", "field_offset": 700, "tag": 33434, "values": [[1, 125]], "field_length": 8, }, "EXIF FocalLength": { "field_type": 5, "printable": "18", "field_offset": 780, "tag": 37386, "values": [[18, 1]], "field_length": 8, }, "Image Model": { "field_type": 2, "printable": "NIKON D80", "field_offset": 152, "tag": 272, "values": "NIKON D80", "field_length": 10, }, "Image Make": { "field_type": 2, "printable": "NIKON CORPORATION", "field_offset": 134, "tag": 271, "values": "NIKON CORPORATION", "field_length": 18, }, "EXIF ExposureMode": { "field_type": 3, "printable": "Manual Exposure", "field_offset": 584, "tag": 41986, "values": [1], "field_length": 2, }, "EXIF ISOSpeedRatings": { "field_type": 3, "printable": "100", "field_offset": 260, "tag": 34855, "values": [100], "field_length": 2, }, "EXIF FNumber": { "field_type": 5, "printable": "10", "field_offset": 708, "tag": 33437, "values": [[10, 1]], "field_length": 8, }, }
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) >= 50 # Do we have clean data? assert len(clean) >= 50 # GPS data? assert gps == {} # Do we have the "useful" tags? expected = OrderedDict({ 'EXIF CVAPattern': { 'field_length': 8, 'field_offset': 26224, 'field_type': 7, 'printable': '[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': 'sRGB', 'tag': 40961, 'values': [1] }, 'EXIF ComponentsConfiguration': { 'field_length': 4, 'field_offset': 308, 'field_type': 7, 'printable': 'YCbCr', 'tag': 37121, 'values': [1, 2, 3, 0] }, 'EXIF CompressedBitsPerPixel': { 'field_length': 8, 'field_offset': 756, 'field_type': 5, 'printable': '4', 'tag': 37122, 'values': [[4, 1]] }, 'EXIF Contrast': { 'field_length': 2, 'field_offset': 656, 'field_type': 3, 'printable': 'Soft', 'tag': 41992, 'values': [1] }, 'EXIF CustomRendered': { 'field_length': 2, 'field_offset': 572, 'field_type': 3, 'printable': 'Normal', 'tag': 41985, 'values': [0] }, 'EXIF DateTimeDigitized': { 'field_length': 20, 'field_offset': 736, 'field_type': 2, 'printable': '2011:06:22 12:20:33', 'tag': 36868, 'values': '2011:06:22 12:20:33' }, 'EXIF DateTimeOriginal': { 'field_length': 20, 'field_offset': 716, 'field_type': 2, 'printable': '2011:06:22 12:20:33', 'tag': 36867, 'values': '2011:06:22 12:20:33' }, 'EXIF DigitalZoomRatio': { 'field_length': 8, 'field_offset': 26232, 'field_type': 5, 'printable': '1', 'tag': 41988, 'values': [[1, 1]] }, 'EXIF ExifImageLength': { 'field_length': 2, 'field_offset': 500, 'field_type': 3, 'printable': '2592', 'tag': 40963, 'values': [2592] }, 'EXIF ExifImageWidth': { 'field_length': 2, 'field_offset': 488, 'field_type': 3, 'printable': '3872', 'tag': 40962, 'values': [3872] }, 'EXIF ExifVersion': { 'field_length': 4, 'field_offset': 272, 'field_type': 7, 'printable': '0221', 'tag': 36864, 'values': [48, 50, 50, 49] }, 'EXIF ExposureBiasValue': { 'field_length': 8, 'field_offset': 764, 'field_type': 10, 'printable': '0', 'tag': 37380, 'values': [[0, 1]] }, 'EXIF ExposureMode': { 'field_length': 2, 'field_offset': 584, 'field_type': 3, 'printable': 'Manual Exposure', 'tag': 41986, 'values': [1] }, 'EXIF ExposureProgram': { 'field_length': 2, 'field_offset': 248, 'field_type': 3, 'printable': 'Manual', 'tag': 34850, 'values': [1] }, 'EXIF ExposureTime': { 'field_length': 8, 'field_offset': 700, 'field_type': 5, 'printable': '1/125', 'tag': 33434, 'values': [[1, 125]] }, 'EXIF FNumber': { 'field_length': 8, 'field_offset': 708, 'field_type': 5, 'printable': '10', 'tag': 33437, 'values': [[10, 1]] }, 'EXIF FileSource': { 'field_length': 1, 'field_offset': 536, 'field_type': 7, 'printable': 'Digital Camera', 'tag': 41728, 'values': [3] }, 'EXIF Flash': { 'field_length': 2, 'field_offset': 380, 'field_type': 3, 'printable': 'Flash did not fire', 'tag': 37385, 'values': [0] }, 'EXIF FlashPixVersion': { 'field_length': 4, 'field_offset': 464, 'field_type': 7, 'printable': '0100', 'tag': 40960, 'values': [48, 49, 48, 48] }, 'EXIF FocalLength': { 'field_length': 8, 'field_offset': 780, 'field_type': 5, 'printable': '18', 'tag': 37386, 'values': [[18, 1]] }, 'EXIF FocalLengthIn35mmFilm': { 'field_length': 2, 'field_offset': 620, 'field_type': 3, 'printable': '27', 'tag': 41989, 'values': [27] }, 'EXIF GainControl': { 'field_length': 2, 'field_offset': 644, 'field_type': 3, 'printable': 'None', 'tag': 41991, 'values': [0] }, 'EXIF ISOSpeedRatings': { 'field_length': 2, 'field_offset': 260, 'field_type': 3, 'printable': '100', 'tag': 34855, 'values': [100] }, 'EXIF InteroperabilityOffset': { 'field_length': 4, 'field_offset': 512, 'field_type': 4, 'printable': '26240', 'tag': 40965, 'values': [26240] }, 'EXIF LightSource': { 'field_length': 2, 'field_offset': 368, 'field_type': 3, 'printable': 'Unknown', 'tag': 37384, 'values': [0] }, 'EXIF MaxApertureValue': { 'field_length': 8, 'field_offset': 772, 'field_type': 5, 'printable': '18/5', 'tag': 37381, 'values': [[18, 5]] }, 'EXIF MeteringMode': { 'field_length': 2, 'field_offset': 356, 'field_type': 3, 'printable': 'Pattern', 'tag': 37383, 'values': [5] }, 'EXIF Saturation': { 'field_length': 2, 'field_offset': 668, 'field_type': 3, 'printable': 'Normal', 'tag': 41993, 'values': [0] }, 'EXIF SceneCaptureType': { 'field_length': 2, 'field_offset': 632, 'field_type': 3, 'printable': 'Standard', 'tag': 41990, 'values': [0] }, 'EXIF SceneType': { 'field_length': 1, 'field_offset': 548, 'field_type': 7, 'printable': 'Directly Photographed', 'tag': 41729, 'values': [1] }, 'EXIF SensingMethod': { 'field_length': 2, 'field_offset': 524, 'field_type': 3, 'printable': 'One-chip color area', 'tag': 41495, 'values': [2] }, 'EXIF Sharpness': { 'field_length': 2, 'field_offset': 680, 'field_type': 3, 'printable': 'Normal', 'tag': 41994, 'values': [0] }, 'EXIF SubSecTime': { 'field_length': 3, 'field_offset': 428, 'field_type': 2, 'printable': '10', 'tag': 37520, 'values': '10' }, 'EXIF SubSecTimeDigitized': { 'field_length': 3, 'field_offset': 452, 'field_type': 2, 'printable': '10', 'tag': 37522, 'values': '10' }, 'EXIF SubSecTimeOriginal': { 'field_length': 3, 'field_offset': 440, 'field_type': 2, 'printable': '10', 'tag': 37521, 'values': '10' }, 'EXIF SubjectDistanceRange': { 'field_length': 2, 'field_offset': 692, 'field_type': 3, 'printable': '0', 'tag': 41996, 'values': [0] }, 'EXIF WhiteBalance': { 'field_length': 2, 'field_offset': 596, 'field_type': 3, 'printable': 'Auto', 'tag': 41987, 'values': [0] }, 'Image DateTime': { 'field_length': 20, 'field_offset': 194, 'field_type': 2, 'printable': '2011:06:22 12:20:33', 'tag': 306, 'values': '2011:06:22 12:20:33' }, 'Image ExifOffset': { 'field_length': 4, 'field_offset': 126, 'field_type': 4, 'printable': '214', 'tag': 34665, 'values': [214] }, 'Image Make': { 'field_length': 18, 'field_offset': 134, 'field_type': 2, 'printable': 'NIKON CORPORATION', 'tag': 271, 'values': 'NIKON CORPORATION' }, 'Image Model': { 'field_length': 10, 'field_offset': 152, 'field_type': 2, 'printable': 'NIKON D80', 'tag': 272, 'values': 'NIKON D80' }, 'Image Orientation': { 'field_length': 2, 'field_offset': 42, 'field_type': 3, 'printable': 'Rotated 90 CW', 'tag': 274, 'values': [6] }, 'Image ResolutionUnit': { 'field_length': 2, 'field_offset': 78, 'field_type': 3, 'printable': 'Pixels/Inch', 'tag': 296, 'values': [2] }, 'Image Software': { 'field_length': 15, 'field_offset': 178, 'field_type': 2, 'printable': 'Shotwell 0.9.3', 'tag': 305, 'values': 'Shotwell 0.9.3' }, 'Image XResolution': { 'field_length': 8, 'field_offset': 162, 'field_type': 5, 'printable': '300', 'tag': 282, 'values': [[300, 1]] }, 'Image YCbCrPositioning': { 'field_length': 2, 'field_offset': 114, 'field_type': 3, 'printable': 'Co-sited', 'tag': 531, 'values': [2] }, 'Image YResolution': { 'field_length': 8, 'field_offset': 170, 'field_type': 5, 'printable': '300', 'tag': 283, 'values': [[300, 1]] }, 'Thumbnail Compression': { 'field_length': 2, 'field_offset': 26280, 'field_type': 3, 'printable': 'JPEG (old-style)', 'tag': 259, 'values': [6] }, 'Thumbnail ResolutionUnit': { 'field_length': 2, 'field_offset': 26316, 'field_type': 3, 'printable': 'Pixels/Inch', 'tag': 296, 'values': [2] }, 'Thumbnail XResolution': { 'field_length': 8, 'field_offset': 26360, 'field_type': 5, 'printable': '300', 'tag': 282, 'values': [[300, 1]] }, 'Thumbnail YCbCrPositioning': { 'field_length': 2, 'field_offset': 26352, 'field_type': 3, 'printable': 'Co-sited', 'tag': 531, 'values': [2] }, 'Thumbnail YResolution': { 'field_length': 8, 'field_offset': 26368, 'field_type': 5, 'printable': '300', 'tag': 283, 'values': [[300, 1]] } }) for key in expected.keys(): assert useful[key] == expected[key]
def process_image(entry): """ Code to process an image """ workbench = mgg.workbench_manager.create_workbench() # Conversions subdirectory to avoid collisions conversions_subdir = os.path.join( workbench.dir, 'conversions') os.mkdir(conversions_subdir) queued_filepath = entry.queued_media_file queued_filename = workbench.localized_file( mgg.queue_store, queued_filepath, 'source') name_builder = FilenameBuilder(queued_filename) # EXIF extraction exif_tags = extract_exif(queued_filename) gps_data = get_gps_data(exif_tags) # Always create a small thumbnail thumb_filepath = create_pub_filepath( entry, name_builder.fill('{basename}.thumbnail{ext}')) resize_image(entry, queued_filename, thumb_filepath, exif_tags, conversions_subdir, (mgg.global_config['media:thumb']['max_width'], mgg.global_config['media:thumb']['max_height'])) # If the size of the original file exceeds the specified size of a `medium` # file, a `.medium.jpg` files is created and later associated with the media # entry. medium = Image.open(queued_filename) if medium.size[0] > mgg.global_config['media:medium']['max_width'] \ or medium.size[1] > mgg.global_config['media:medium']['max_height'] \ or exif_image_needs_rotation(exif_tags): medium_filepath = create_pub_filepath( entry, name_builder.fill('{basename}.medium{ext}')) resize_image( entry, queued_filename, medium_filepath, exif_tags, conversions_subdir, (mgg.global_config['media:medium']['max_width'], mgg.global_config['media:medium']['max_height'])) else: medium_filepath = None # we have to re-read because unlike PIL, not everything reads # things in string representation :) queued_file = file(queued_filename, 'rb') with queued_file: original_filepath = create_pub_filepath( entry, name_builder.fill('{basename}{ext}')) with mgg.public_store.get_file(original_filepath, 'wb') \ as original_file: original_file.write(queued_file.read()) # Remove queued media file from storage and database mgg.queue_store.delete_file(queued_filepath) entry.queued_media_file = [] # Insert media file information into database media_files_dict = entry.setdefault('media_files', {}) media_files_dict[u'thumb'] = thumb_filepath media_files_dict[u'original'] = original_filepath if medium_filepath: media_files_dict[u'medium'] = medium_filepath # Insert exif data into database exif_all = clean_exif(exif_tags) if len(exif_all): entry.media_data_init(exif_all=exif_all) if len(gps_data): for key in list(gps_data.keys()): gps_data['gps_' + key] = gps_data.pop(key) entry.media_data_init(**gps_data) # clean up workbench workbench.destroy_self()
# Copy our queued local workbench to its final destination proc_state.copy_original(name_builder.fill('{basename}{ext}')) # Remove queued media file from storage and database proc_state.delete_queue_file() # Insert exif data into database exif_all = clean_exif(exif_tags) if len(exif_all): entry.media_data_init(exif_all=exif_all) if len(gps_data): for key in list(gps_data.keys()): gps_data['gps_' + key] = gps_data.pop(key) entry.media_data_init(**gps_data) if __name__ == '__main__': import sys import pprint pp = pprint.PrettyPrinter() result = extract_exif(sys.argv[1]) gps = get_gps_data(result) clean = clean_exif(result) useful = get_useful(clean) print pp.pprint(clean)
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) == 55 # Do we have clean data? assert len(clean) == 53 # GPS data? assert gps == {} # Do we have the "useful" tags? expected = OrderedDict( { "EXIF CVAPattern": { "field_length": 8, "field_offset": 26224, "field_type": 7, "printable": "[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": "sRGB", "tag": 40961, "values": [1], }, "EXIF ComponentsConfiguration": { "field_length": 4, "field_offset": 308, "field_type": 7, "printable": "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]], }, } ) for k, v in useful.items(): assert v == expected[k]
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) >= 50 # Do we have clean data? assert len(clean) >= 50 # GPS data? assert gps == {} # Do we have the "useful" tags? expected = OrderedDict({'EXIF CVAPattern': {'field_length': 8, 'field_offset': 26224, 'field_type': 7, 'printable': '[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': 'sRGB', 'tag': 40961, 'values': [1]}, 'EXIF ComponentsConfiguration': {'field_length': 4, 'field_offset': 308, 'field_type': 7, 'printable': '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]]}}) for key in expected.keys(): assert useful[key] == expected[key]
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) == 108 # Do we have clean data? assert len(clean) == 105 # GPS data? assert gps == {} # Do we have the "useful" tags? assert useful == { 'EXIF Flash': { 'field_type': 3, 'printable': 'No', 'field_offset': 380, 'tag': 37385, 'values': [0], 'field_length': 2}, 'EXIF ExposureTime': { 'field_type': 5, 'printable': '1/125', 'field_offset': 700, 'tag': 33434, 'values': [[1, 125]], 'field_length': 8}, 'EXIF FocalLength': { 'field_type': 5, 'printable': '18', 'field_offset': 780, 'tag': 37386, 'values': [[18, 1]], 'field_length': 8}, 'Image Model': { 'field_type': 2, 'printable': 'NIKON D80', 'field_offset': 152, 'tag': 272, 'values': 'NIKON D80', 'field_length': 10}, 'Image Make': { 'field_type': 2, 'printable': 'NIKON CORPORATION', 'field_offset': 134, 'tag': 271, 'values': 'NIKON CORPORATION', 'field_length': 18}, 'EXIF ExposureMode': { 'field_type': 3, 'printable': 'Manual Exposure', 'field_offset': 584, 'tag': 41986, 'values': [1], 'field_length': 2}, 'EXIF ISOSpeedRatings': { 'field_type': 3, 'printable': '100', 'field_offset': 260, 'tag': 34855, 'values': [100], 'field_length': 2}, 'EXIF FNumber': { 'field_type': 5, 'printable': '10', 'field_offset': 708, 'tag': 33437, 'values': [[10, 1]], 'field_length': 8}, 'EXIF UserComment': { 'field_type': 7, 'printable': 'Joar Wandborg ', 'field_offset': 26180, 'tag': 37510, 'values': [ 65, 83, 67, 73, 73, 0, 0, 0, 74, 111, 97, 114, 32, 87, 97, 110, 100, 98, 111, 114, 103, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32], 'field_length': 44}}