annotate core/image_uploader.py @ 861:e4f8d87c3d30

Configure Markdown logger to reduce noise in logs. Markdown is logging at the INFO level whenever it loads an extension. This looks like it has been fixed in master at GitHub. But until then we will explicitly configure the MARKDOWN logger to log at WARNING or higher.
author Brian Neal <bgneal@gmail.com>
date Mon, 01 Dec 2014 18:36:27 -0600
parents 234726f5a47a
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)