bgneal@700: """This module contains a function to upload an image file to a S3Bucket. bgneal@696: bgneal@700: The image can be resized and a thumbnail can be generated and uploaded as well. bgneal@696: bgneal@696: """ bgneal@696: import logging bgneal@696: from io import BytesIO bgneal@696: import os.path bgneal@700: import tempfile bgneal@696: import uuid bgneal@696: bgneal@696: from PIL import Image bgneal@696: bgneal@700: from core.functions import temp_open bgneal@700: bgneal@696: bgneal@696: logger = logging.getLogger(__name__) bgneal@696: bgneal@696: bgneal@700: def upload(fp, bucket, metadata=None, new_size=None, thumb_size=None): bgneal@700: """Upload an image file to a given S3Bucket. bgneal@696: bgneal@700: The image can optionally be resized and a thumbnail can be generated and bgneal@700: uploaded as well. bgneal@696: bgneal@700: Parameters: bgneal@700: fp - The image file to process. This is expected to be an instance of bgneal@700: Django's UploadedFile class. The file must have a name attribute and bgneal@700: we expect the name to have an extension. This is extension is bgneal@700: used for the uploaded image and thumbnail names. bgneal@700: bucket - A core.s3.S3Bucket instance to upload to. bgneal@700: metadata - If not None, must be a dictionary of metadata to apply to the bgneal@700: uploaded file and thumbnail. bgneal@700: new_size - If not None, the image will be resized to the dimensions bgneal@700: specified by new_size, which must be a (width, height) tuple. bgneal@700: thumb_size - If not None, a thumbnail image will be created with the bgneal@700: dimensions specified by thumb_size, which must be a (width, bgneal@700: height) tuple. The thumbnail will use the same metadata, if bgneal@700: present, as the image. The thumbnail filename will be the bgneal@700: same basename as the image with a 't' appended. The bgneal@700: extension will be the same as the original image. bgneal@700: bgneal@700: A tuple is returned: (image_url, thumb_url) where thumb_url will be None if bgneal@700: a thumbnail was not requested. bgneal@696: """ bgneal@696: bgneal@738: filename = fp.name bgneal@737: logger.info('Processing image file: %s', filename) bgneal@700: bgneal@700: # Trying to use PIL (or Pillow) on a Django UploadedFile is often bgneal@700: # problematic because the file is often an in-memory file if it is under bgneal@700: # a certain size. This complicates matters and many of the operations we try bgneal@700: # to perform on it fail if this is the case. To get around these issues, bgneal@700: # we make a copy of the file on the file system and operate on the copy. bgneal@700: # First generate a unique name and temporary file path. bgneal@696: unique_key = uuid.uuid4().hex bgneal@700: ext = os.path.splitext(fp.name)[1] bgneal@700: temp_name = os.path.join(tempfile.gettempdir(), unique_key + ext) bgneal@696: bgneal@700: # Write the UploadedFile to a temporary file on disk bgneal@700: with temp_open(temp_name, 'wb') as temp_file: bgneal@700: for chunk in fp.chunks(): bgneal@700: temp_file.write(chunk) bgneal@700: temp_file.close() bgneal@696: bgneal@700: # Resize image if necessary bgneal@700: if new_size: bgneal@700: image = Image.open(temp_name) bgneal@700: if image.size > new_size: bgneal@700: logger.debug('Resizing from {} to {}'.format(image.size, new_size)) bgneal@700: image.thumbnail(new_size, Image.ANTIALIAS) bgneal@700: image.save(temp_name) bgneal@696: bgneal@700: # Create thumbnail if necessary bgneal@700: thumb = None bgneal@700: if thumb_size: bgneal@700: logger.debug('Creating thumbnail {}'.format(thumb_size)) bgneal@700: image = Image.open(temp_name) bgneal@700: image.thumbnail(thumb_size, Image.ANTIALIAS) bgneal@700: thumb = BytesIO() bgneal@700: image.save(thumb, format=image.format) bgneal@696: bgneal@700: # Upload images to S3 bgneal@700: file_key = unique_key + ext bgneal@700: logging.debug('Uploading image') bgneal@700: image_url = bucket.upload_from_filename(file_key, temp_name, metadata) bgneal@696: bgneal@700: thumb_url = None bgneal@700: if thumb: bgneal@700: logging.debug('Uploading thumbnail') bgneal@700: thumb_key = '{}t{}'.format(unique_key, ext) bgneal@700: thumb_url = bucket.upload_from_string(thumb_key, bgneal@700: thumb.getvalue(), bgneal@700: metadata) bgneal@696: bgneal@737: logger.info('Completed processing image file: %s', filename) bgneal@696: bgneal@700: return (image_url, thumb_url)