Commit | Line | Data |
---|---|---|
a180ca26 JW |
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 | ||
a180ca26 JW |
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 | ''' | |
63bd7c04 | 74 | Clean the result from anything the database cannot handle |
a180ca26 JW |
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 | """ | |
a180ca26 JW |
129 | gps_data = {} |
130 | ||
63bd7c04 JW |
131 | if not 'Image GPSInfo' in tags: |
132 | return gps_data | |
133 | ||
a180ca26 JW |
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 |