annotate core/image_uploader.py @ 837:234726f5a47a

For issue #74, re-orient uploaded images if necessary.
author Brian Neal <bgneal@gmail.com>
date Sat, 04 Oct 2014 13:43:22 -0500
parents df2799f725d8
children ee47122d6277
rev   line source
bgneal@700 1 """This module contains a function to upload an image file to a S3Bucket.
bgneal@696 2
bgneal@700 3 The image can be resized and a thumbnail can be generated and uploaded as well.
bgneal@696 4
bgneal@696 5 """
bgneal@696 6 import logging
bgneal@696 7 from io import BytesIO
bgneal@696 8 import os.path
bgneal@700 9 import tempfile
bgneal@696 10 import uuid
bgneal@696 11
bgneal@696 12 from PIL import Image
bgneal@696 13
bgneal@700 14 from core.functions import temp_open
bgneal@837 15 from core.image import orient_image
bgneal@700 16
bgneal@696 17
bgneal@696 18 logger = logging.getLogger(__name__)
bgneal@696 19
bgneal@696 20
bgneal@700 21 def upload(fp, bucket, metadata=None, new_size=None, thumb_size=None):
bgneal@700 22 """Upload an image file to a given S3Bucket.
bgneal@696 23
bgneal@700 24 The image can optionally be resized and a thumbnail can be generated and
bgneal@700 25 uploaded as well.
bgneal@696 26
bgneal@700 27 Parameters:
bgneal@700 28 fp - The image file to process. This is expected to be an instance of
bgneal@700 29 Django's UploadedFile class. The file must have a name attribute and
bgneal@700 30 we expect the name to have an extension. This is extension is
bgneal@700 31 used for the uploaded image and thumbnail names.
bgneal@700 32 bucket - A core.s3.S3Bucket instance to upload to.
bgneal@700 33 metadata - If not None, must be a dictionary of metadata to apply to the
bgneal@700 34 uploaded file and thumbnail.
bgneal@700 35 new_size - If not None, the image will be resized to the dimensions
bgneal@700 36 specified by new_size, which must be a (width, height) tuple.
bgneal@700 37 thumb_size - If not None, a thumbnail image will be created with the
bgneal@700 38 dimensions specified by thumb_size, which must be a (width,
bgneal@700 39 height) tuple. The thumbnail will use the same metadata, if
bgneal@700 40 present, as the image. The thumbnail filename will be the
bgneal@700 41 same basename as the image with a 't' appended. The
bgneal@700 42 extension will be the same as the original image.
bgneal@700 43
bgneal@700 44 A tuple is returned: (image_url, thumb_url) where thumb_url will be None if
bgneal@700 45 a thumbnail was not requested.
bgneal@696 46 """
bgneal@696 47
bgneal@738 48 filename = fp.name
bgneal@737 49 logger.info('Processing image file: %s', filename)
bgneal@700 50
bgneal@700 51 # Trying to use PIL (or Pillow) on a Django UploadedFile is often
bgneal@700 52 # problematic because the file is often an in-memory file if it is under
bgneal@700 53 # a certain size. This complicates matters and many of the operations we try
bgneal@700 54 # to perform on it fail if this is the case. To get around these issues,
bgneal@700 55 # we make a copy of the file on the file system and operate on the copy.
bgneal@700 56 # First generate a unique name and temporary file path.
bgneal@696 57 unique_key = uuid.uuid4().hex
bgneal@700 58 ext = os.path.splitext(fp.name)[1]
bgneal@700 59 temp_name = os.path.join(tempfile.gettempdir(), unique_key + ext)
bgneal@696 60
bgneal@700 61 # Write the UploadedFile to a temporary file on disk
bgneal@700 62 with temp_open(temp_name, 'wb') as temp_file:
bgneal@700 63 for chunk in fp.chunks():
bgneal@700 64 temp_file.write(chunk)
bgneal@700 65 temp_file.close()
bgneal@696 66
bgneal@837 67 # Re-orient if necessary
bgneal@837 68 image = Image.open(temp_name)
bgneal@837 69 changed, image = orient_image(image)
bgneal@837 70 if changed:
bgneal@837 71 image.save(temp_name)
bgneal@837 72
bgneal@700 73 # Resize image if necessary
bgneal@700 74 if new_size:
bgneal@700 75 image = Image.open(temp_name)
bgneal@700 76 if image.size > new_size:
bgneal@700 77 logger.debug('Resizing from {} to {}'.format(image.size, new_size))
bgneal@700 78 image.thumbnail(new_size, Image.ANTIALIAS)
bgneal@700 79 image.save(temp_name)
bgneal@696 80
bgneal@700 81 # Create thumbnail if necessary
bgneal@700 82 thumb = None
bgneal@700 83 if thumb_size:
bgneal@700 84 logger.debug('Creating thumbnail {}'.format(thumb_size))
bgneal@700 85 image = Image.open(temp_name)
bgneal@700 86 image.thumbnail(thumb_size, Image.ANTIALIAS)
bgneal@700 87 thumb = BytesIO()
bgneal@700 88 image.save(thumb, format=image.format)
bgneal@696 89
bgneal@700 90 # Upload images to S3
bgneal@700 91 file_key = unique_key + ext
bgneal@700 92 logging.debug('Uploading image')
bgneal@700 93 image_url = bucket.upload_from_filename(file_key, temp_name, metadata)
bgneal@696 94
bgneal@700 95 thumb_url = None
bgneal@700 96 if thumb:
bgneal@700 97 logging.debug('Uploading thumbnail')
bgneal@700 98 thumb_key = '{}t{}'.format(unique_key, ext)
bgneal@700 99 thumb_url = bucket.upload_from_string(thumb_key,
bgneal@700 100 thumb.getvalue(),
bgneal@700 101 metadata)
bgneal@696 102
bgneal@737 103 logger.info('Completed processing image file: %s', filename)
bgneal@696 104
bgneal@700 105 return (image_url, thumb_url)