Mercurial > public > sg101
diff core/image_uploader.py @ 700:e888d627928f
Refactored the processing of image uploads.
I suspect I will use this general algorithm in other places (like POTD), so
I made it reusable.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Wed, 11 Sep 2013 20:31:23 -0500 |
parents | user_photos/images.py@67f8d49a9377 |
children | 775e7f74096a |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/image_uploader.py Wed Sep 11 20:31:23 2013 -0500 @@ -0,0 +1,97 @@ +"""This module contains a function to upload an image file to a S3Bucket. + +The image can be resized and a thumbnail can be generated and uploaded as well. + +""" +import logging +from io import BytesIO +import os.path +import tempfile +import uuid + +from PIL import Image + +from core.functions import temp_open + + +logger = logging.getLogger(__name__) + + +def upload(fp, bucket, metadata=None, new_size=None, thumb_size=None): + """Upload an image file to a given S3Bucket. + + The image can optionally be resized and a thumbnail can be generated and + uploaded as well. + + Parameters: + fp - The image file to process. This is expected to be an instance of + Django's UploadedFile class. The file must have a name attribute and + we expect the name to have an extension. This is extension is + used for the uploaded image and thumbnail names. + bucket - A core.s3.S3Bucket instance to upload to. + metadata - If not None, must be a dictionary of metadata to apply to the + uploaded file and thumbnail. + new_size - If not None, the image will be resized to the dimensions + specified by new_size, which must be a (width, height) tuple. + thumb_size - If not None, a thumbnail image will be created with the + dimensions specified by thumb_size, which must be a (width, + height) tuple. The thumbnail will use the same metadata, if + present, as the image. The thumbnail filename will be the + same basename as the image with a 't' appended. The + extension will be the same as the original image. + + A tuple is returned: (image_url, thumb_url) where thumb_url will be None if + a thumbnail was not requested. + """ + + logger.info('Processing image file: {}'.format(fp.name)) + + # Trying to use PIL (or Pillow) on a Django UploadedFile is often + # problematic because the file is often an in-memory file if it is under + # a certain size. This complicates matters and many of the operations we try + # to perform on it fail if this is the case. To get around these issues, + # we make a copy of the file on the file system and operate on the copy. + # First generate a unique name and temporary file path. + unique_key = uuid.uuid4().hex + ext = os.path.splitext(fp.name)[1] + temp_name = os.path.join(tempfile.gettempdir(), unique_key + ext) + + # Write the UploadedFile to a temporary file on disk + with temp_open(temp_name, 'wb') as temp_file: + for chunk in fp.chunks(): + temp_file.write(chunk) + temp_file.close() + + # Resize image if necessary + if new_size: + image = Image.open(temp_name) + if image.size > new_size: + logger.debug('Resizing from {} to {}'.format(image.size, new_size)) + image.thumbnail(new_size, Image.ANTIALIAS) + image.save(temp_name) + + # Create thumbnail if necessary + thumb = None + if thumb_size: + logger.debug('Creating thumbnail {}'.format(thumb_size)) + image = Image.open(temp_name) + image.thumbnail(thumb_size, Image.ANTIALIAS) + thumb = BytesIO() + image.save(thumb, format=image.format) + + # Upload images to S3 + file_key = unique_key + ext + logging.debug('Uploading image') + image_url = bucket.upload_from_filename(file_key, temp_name, metadata) + + thumb_url = None + if thumb: + logging.debug('Uploading thumbnail') + thumb_key = '{}t{}'.format(unique_key, ext) + thumb_url = bucket.upload_from_string(thumb_key, + thumb.getvalue(), + metadata) + + logger.info('Completed processing image file: {}'.format(fp.name)) + + return (image_url, thumb_url)