typofix in comment
[mediagoblin.git] / mediagoblin / tools / exif.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
3 #
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.
8 #
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.
13 #
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/>.
16
17 try:
18 from EXIF import process_file, Ratio
19 except ImportError:
20 from mediagoblin.tools.extlib.EXIF import process_file, Ratio
21
22 from mediagoblin.processing import BadMediaFail
23 from mediagoblin.tools.translate import pass_to_ugettext as _
24
25 # A list of tags that should be stored for faster access
26 USEFUL_TAGS = [
27 'Image Make',
28 'Image Model',
29 'EXIF FNumber',
30 'EXIF Flash',
31 'EXIF FocalLength',
32 'EXIF ExposureTime',
33 'EXIF ApertureValue',
34 'EXIF ExposureMode',
35 'EXIF ISOSpeedRatings',
36 'EXIF UserComment',
37 ]
38
39
40 def exif_image_needs_rotation(exif_tags):
41 """
42 Returns True if EXIF orientation requires rotation
43 """
44 return 'Image Orientation' in exif_tags \
45 and exif_tags['Image Orientation'].values[0] != 1
46
47
48 def exif_fix_image_orientation(im, exif_tags):
49 """
50 Translate any EXIF orientation to raw orientation
51
52 Cons:
53 - REDUCES IMAGE QUALITY by recompressing it
54
55 Pros:
56 - Prevents neck pain
57 """
58 # Rotate image
59 if 'Image Orientation' in exif_tags:
60 rotation_map = {
61 3: 180,
62 6: 270,
63 8: 90}
64 orientation = exif_tags['Image Orientation'].values[0]
65 if orientation in rotation_map:
66 im = im.rotate(
67 rotation_map[orientation])
68
69 return im
70
71
72 def extract_exif(filename):
73 """
74 Returns EXIF tags found in file at ``filename``
75 """
76 exif_tags = {}
77
78 try:
79 image = open(filename)
80 exif_tags = process_file(image, details=False)
81 except IOError:
82 raise BadMediaFail(_('Could not read the image file.'))
83
84 return exif_tags
85
86
87 def clean_exif(exif):
88 '''
89 Clean the result from anything the database cannot handle
90 '''
91 # Discard any JPEG thumbnail, for database compatibility
92 # and that I cannot see a case when we would use it.
93 # It takes up some space too.
94 disabled_tags = [
95 'Thumbnail JPEGInterchangeFormatLength',
96 'JPEGThumbnail',
97 'Thumbnail JPEGInterchangeFormat']
98
99 clean_exif = {}
100
101 for key, value in exif.items():
102 if not key in disabled_tags:
103 clean_exif[key] = _ifd_tag_to_dict(value)
104 return clean_exif
105
106
107 def _ifd_tag_to_dict(tag):
108 '''
109 Takes an IFD tag object from the EXIF library and converts it to a dict
110 that can be stored as JSON in the database.
111 '''
112 data = {
113 'printable': tag.printable,
114 'tag': tag.tag,
115 'field_type': tag.field_type,
116 'field_offset': tag.field_offset,
117 'field_length': tag.field_length,
118 'values': None}
119
120 if isinstance(tag.printable, str):
121 # Force it to be decoded as UTF-8 so that it'll fit into the DB
122 data['printable'] = tag.printable.decode('utf8', 'replace')
123
124 if type(tag.values) == list:
125 data['values'] = []
126 for val in tag.values:
127 if isinstance(val, Ratio):
128 data['values'].append(
129 _ratio_to_list(val))
130 else:
131 data['values'].append(val)
132 else:
133 if isinstance(tag.values, str):
134 # Force UTF-8, so that it fits into the DB
135 data['values'] = tag.values.decode('utf8', 'replace')
136 else:
137 data['values'] = tag.values
138
139 return data
140
141
142 def _ratio_to_list(ratio):
143 return [ratio.num, ratio.den]
144
145
146 def get_useful(tags):
147 useful = {}
148 for key, tag in tags.items():
149 if key in USEFUL_TAGS:
150 useful[key] = tag
151
152 return useful
153
154
155 def get_gps_data(tags):
156 """
157 Processes EXIF data returned by EXIF.py
158 """
159 gps_data = {}
160
161 if not 'Image GPSInfo' in tags:
162 return gps_data
163
164 try:
165 dms_data = {
166 'latitude': tags['GPS GPSLatitude'],
167 'longitude': tags['GPS GPSLongitude']}
168
169 for key, dat in dms_data.items():
170 gps_data[key] = (
171 lambda v:
172 float(v[0].num) / float(v[0].den) \
173 + (float(v[1].num) / float(v[1].den) / 60) \
174 + (float(v[2].num) / float(v[2].den) / (60 * 60))
175 )(dat.values)
176
177 if tags['GPS GPSLatitudeRef'].values == 'S':
178 gps_data['latitude'] /= -1
179
180 if tags['GPS GPSLongitudeRef'].values == 'W':
181 gps_data['longitude'] /= -1
182
183 except KeyError:
184 pass
185
186 try:
187 gps_data['direction'] = (
188 lambda d:
189 float(d.num) / float(d.den)
190 )(tags['GPS GPSImgDirection'].values[0])
191 except KeyError:
192 pass
193
194 try:
195 gps_data['altitude'] = (
196 lambda a:
197 float(a.num) / float(a.den)
198 )(tags['GPS GPSAltitude'].values[0])
199 except KeyError:
200 pass
201
202 return gps_data