From: Joar Wandborg Date: Tue, 10 Jan 2012 01:59:07 +0000 (+0100) Subject: EXIF extraction, geolocation map, image rotation X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=e8e444a85e2b16583587ff5a074f0ab1ffbaca85;p=mediagoblin.git EXIF extraction, geolocation map, image rotation - Images are now rotated based on EXIF image orientation (in case the image isn't flipped on X or Y axis or correctly oriented, then we do nothing) - *Always* create a medium.jpg in image.processing, for the sake of rotation of display image - Extract EXIF and GPS tags from images and insert them into media_data - Geolocation map display added to media.html - EXIF display added, then removed. It is not in this revision, although some of it is (the "EXIF" h4 header). Need to make it presentable, filtering out purely robotical tags, perhaps. --- diff --git a/mediagoblin/media_types/image/processing.py b/mediagoblin/media_types/image/processing.py index cf90388f..9eb8fa16 100644 --- a/mediagoblin/media_types/image/processing.py +++ b/mediagoblin/media_types/image/processing.py @@ -18,14 +18,10 @@ import Image import os from mediagoblin import mg_globals as mgg - from mediagoblin.processing import BadMediaFail, \ create_pub_filepath, THUMB_SIZE, MEDIUM_SIZE - -################################ -# Media processing initial steps -################################ - +from mediagoblin.media_types.image.EXIF import process_file +from mediagoblin.tools.translate import pass_to_ugettext as _ def process_image(entry): """ @@ -46,11 +42,17 @@ def process_image(entry): basename = os.path.split(filename_bits[0])[1] extension = filename_bits[1].lower() + # EXIF extraction + exif_tags = extract_exif(queued_filename) + gps_data = get_gps_data(exif_tags) + try: thumb = Image.open(queued_filename) except IOError: raise BadMediaFail() + thumb = exif_fix_image_orientation(thumb, exif_tags) + thumb.thumbnail(THUMB_SIZE, Image.ANTIALIAS) # Copy the thumb to the conversion subdir, then remotely. @@ -67,23 +69,22 @@ def process_image(entry): # file, a `medium.jpg` files is created and later associated with the media # entry. medium = Image.open(queued_filename) - medium_processed = False + # Fox orientation + medium = exif_fix_image_orientation(medium, exif_tags) if medium.size[0] > MEDIUM_SIZE[0] or medium.size[1] > MEDIUM_SIZE[1]: medium.thumbnail(MEDIUM_SIZE, Image.ANTIALIAS) - medium_filename = 'medium' + extension - medium_filepath = create_pub_filepath(entry, medium_filename) - tmp_medium_filename = os.path.join( - conversions_subdir, medium_filename) - - with file(tmp_medium_filename, 'w') as medium_file: - medium.save(medium_file) + medium_filename = 'medium' + extension + medium_filepath = create_pub_filepath(entry, medium_filename) + tmp_medium_filename = os.path.join( + conversions_subdir, medium_filename) - mgg.public_store.copy_local_to_storage( - tmp_medium_filename, medium_filepath) + with file(tmp_medium_filename, 'w') as medium_file: + medium.save(medium_file) - medium_processed = True + mgg.public_store.copy_local_to_storage( + tmp_medium_filename, medium_filepath) # we have to re-read because unlike PIL, not everything reads # things in string representation :) @@ -97,13 +98,135 @@ def process_image(entry): 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['thumb'] = thumb_filepath media_files_dict['original'] = original_filepath - if medium_processed: - media_files_dict['medium'] = medium_filepath + media_files_dict['medium'] = medium_filepath + + # Insert exif data into database + media_data = entry.setdefault('media_data', {}) + media_data['exif'] = clean_exif(exif_tags) + media_data['gps'] = gps_data # clean up workbench workbench.destroy_self() + +def exif_fix_image_orientation(im, exif_tags): + """ + Translate any EXIF orientation to raw orientation + + Cons: + - REDUCES IMAGE QUALITY by recompressig it + + Pros: + - Cures my neck pain + """ + # Rotate image + if 'Image Orientation' in exif_tags: + rotation_map = { + 3: 180, + 6: 270, + 8: 90} + orientation = exif_tags['Image Orientation'].values[0] + if orientation in rotation_map.keys(): + im = im.rotate( + rotation_map[orientation]) + + return im + +def extract_exif(filename): + """ + Returns EXIF tags found in file at ``filename`` + """ + exif_tags = {} + + try: + image = open(filename) + exif_tags = process_file(image) + except IOError: + BadMediaFail(_('Could not read the image file.')) + + return exif_tags + +def clean_exif(exif): + # Clean the result from anything the database cannot handle + + # Discard any JPEG thumbnail, for database compatibility + # and that I cannot see a case when we would use it. + # It takes up some space too. + disabled_tags = [ + 'Thumbnail JPEGInterchangeFormatLength', + 'JPEGThumbnail', + 'Thumbnail JPEGInterchangeFormat'] + + clean_exif = {} + + for key, value in exif.items(): + if not key in disabled_tags: + clean_exif[key] = str(value) + + return clean_exif + + +def get_gps_data(exif): + """ + Processes EXIF data returned by EXIF.py + """ + if not 'Image GPSInfo' in exif: + return False + + gps_data = {} + + try: + dms_data = { + 'latitude': exif['GPS GPSLatitude'], + 'longitude': exif['GPS GPSLongitude']} + + for key, dat in dms_data.items(): + gps_data[key] = ( + lambda v: + float(v[0].num) / float(v[0].den) \ + + (float(v[1].num) / float(v[1].den) / 60 )\ + + (float(v[2].num) / float(v[2].den) / (60 * 60)) + )(dat.values) + except KeyError: + pass + + try: + gps_data['direction'] = ( + lambda d: + float(d.num) / float(d.den) + )(exif['GPS GPSImgDirection'].values[0]) + except KeyError: + pass + + try: + gps_data['altitude'] = ( + lambda a: + float(a.num) / float(a.den) + )(exif['GPS GPSAltitude'].values[0]) + except KeyError: + pass + + return gps_data + + +if __name__ == '__main__': + import sys + import pprint + + pp = pprint.PrettyPrinter() + + result = extract_exif(sys.argv[1]) + gps = get_gps_data(result) + + import pdb + pdb.set_trace() + + print pp.pprint( + result) diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html index cbe26cbf..944d7f6e 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -23,8 +23,16 @@ {% block title %}{{ media.title }} — {{ super() }}{% endblock %} {% block mediagoblin_head %} + + + + {% endblock mediagoblin_head %} {% block mediagoblin_content %} @@ -72,6 +80,21 @@ media= media._id) %} {% trans %}Delete{% endtrans %} {% endif %} + {% if media.media_data.exif %} + {#- + TODO: + - Render GPS data in a human-readable format + +

EXIF

+ + {% for tag, value in media.media_data.exif.items() %} + + + + + {% endfor %} +
{{ tag }}{{ value }}
#} + {% endif %}

{% if comments %}

@@ -171,6 +194,26 @@ {% include "mediagoblin/utils/tags.html" %} {% endif %} + {% if media.media_data.gps %} +

Map

+
+ {% set gps = media.media_data.gps %} +
+ + +
+

+ + View on + + OpenStreetMap + + +

+
+ {% endif %} {% include "mediagoblin/utils/license.html" %} {% endblock %}