change get_queued_filename to get_orig_filename and modified function
[mediagoblin.git] / mediagoblin / media_types / image / processing.py
CommitLineData
93bdab9d 1# GNU MediaGoblin -- federated, autonomous media hosting
cf29e8a8 2# Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
93bdab9d
JW
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
d0e9f843
AL
17try:
18 from PIL import Image
19except ImportError:
20 import Image
8e5f9746 21import os
92f129b5 22import logging
93bdab9d 23
93bdab9d 24from mediagoblin import mg_globals as mgg
8f88b1f6 25from mediagoblin.processing import BadMediaFail, FilenameBuilder
a180ca26 26from mediagoblin.tools.exif import exif_fix_image_orientation, \
0f8221dc
B
27 extract_exif, clean_exif, get_gps_data, get_useful, \
28 exif_image_needs_rotation
93bdab9d 29
92f129b5
JW
30_log = logging.getLogger(__name__)
31
d63cc34e
JW
32PIL_FILTERS = {
33 'NEAREST': Image.NEAREST,
34 'BILINEAR': Image.BILINEAR,
35 'BICUBIC': Image.BICUBIC,
36 'ANTIALIAS': Image.ANTIALIAS}
37
58a94757
RE
38MEDIA_TYPE = 'mediagoblin.media_types.image'
39
deea3f66 40
8f88b1f6 41def resize_image(proc_state, resized, keyname, target_name, new_size,
3b359ddd 42 exif_tags, workdir):
c72d661b
JW
43 """
44 Store a resized version of an image and return its pathname.
063670e9
BS
45
46 Arguments:
c82a8ba5 47 proc_state -- the processing state for the image to resize
3b359ddd 48 resized -- an image from Image.open() of the original image being resized
8f88b1f6
E
49 keyname -- Under what key to save in the db.
50 target_name -- public file path for the new resized image
063670e9
BS
51 exif_tags -- EXIF data for the original image
52 workdir -- directory path for storing converted image files
53 new_size -- 2-tuple size for the resized image
063670e9 54 """
dc1ec36e
E
55 config = mgg.global_config['media_type:mediagoblin.media_types.image']
56
063670e9 57 resized = exif_fix_image_orientation(resized, exif_tags) # Fix orientation
7cd7db5a 58
dc1ec36e 59 filter_config = config['resize_filter']
7cd7db5a 60 try:
d63cc34e 61 resize_filter = PIL_FILTERS[filter_config.upper()]
7cd7db5a
JW
62 except KeyError:
63 raise Exception('Filter "{0}" not found, choose one of {1}'.format(
64 unicode(filter_config),
d63cc34e 65 u', '.join(PIL_FILTERS.keys())))
7cd7db5a
JW
66
67 resized.thumbnail(new_size, resize_filter)
063670e9 68
063670e9 69 # Copy the new file to the conversion subdir, then remotely.
8f88b1f6 70 tmp_resized_filename = os.path.join(workdir, target_name)
063670e9 71 with file(tmp_resized_filename, 'w') as resized_file:
dc1ec36e 72 resized.save(resized_file, quality=config['quality'])
8f88b1f6 73 proc_state.store_public(keyname, tmp_resized_filename, target_name)
063670e9 74
deea3f66 75
3e9faf85 76def resize_tool(proc_state, force, keyname, target_name,
9a2c66ca 77 conversions_subdir, exif_tags, new_size=None):
45b20dce 78 # filename -- the filename of the original image being resized
3e9faf85
RE
79 filename = proc_state.get_orig_filename()
80
81 # Use the default size if new_size was not given
9a2c66ca
RE
82 if not new_size:
83 max_width = mgg.global_config['media:' + keyname]['max_width']
84 max_height = mgg.global_config['media:' + keyname]['max_height']
85 new_size = (max_width, max_height)
3e9faf85 86
3b359ddd
E
87 # If the size of the original file exceeds the specified size for the desized
88 # file, a target_name file is created and later associated with the media
89 # entry.
90 # Also created if the file needs rotation, or if forced.
91 try:
92 im = Image.open(filename)
93 except IOError:
94 raise BadMediaFail()
95 if force \
49db7785
RE
96 or im.size[0] > new_size[0]\
97 or im.size[1] > new_size[1]\
3b359ddd 98 or exif_image_needs_rotation(exif_tags):
3b359ddd 99 resize_image(
8f88b1f6 100 proc_state, im, unicode(keyname), target_name,
9a2c66ca 101 new_size,
3b359ddd 102 exif_tags, conversions_subdir)
3b359ddd
E
103
104
b1a763f6 105SUPPORTED_FILETYPES = ['png', 'gif', 'jpg', 'jpeg', 'tiff']
92f129b5 106
c56d4b55 107
ec4261a4 108def sniff_handler(media_file, **kw):
58a94757 109 _log.info('Sniffing {0}'.format(MEDIA_TYPE))
e2caf574 110 if kw.get('media') is not None: # That's a double negative!
92f129b5
JW
111 name, ext = os.path.splitext(kw['media'].filename)
112 clean_ext = ext[1:].lower() # Strip the . from ext and make lowercase
113
92f129b5
JW
114 if clean_ext in SUPPORTED_FILETYPES:
115 _log.info('Found file extension in supported filetypes')
58a94757 116 return MEDIA_TYPE
92f129b5 117 else:
10085b77 118 _log.debug('Media present, extension not found in {0}'.format(
92f129b5
JW
119 SUPPORTED_FILETYPES))
120 else:
121 _log.warning('Need additional information (keyword argument \'media\')'
122 ' to be able to handle sniffing')
123
58a94757 124 return None
ec4261a4 125
c56d4b55 126
9a2c66ca 127def process_image(proc_state, reprocess_info=None):
45ab3e07
SS
128 """Code to process an image. Will be run by celery.
129
130 A Workbench() represents a local tempory dir. It is automatically
131 cleaned up when this function exits.
93bdab9d 132 """
3e9faf85
RE
133 def init(self, proc_state):
134 self.proc_state = proc_state
135 self.entry = proc_state.entry
136 self.workbench = proc_state.workbench
49db7785 137
3e9faf85
RE
138 # Conversions subdirectory to avoid collisions
139 self.conversions_subdir = os.path.join(
140 self.workbench.dir, 'convirsions')
93b14fc3 141
3e9faf85
RE
142 self.orig_filename = proc_state.get_orig_filename()
143 self.name_builder = FilenameBuilder(self.orig_filename)
9a2c66ca 144
3e9faf85
RE
145 # Exif extraction
146 self.exif_tags = extract_exif(self.orig_filename)
147
148 os.mkdir(self.conversions_subdir)
8e5f9746 149
3e9faf85
RE
150 def initial_processing(self):
151 # Is there any GPS data
152 gps_data = get_gps_data(self.exif_tags)
e8e444a8 153
3e9faf85
RE
154 # Always create a small thumbnail
155 resize_tool(self.proc_state, True, 'thumb', self.orig_filename,
156 self.name_builder.fill('{basename}.thumbnail{ext}'),
157 self.conversions_subdir, self.exif_tags)
9a2c66ca
RE
158
159 # Possibly create a medium
3e9faf85
RE
160 resize_tool(self.proc_state, False, 'medium', self.orig_filename,
161 self.name_builder.fill('{basename}.medium{ext}'),
162 self.conversions_subdir, self.exif_tags)
9a2c66ca
RE
163
164 # Copy our queued local workbench to its final destination
3e9faf85 165 self.proc_state.copy_original(self.name_builder.fill('{basename}{ext}'))
9a2c66ca
RE
166
167 # Remove queued media file from storage and database
3e9faf85 168 self.proc_state.delete_queue_file()
3b359ddd 169
9a2c66ca 170 # Insert exif data into database
3e9faf85 171 exif_all = clean_exif(self.exif_tags)
93bdab9d 172
9a2c66ca 173 if len(exif_all):
3e9faf85 174 self.entry.media_data_init(exif_all=exif_all)
93bdab9d 175
9a2c66ca
RE
176 if len(gps_data):
177 for key in list(gps_data.keys()):
178 gps_data['gps_' + key] = gps_data.pop(key)
3e9faf85 179 self.entry.media_data_init(**gps_data)
e8e444a8 180
3e9faf85
RE
181 def reprocess(self, reprocess_info):
182 new_size = None
763ef5b7 183
3e9faf85
RE
184 # Did they specify a size?
185 if reprocess_info.get('max_width'):
186 max_width = reprocess_info['max_width']
187 max_height = reprocess_info['max_height']
9a2c66ca 188
3e9faf85 189 new_size = (max_width, max_height)
93bdab9d 190
3e9faf85
RE
191 resize_tool(self.proc_state, False, reprocess_info['resize'],
192 self.name_builder.fill('{basename}.medium{ext}'),
193 self.conversions_subdir, self.exif_tags, new_size)
e8e444a8 194
e8e444a8
JW
195if __name__ == '__main__':
196 import sys
197 import pprint
198
199 pp = pprint.PrettyPrinter()
200
201 result = extract_exif(sys.argv[1])
202 gps = get_gps_data(result)
a180ca26
JW
203 clean = clean_exif(result)
204 useful = get_useful(clean)
e8e444a8 205
e8e444a8 206 print pp.pprint(
a180ca26 207 clean)