3c1aebe53fbfbcb0a7c3cc503fb54b6528cbcc95
[mediagoblin.git] / mediagoblin / tools / exif.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011 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 def exif_fix_image_orientation(im, exif_tags):
36 """
37 Translate any EXIF orientation to raw orientation
38
39 Cons:
40 - REDUCES IMAGE QUALITY by recompressig it
41
42 Pros:
43 - Cures my neck pain
44 """
45 # Rotate image
46 if 'Image Orientation' in exif_tags:
47 rotation_map = {
48 3: 180,
49 6: 270,
50 8: 90}
51 orientation = exif_tags['Image Orientation'].values[0]
52 if orientation in rotation_map.keys():
53 im = im.rotate(
54 rotation_map[orientation])
55
56 return im
57
58 def extract_exif(filename):
59 """
60 Returns EXIF tags found in file at ``filename``
61 """
62 exif_tags = {}
63
64 try:
65 image = open(filename)
66 exif_tags = process_file(image)
67 except IOError:
68 raise BadMediaFail(_('Could not read the image file.'))
69
70 return exif_tags
71
72 def clean_exif(exif):
73 '''
74 Clean the result from anything the database cannot handle
75 '''
76 # Discard any JPEG thumbnail, for database compatibility
77 # and that I cannot see a case when we would use it.
78 # It takes up some space too.
79 disabled_tags = [
80 'Thumbnail JPEGInterchangeFormatLength',
81 'JPEGThumbnail',
82 'Thumbnail JPEGInterchangeFormat']
83
84 clean_exif = {}
85
86 for key, value in exif.items():
87 if not key in disabled_tags:
88 clean_exif[key] = _ifd_tag_to_dict(value)
89
90 return clean_exif
91
92 def _ifd_tag_to_dict(tag):
93 data = {
94 'printable': tag.printable,
95 'tag': tag.tag,
96 'field_type': tag.field_type,
97 'field_offset': tag.field_offset,
98 'field_length': tag.field_length,
99 'values': None}
100 if type(tag.values) == list:
101 data['values'] = []
102 for val in tag.values:
103 if isinstance(val, Ratio):
104 data['values'].append(
105 _ratio_to_list(val))
106 else:
107 data['values'].append(val)
108 else:
109 data['values'] = tag.values
110
111 return data
112
113 def _ratio_to_list(ratio):
114 return [ratio.num, ratio.den]
115
116 def get_useful(tags):
117 useful = {}
118 for key, tag in tags.items():
119 if key in USEFUL_TAGS:
120 useful[key] = tag
121
122 return useful
123
124
125 def get_gps_data(tags):
126 """
127 Processes EXIF data returned by EXIF.py
128 """
129 gps_data = {}
130
131 if not 'Image GPSInfo' in tags:
132 return gps_data
133
134 try:
135 dms_data = {
136 'latitude': tags['GPS GPSLatitude'],
137 'longitude': tags['GPS GPSLongitude']}
138
139 for key, dat in dms_data.items():
140 gps_data[key] = (
141 lambda v:
142 float(v[0].num) / float(v[0].den) \
143 + (float(v[1].num) / float(v[1].den) / 60 )\
144 + (float(v[2].num) / float(v[2].den) / (60 * 60))
145 )(dat.values)
146 except KeyError:
147 pass
148
149 try:
150 gps_data['direction'] = (
151 lambda d:
152 float(d.num) / float(d.den)
153 )(tags['GPS GPSImgDirection'].values[0])
154 except KeyError:
155 pass
156
157 try:
158 gps_data['altitude'] = (
159 lambda a:
160 float(a.num) / float(a.den)
161 )(tags['GPS GPSAltitude'].values[0])
162 except KeyError:
163 pass
164
165 return gps_data