diff options
Diffstat (limited to 'mediagoblin')
19 files changed, 607 insertions, 24 deletions
diff --git a/mediagoblin/config_spec.ini b/mediagoblin/config_spec.ini index dc286a27..2d410899 100644 --- a/mediagoblin/config_spec.ini +++ b/mediagoblin/config_spec.ini @@ -53,6 +53,9 @@ csrf_cookie_name = string(default='mediagoblin_csrftoken') # Push stuff push_urls = string_list(default=list()) +exif_visible = boolean(default=False) +geolocation_map_visible = boolean(default=False) + [storage:publicstore] storage_class = string(default="mediagoblin.storage.filestorage:BasicFileStorage") base_dir = string(default="%(here)s/user_dev/media/public") diff --git a/mediagoblin/media_types/image/processing.py b/mediagoblin/media_types/image/processing.py index cf90388f..78f64be0 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.tools.exif import exif_fix_image_orientation, \ + extract_exif, clean_exif, get_gps_data, get_useful def process_image(entry): """ @@ -46,20 +42,29 @@ 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. thumb_filename = 'thumbnail' + extension thumb_filepath = create_pub_filepath(entry, thumb_filename) + tmp_thumb_filename = os.path.join( conversions_subdir, thumb_filename) + with file(tmp_thumb_filename, 'w') as thumb_file: thumb.save(thumb_file) + mgg.public_store.copy_local_to_storage( tmp_thumb_filename, thumb_filepath) @@ -67,23 +72,24 @@ 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 + + # Fix 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) + medium_filename = 'medium' + extension + medium_filepath = create_pub_filepath(entry, medium_filename) - with file(tmp_medium_filename, 'w') as medium_file: - medium.save(medium_file) + 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 +103,37 @@ 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': clean_exif(exif_tags)} + media_data['exif']['useful'] = get_useful( + media_data['exif']['clean']) + media_data['gps'] = gps_data # clean up workbench workbench.destroy_self() + +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) diff --git a/mediagoblin/static/extlib/leaflet b/mediagoblin/static/extlib/leaflet new file mode 120000 index 00000000..b47e2b1b --- /dev/null +++ b/mediagoblin/static/extlib/leaflet @@ -0,0 +1 @@ +../../../extlib/leaflet/dist/
\ No newline at end of file diff --git a/mediagoblin/static/js/comment_show.js b/mediagoblin/static/js/comment_show.js index 2212b9ad..71466a8d 100644 --- a/mediagoblin/static/js/comment_show.js +++ b/mediagoblin/static/js/comment_show.js @@ -1,3 +1,21 @@ +/** + * GNU MediaGoblin -- federated, autonomous media hosting + * Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + $(document).ready(function(){ $('#form_comment').hide(); $('#button_addcomment').click(function(){ diff --git a/mediagoblin/static/js/extlib/leaflet b/mediagoblin/static/js/extlib/leaflet new file mode 120000 index 00000000..2fc302d7 --- /dev/null +++ b/mediagoblin/static/js/extlib/leaflet @@ -0,0 +1 @@ +../../../../extlib/leaflet/dist/
\ No newline at end of file diff --git a/mediagoblin/static/js/geolocation-map.js b/mediagoblin/static/js/geolocation-map.js new file mode 100644 index 00000000..35083d4f --- /dev/null +++ b/mediagoblin/static/js/geolocation-map.js @@ -0,0 +1,46 @@ +/** + * GNU MediaGoblin -- federated, autonomous media hosting + * Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + +$(document).ready(function () { + var longitude = Number( + $('#tile-map #gps-longitude').val()); + var latitude = Number( + $('#tile-map #gps-latitude').val()); + + // Get a new map instance attached and element with id="tile-map" + var map = new L.Map('tile-map'); + + var mqtileUrl = 'http://otile{s}.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg'; + var mqtileAttrib = 'Map data © ' + + String(new Date().getFullYear()) + + ' OpenStreetMap contributors, CC-BY-SA.' + + ' Imaging © ' + + String(new Date().getFullYear()) + + ' <a target="_blank" href="http://mapquest.com">MapQuest</a>.'; + var mqtile = new L.TileLayer( + mqtileUrl, + {maxZoom: 18, + attribution: mqtileAttrib, + subdomains: '1234'}); + + var location = new L.LatLng(latitude, longitude); + map.setView(location, 13).addLayer(mqtile); + + var marker = new L.Marker(location); + map.addLayer(marker); +}); diff --git a/mediagoblin/static/js/show_password.js b/mediagoblin/static/js/show_password.js index 519b29c1..513fe327 100644 --- a/mediagoblin/static/js/show_password.js +++ b/mediagoblin/static/js/show_password.js @@ -1,3 +1,21 @@ +/** + * GNU MediaGoblin -- federated, autonomous media hosting + * Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + */ + $(document).ready(function(){ $("#password").after('<input type="text" value="" name="password_clear" id="password_clear" /><label><input type="checkbox" id="password_boolean" />Show password</label>'); $('#password_clear').hide(); diff --git a/mediagoblin/templates/mediagoblin/user_pages/media.html b/mediagoblin/templates/mediagoblin/user_pages/media.html index 9df3dfbc..dec443cd 100644 --- a/mediagoblin/templates/mediagoblin/user_pages/media.html +++ b/mediagoblin/templates/mediagoblin/user_pages/media.html @@ -23,8 +23,20 @@ {% block title %}{{ media.title }} — {{ super() }}{% endblock %} {% block mediagoblin_head %} +<!--[if lte IE 8]><link rel="stylesheet" + href="{{ request.staticdirect('/extlib/leaflet/leaflet.ie.css') }}" /><![endif]--> <script type="text/javascript" src="{{ request.staticdirect('/js/comment_show.js') }}"></script> + + {% if app_config['geolocation_map_visible'] %} + <link rel="stylesheet" + href="{{ request.staticdirect('/extlib/leaflet/leaflet.css') }}" /> + + <script type="text/javascript" + src="{{ request.staticdirect('/extlib/leaflet/leaflet.js') }}"></script> + <script type="text/javascript" + src="{{ request.staticdirect('/js/geolocation-map.js') }}"></script> + {% endif %} {% endblock mediagoblin_head %} {% block mediagoblin_content %} @@ -172,5 +184,9 @@ {% endif %} {% include "mediagoblin/utils/license.html" %} + + {% include "mediagoblin/utils/geolocation_map.html" %} + + {% include "mediagoblin/utils/exif.html" %} </div> {% endblock %} diff --git a/mediagoblin/templates/mediagoblin/utils/exif.html b/mediagoblin/templates/mediagoblin/utils/exif.html new file mode 100644 index 00000000..9962dd65 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/utils/exif.html @@ -0,0 +1,33 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. +#} + +{% block exif_content %} + {% if media.media_data.has_key('exif') + and app_config['exif_visible'] + and media.media_data.exif.has_key('useful') %} + <h4>EXIF</h4> + <table> + {% for key, tag in media.media_data.exif.useful.items() %} + <tr> + <td>{{ key }}</td> + <td>{{ tag.printable }}</td> + </tr> + {% endfor %} + </table> + {% endif %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/utils/geolocation_map.html b/mediagoblin/templates/mediagoblin/utils/geolocation_map.html new file mode 100644 index 00000000..ce1edc39 --- /dev/null +++ b/mediagoblin/templates/mediagoblin/utils/geolocation_map.html @@ -0,0 +1,42 @@ +{# +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. +#} + +{% block geolocation_map %} + {% if media.media_data.has_key('gps') + and app_config['geolocation_map_visible'] + and media.media_data.gps %} + <h4>Map</h4> + <div> + {% set gps = media.media_data.gps %} + <div id="tile-map" style="width: 100%; height: 196px;"> + <input type="hidden" id="gps-longitude" + value="{{ gps.longitude }}" /> + <input type="hidden" id="gps-latitude" + value="{{ gps.latitude }}" /> + </div> + <p> + <small> + View on + <a href="http://openstreetmap.org/?mlat={{ gps.latitude }}&mlon={{ gps.longitude }}"> + OpenStreetMap + </a> + </small> + </p> + </div> + {% endif %} +{% endblock %} diff --git a/mediagoblin/templates/mediagoblin/utils/license.html b/mediagoblin/templates/mediagoblin/utils/license.html index 056c356e..5a268e39 100644 --- a/mediagoblin/templates/mediagoblin/utils/license.html +++ b/mediagoblin/templates/mediagoblin/utils/license.html @@ -17,10 +17,12 @@ #} {% block license_content -%} - {% trans %}License:{% endtrans %} - {% if media.license %} - <a href="{{ media.license }}">{{ media.get_license_data().abbreviation }}</a> - {% else %} - {% trans %}All rights reserved{% endtrans %} - {% endif %} + <p> + {% trans %}License:{% endtrans %} + {% if media.license %} + <a href="{{ media.license }}">{{ media.get_license_data().abbreviation }}</a> + {% else %} + {% trans %}All rights reserved{% endtrans %} + {% endif %} + </p> {% endblock %} diff --git a/mediagoblin/tests/test_exif.py b/mediagoblin/tests/test_exif.py new file mode 100644 index 00000000..9f2219c0 --- /dev/null +++ b/mediagoblin/tests/test_exif.py @@ -0,0 +1,189 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + +import os +import pkg_resources +import Image + +from mediagoblin.tools.exif import exif_fix_image_orientation, \ + extract_exif, clean_exif, get_gps_data, get_useful + +GOOD_JPG = pkg_resources.resource_filename( + 'mediagoblin.tests', + os.path.join( + 'test_exif', + 'good.jpg')) +EMPTY_JPG = pkg_resources.resource_filename( + 'mediagoblin.tests', + os.path.join( + 'test_exif', + 'empty.jpg')) +BAD_JPG = pkg_resources.resource_filename( + 'mediagoblin.tests', + os.path.join( + 'test_exif', + 'bad.jpg')) +GPS_JPG = pkg_resources.resource_filename( + 'mediagoblin.tests', + os.path.join( + 'test_exif', + 'has-gps.jpg')) + +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}} + +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 image.getdata()[10000] == (41, 28, 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} + diff --git a/mediagoblin/tests/test_exif/bad.jpg b/mediagoblin/tests/test_exif/bad.jpg new file mode 100644 index 00000000..4cde23cd --- /dev/null +++ b/mediagoblin/tests/test_exif/bad.jpg @@ -0,0 +1,18 @@ +V2UncmUgbm8gc3RyYW5nZXJzIHRvIGxvdmUKWW91IGtub3cgdGhlIHJ1bGVzIGFuZCBzbyBkbyBJ +CkEgZnVsbCBjb21taXRtZW50J3Mgd2hhdCBJJ20gdGhpbmtpbicgb2YKWW91IHdvdWxkbid0IGdl +dCB0aGlzIGZyb20gYW55IG90aGVyIGd1eQpJIGp1c3Qgd2FubmEgdGVsbCB5b3UgaG93IEknbSBm +ZWVsaW4nCkdvdHRhIG1ha2UgeW91IHVuZGVyc3RhbmQKCihDaG9ydXMpCk5ldmVyIGdvbm5hIGdp +dmUgeW91IHVwCk5ldmVyIGdvbm5hIGxldCB5b3UgZG93bgpOZXZlciBnb25uYSBydW4gYXJvdW5k +IGFuZCBkZXNlcnQgeW91Ck5ldmVyIGdvbm5hIG1ha2UgeW91IGNyeQpOZXZlciBnb25uYSBzYXkg +Z29vZGJ5ZQpOZXZlciBnb25uYSB0ZWxsIGEgbGllIGFuZCBodXJ0IHlvdQoKV2UndmUga25vdyBl +YWNoIG90aGVyIGZvciBzbyBsb25nCllvdXIgaGVhcnQncyBiZWVuIGFjaGluJyBidXQgeW91J3Jl +IHRvbyBzaHkgdG8gc2F5IGl0Ckluc2lkZSB3ZSBib3RoIGtub3cgd2hhdCdzIGJlZW4gZ29pbmcg +b24KV2Uga25vdyB0aGUgZ2FtZSBhbmQgd2UncmUgZ29ubmEgcGxheSBpdApBbmQgaWYgeW91IGFz +ayBtZSBob3cgSSdtIGZlZWxpbicKRG9uJ3QgdGVsbCBtZSB5b3UncmUgdG9vIGJsaW5kIHRvIHNl +ZQoKKENob3J1cyB4MikKCihHaXZlIHlvdSB1cCwgZ2l2ZSB5b3UgdXApCk5ldmVyIGdvbm5hIGdp +dmUsIG5ldmVyIGdvbm5hIGdpdmUKKEdpdmUgeW91IHVwKQpOZXZlciBnb25uYSBnaXZlLCBuZXZl +ciBnb25uYSBnaXZlCihHaXZlIHlvdSB1cCkKCldlJ3ZlIGtub3cgZWFjaCBvdGhlciBmb3Igc28g +bG9uZwpZb3VyIGhlYXJ0J3MgYmVlbiBhY2hpbicgYnV0IHlvdSdyZSB0b28gc2h5IHRvIHNheSBp +dApJbnNpZGUgd2UgYm90aCBrbm93IHdoYXQncyBiZWVuIGdvaW5nIG9uCldlIGtub3cgdGhlIGdh +bWUgYW5kIHdlJ3JlIGdvbm5hIHBsYXkgaXQKSSBqdXN0IHdhbm5hIHRlbGwgeW91IGhvdyBJJ20g +ZmVlbGluJwpHb3R0YSBtYWtlIHlvdSB1bmRlcnN0YW5kCgooQ2hvcnVzIHgzKQo= diff --git a/mediagoblin/tests/test_exif/empty.jpg b/mediagoblin/tests/test_exif/empty.jpg Binary files differnew file mode 100644 index 00000000..37533af5 --- /dev/null +++ b/mediagoblin/tests/test_exif/empty.jpg diff --git a/mediagoblin/tests/test_exif/good.jpg b/mediagoblin/tests/test_exif/good.jpg Binary files differnew file mode 100644 index 00000000..0ee956fe --- /dev/null +++ b/mediagoblin/tests/test_exif/good.jpg diff --git a/mediagoblin/tests/test_exif/has-gps.jpg b/mediagoblin/tests/test_exif/has-gps.jpg Binary files differnew file mode 100644 index 00000000..c7d2cc93 --- /dev/null +++ b/mediagoblin/tests/test_exif/has-gps.jpg diff --git a/mediagoblin/tools/exif.py b/mediagoblin/tools/exif.py new file mode 100644 index 00000000..3c1aebe5 --- /dev/null +++ b/mediagoblin/tools/exif.py @@ -0,0 +1,165 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 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 <http://www.gnu.org/licenses/>. + +from mediagoblin.tools.extlib.EXIF import process_file, Ratio +from mediagoblin.processing import BadMediaFail +from mediagoblin.tools.translate import pass_to_ugettext as _ + +# A list of tags that should be stored for faster access +USEFUL_TAGS = [ + 'Image Make', + 'Image Model', + 'EXIF FNumber', + 'EXIF Flash', + 'EXIF FocalLength', + 'EXIF ExposureTime', + 'EXIF ApertureValue', + 'EXIF ExposureMode', + 'EXIF ISOSpeedRatings', + 'EXIF UserComment', + ] + +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: + raise 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] = _ifd_tag_to_dict(value) + + return clean_exif + +def _ifd_tag_to_dict(tag): + data = { + 'printable': tag.printable, + 'tag': tag.tag, + 'field_type': tag.field_type, + 'field_offset': tag.field_offset, + 'field_length': tag.field_length, + 'values': None} + if type(tag.values) == list: + data['values'] = [] + for val in tag.values: + if isinstance(val, Ratio): + data['values'].append( + _ratio_to_list(val)) + else: + data['values'].append(val) + else: + data['values'] = tag.values + + return data + +def _ratio_to_list(ratio): + return [ratio.num, ratio.den] + +def get_useful(tags): + useful = {} + for key, tag in tags.items(): + if key in USEFUL_TAGS: + useful[key] = tag + + return useful + + +def get_gps_data(tags): + """ + Processes EXIF data returned by EXIF.py + """ + gps_data = {} + + if not 'Image GPSInfo' in tags: + return gps_data + + try: + dms_data = { + 'latitude': tags['GPS GPSLatitude'], + 'longitude': tags['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) + )(tags['GPS GPSImgDirection'].values[0]) + except KeyError: + pass + + try: + gps_data['altitude'] = ( + lambda a: + float(a.num) / float(a.den) + )(tags['GPS GPSAltitude'].values[0]) + except KeyError: + pass + + return gps_data diff --git a/mediagoblin/tools/extlib/EXIF.py b/mediagoblin/tools/extlib/EXIF.py new file mode 120000 index 00000000..82a2fb30 --- /dev/null +++ b/mediagoblin/tools/extlib/EXIF.py @@ -0,0 +1 @@ +../../../extlib/exif/EXIF.py
\ No newline at end of file diff --git a/mediagoblin/tools/extlib/__init__.py b/mediagoblin/tools/extlib/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/mediagoblin/tools/extlib/__init__.py |