2013-02-22 22:24:29 +01:00

203 lines
5.4 KiB
Python

# 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 <http://www.gnu.org/licenses/>.
try:
from EXIF import process_file, Ratio
except ImportError:
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_image_needs_rotation(exif_tags):
"""
Returns True if EXIF orientation requires rotation
"""
return 'Image Orientation' in exif_tags \
and exif_tags['Image Orientation'].values[0] != 1
def exif_fix_image_orientation(im, exif_tags):
"""
Translate any EXIF orientation to raw orientation
Cons:
- REDUCES IMAGE QUALITY by recompressig it
Pros:
- Prevents 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:
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, details=False)
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):
'''
Takes an IFD tag object from the EXIF library and converts it to a dict
that can be stored as JSON in the database.
'''
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 isinstance(tag.printable, str):
# Force it to be decoded as UTF-8 so that it'll fit into the DB
data['printable'] = tag.printable.decode('utf8', 'replace')
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:
if isinstance(tag.values, str):
# Force UTF-8, so that it fits into the DB
data['values'] = tag.values.decode('utf8', 'replace')
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)
if tags['GPS GPSLatitudeRef'].values == 'S':
gps_data['latitude'] /= -1
if tags['GPS GPSLongitudeRef'].values == 'W':
gps_data['longitude'] /= -1
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