1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 from exifread
import process_file
20 from exifread
.utils
import Ratio
22 from mediagoblin
.processing
import BadMediaFail
23 from mediagoblin
.tools
.translate
import pass_to_ugettext
as _
25 # A list of tags that should be stored for faster access
35 'EXIF ISOSpeedRatings',
40 def exif_image_needs_rotation(exif_tags
):
42 Returns True if EXIF orientation requires rotation
44 return 'Image Orientation' in exif_tags \
45 and exif_tags
['Image Orientation'].values
[0] != 1
48 def exif_fix_image_orientation(im
, exif_tags
):
50 Translate any EXIF orientation to raw orientation
53 - Well, it changes the image, which means we'll recompress
54 it... not a problem if scaling it down already anyway. We might
55 lose some quality in recompressing if it's at the same-size
62 if 'Image Orientation' in exif_tags
:
67 orientation
= exif_tags
['Image Orientation'].values
[0]
68 if orientation
in rotation_map
:
70 rotation_map
[orientation
])
75 def extract_exif(filename
):
77 Returns EXIF tags found in file at ``filename``
80 with
open(filename
, 'rb') as image
:
81 return process_file(image
, details
=False)
83 raise BadMediaFail(_('Could not read the image file.'))
88 Clean the result from anything the database cannot handle
90 # Discard any JPEG thumbnail, for database compatibility
91 # and that I cannot see a case when we would use it.
92 # It takes up some space too.
94 'Thumbnail JPEGInterchangeFormatLength',
96 'Thumbnail JPEGInterchangeFormat']
98 return dict((key
, _ifd_tag_to_dict(value
)) for (key
, value
)
99 in six
.iteritems(exif
) if key
not in disabled_tags
)
102 def _ifd_tag_to_dict(tag
):
104 Takes an IFD tag object from the EXIF library and converts it to a dict
105 that can be stored as JSON in the database.
108 'printable': tag
.printable
,
110 'field_type': tag
.field_type
,
111 'field_offset': tag
.field_offset
,
112 'field_length': tag
.field_length
,
115 if isinstance(tag
.printable
, six
.binary_type
):
116 # Force it to be decoded as UTF-8 so that it'll fit into the DB
117 data
['printable'] = tag
.printable
.decode('utf8', 'replace')
119 if type(tag
.values
) == list:
120 data
['values'] = [_ratio_to_list(val
) if isinstance(val
, Ratio
) else val
121 for val
in tag
.values
]
123 if isinstance(tag
.values
, six
.binary_type
):
124 # Force UTF-8, so that it fits into the DB
125 data
['values'] = tag
.values
.decode('utf8', 'replace')
127 data
['values'] = tag
.values
132 def _ratio_to_list(ratio
):
133 return [ratio
.num
, ratio
.den
]
136 def get_useful(tags
):
137 from collections
import OrderedDict
138 return OrderedDict((key
, tag
) for (key
, tag
) in six
.iteritems(tags
))
141 def get_gps_data(tags
):
143 Processes EXIF data returned by EXIF.py
145 def safe_gps_ratio_divide(ratio
):
148 return float(ratio
.num
) / float(ratio
.den
)
152 if not 'Image GPSInfo' in tags
:
157 'latitude': tags
['GPS GPSLatitude'],
158 'longitude': tags
['GPS GPSLongitude']}
160 for key
, dat
in six
.iteritems(dms_data
):
163 safe_gps_ratio_divide(v
[0]) \
164 + (safe_gps_ratio_divide(v
[1]) / 60) \
165 + (safe_gps_ratio_divide(v
[2]) / (60 * 60))
168 if tags
['GPS GPSLatitudeRef'].values
== 'S':
169 gps_data
['latitude'] /= -1
171 if tags
['GPS GPSLongitudeRef'].values
== 'W':
172 gps_data
['longitude'] /= -1
178 gps_data
['direction'] = (
180 float(d
.num
) / float(d
.den
)
181 )(tags
['GPS GPSImgDirection'].values
[0])
186 gps_data
['altitude'] = (
188 float(a
.num
) / float(a
.den
)
189 )(tags
['GPS GPSAltitude'].values
[0])