EXIF fixes
- Moved exif functions from mediagoblin.media_types.image.processing to mediagoblin.tools.exif - Moved EXIF.py link from mediagoblin.media_types to mediagoblin.tools.extlib - Refractored and updated EXIF exctraction and presentation
This commit is contained in:
parent
a020391d90
commit
a180ca264e
@ -20,8 +20,8 @@ import os
|
|||||||
from mediagoblin import mg_globals as mgg
|
from mediagoblin import mg_globals as mgg
|
||||||
from mediagoblin.processing import BadMediaFail, \
|
from mediagoblin.processing import BadMediaFail, \
|
||||||
create_pub_filepath, THUMB_SIZE, MEDIUM_SIZE
|
create_pub_filepath, THUMB_SIZE, MEDIUM_SIZE
|
||||||
from mediagoblin.media_types.image.EXIF import process_file
|
from mediagoblin.tools.exif import exif_fix_image_orientation, \
|
||||||
from mediagoblin.tools.translate import pass_to_ugettext as _
|
extract_exif, clean_exif, get_gps_data, get_useful
|
||||||
|
|
||||||
def process_image(entry):
|
def process_image(entry):
|
||||||
"""
|
"""
|
||||||
@ -110,112 +110,15 @@ def process_image(entry):
|
|||||||
|
|
||||||
# Insert exif data into database
|
# Insert exif data into database
|
||||||
media_data = entry.setdefault('media_data', {})
|
media_data = entry.setdefault('media_data', {})
|
||||||
media_data['exif'] = clean_exif(exif_tags)
|
media_data['exif'] = {
|
||||||
|
'clean': clean_exif(exif_tags)}
|
||||||
|
media_data['exif']['useful'] = get_useful(
|
||||||
|
media_data['exif']['clean'])
|
||||||
media_data['gps'] = gps_data
|
media_data['gps'] = gps_data
|
||||||
|
|
||||||
# clean up workbench
|
# clean up workbench
|
||||||
workbench.destroy_self()
|
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__':
|
if __name__ == '__main__':
|
||||||
import sys
|
import sys
|
||||||
import pprint
|
import pprint
|
||||||
@ -224,9 +127,11 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
result = extract_exif(sys.argv[1])
|
result = extract_exif(sys.argv[1])
|
||||||
gps = get_gps_data(result)
|
gps = get_gps_data(result)
|
||||||
|
clean = clean_exif(result)
|
||||||
|
useful = get_useful(clean)
|
||||||
|
|
||||||
import pdb
|
import pdb
|
||||||
pdb.set_trace()
|
pdb.set_trace()
|
||||||
|
|
||||||
print pp.pprint(
|
print pp.pprint(
|
||||||
result)
|
clean)
|
||||||
|
@ -80,20 +80,21 @@
|
|||||||
media= media._id) %}
|
media= media._id) %}
|
||||||
<a class="button_action" href="{{ delete_url }}">{% trans %}Delete{% endtrans %}</a>
|
<a class="button_action" href="{{ delete_url }}">{% trans %}Delete{% endtrans %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if media.media_data.exif %}
|
{% if media.media_data.has_key('exif')
|
||||||
|
and media.media_data.exif.has_key('useful') %}
|
||||||
{#-
|
{#-
|
||||||
TODO:
|
TODO:
|
||||||
- Render GPS data in a human-readable format
|
- Render GPS data in a human-readable format
|
||||||
|
#}
|
||||||
<h4>EXIF</h4>
|
<h4>EXIF</h4>
|
||||||
<table>
|
<table>
|
||||||
{% for tag, value in media.media_data.exif.items() %}
|
{% for key, tag in media.media_data.exif.useful.items() %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ tag }}</td>
|
<td>{{ key }}</td>
|
||||||
<td>{{ value }}</td>
|
<td>{{ tag.printable }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>#}
|
</table>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
{% if comments %}
|
{% if comments %}
|
||||||
@ -194,7 +195,8 @@
|
|||||||
{% include "mediagoblin/utils/tags.html" %}
|
{% include "mediagoblin/utils/tags.html" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if media.media_data.gps %}
|
{% if media.media_data.has_key('gps')
|
||||||
|
and media.media_data.gps %}
|
||||||
<h4>Map</h4>
|
<h4>Map</h4>
|
||||||
<div>
|
<div>
|
||||||
{% set gps = media.media_data.gps %}
|
{% set gps = media.media_data.gps %}
|
||||||
|
168
mediagoblin/tools/exif.py
Normal file
168
mediagoblin/tools/exif.py
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
# 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 _
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
# 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 anyt
|
||||||
|
hing 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
|
||||||
|
"""
|
||||||
|
if not 'Image GPSInfo' in tags:
|
||||||
|
return False
|
||||||
|
|
||||||
|
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
|
1
mediagoblin/tools/extlib/EXIF.py
Symbolic link
1
mediagoblin/tools/extlib/EXIF.py
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../../../extlib/exif/EXIF.py
|
0
mediagoblin/tools/extlib/__init__.py
Normal file
0
mediagoblin/tools/extlib/__init__.py
Normal file
Loading…
x
Reference in New Issue
Block a user