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