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