comparison core/image_uploader.py @ 964:51a2051588f5

Image uploading now expects a file. Refactor image uploading to not expect a Django UploadedFile and use a regular file instead. This will be needed for the future feature of being able to save and upload images from the Internet.
author Brian Neal <bgneal@gmail.com>
date Wed, 02 Sep 2015 20:50:08 -0500
parents ee47122d6277
children
comparison
equal deleted inserted replaced
963:4619290d171d 964:51a2051588f5
5 """ 5 """
6 from base64 import b64encode 6 from base64 import b64encode
7 import logging 7 import logging
8 from io import BytesIO 8 from io import BytesIO
9 import os.path 9 import os.path
10 import tempfile
11 import uuid 10 import uuid
12 11
13 from PIL import Image 12 from PIL import Image
14 13
15 from core.functions import temp_open
16 from core.image import orient_image 14 from core.image import orient_image
17 15
18 16
19 logger = logging.getLogger(__name__) 17 logger = logging.getLogger(__name__)
20 18
22 def make_key(): 20 def make_key():
23 """Generate a random key suitable for a filename""" 21 """Generate a random key suitable for a filename"""
24 return b64encode(uuid.uuid4().bytes, '-_').rstrip('=') 22 return b64encode(uuid.uuid4().bytes, '-_').rstrip('=')
25 23
26 24
27 def upload(fp, bucket, metadata=None, new_size=None, thumb_size=None): 25 def upload(filename, bucket, metadata=None, new_size=None, thumb_size=None):
28 """Upload an image file to a given S3Bucket. 26 """Upload an image file to a given S3Bucket.
29 27
30 The image can optionally be resized and a thumbnail can be generated and 28 The image can optionally be resized and a thumbnail can be generated and
31 uploaded as well. 29 uploaded as well.
32 30
33 Parameters: 31 Parameters:
34 fp - The image file to process. This is expected to be an instance of 32 filename - The path to the file to process. The filename should have an
35 Django's UploadedFile class. The file must have a name attribute and 33 extension, and this is used for the uploaded image & thumbnail
36 we expect the name to have an extension. This is extension is 34 names.
37 used for the uploaded image and thumbnail names.
38 bucket - A core.s3.S3Bucket instance to upload to. 35 bucket - A core.s3.S3Bucket instance to upload to.
39 metadata - If not None, must be a dictionary of metadata to apply to the 36 metadata - If not None, must be a dictionary of metadata to apply to the
40 uploaded file and thumbnail. 37 uploaded file and thumbnail.
41 new_size - If not None, the image will be resized to the dimensions 38 new_size - If not None, the image will be resized to the dimensions
42 specified by new_size, which must be a (width, height) tuple. 39 specified by new_size, which must be a (width, height) tuple.
49 46
50 A tuple is returned: (image_url, thumb_url) where thumb_url will be None if 47 A tuple is returned: (image_url, thumb_url) where thumb_url will be None if
51 a thumbnail was not requested. 48 a thumbnail was not requested.
52 """ 49 """
53 50
54 filename = fp.name
55 logger.info('Processing image file: %s', filename) 51 logger.info('Processing image file: %s', filename)
56 52
57 # Trying to use PIL (or Pillow) on a Django UploadedFile is often
58 # problematic because the file is often an in-memory file if it is under
59 # a certain size. This complicates matters and many of the operations we try
60 # to perform on it fail if this is the case. To get around these issues,
61 # we make a copy of the file on the file system and operate on the copy.
62 # First generate a unique name and temporary file path.
63 unique_key = make_key() 53 unique_key = make_key()
64 ext = os.path.splitext(fp.name)[1] 54 ext = os.path.splitext(filename)[1]
65 temp_name = os.path.join(tempfile.gettempdir(), unique_key + ext)
66 55
67 # Write the UploadedFile to a temporary file on disk 56 # Re-orient if necessary
68 with temp_open(temp_name, 'wb') as temp_file: 57 image = Image.open(filename)
69 for chunk in fp.chunks(): 58 changed, image = orient_image(image)
70 temp_file.write(chunk) 59 if changed:
71 temp_file.close() 60 image.save(filename)
72 61
73 # Re-orient if necessary 62 # Resize image if necessary
74 image = Image.open(temp_name) 63 if new_size:
75 changed, image = orient_image(image) 64 image = Image.open(filename)
76 if changed: 65 if image.size > new_size:
77 image.save(temp_name) 66 logger.debug('Resizing from {} to {}'.format(image.size, new_size))
67 image.thumbnail(new_size, Image.ANTIALIAS)
68 image.save(filename)
78 69
79 # Resize image if necessary 70 # Create thumbnail if necessary
80 if new_size: 71 thumb = None
81 image = Image.open(temp_name) 72 if thumb_size:
82 if image.size > new_size: 73 logger.debug('Creating thumbnail {}'.format(thumb_size))
83 logger.debug('Resizing from {} to {}'.format(image.size, new_size)) 74 image = Image.open(filename)
84 image.thumbnail(new_size, Image.ANTIALIAS) 75 image.thumbnail(thumb_size, Image.ANTIALIAS)
85 image.save(temp_name) 76 thumb = BytesIO()
77 image.save(thumb, format=image.format)
86 78
87 # Create thumbnail if necessary 79 # Upload images to S3
88 thumb = None 80 file_key = unique_key + ext
89 if thumb_size: 81 logging.debug('Uploading image')
90 logger.debug('Creating thumbnail {}'.format(thumb_size)) 82 image_url = bucket.upload_from_filename(file_key, filename, metadata)
91 image = Image.open(temp_name)
92 image.thumbnail(thumb_size, Image.ANTIALIAS)
93 thumb = BytesIO()
94 image.save(thumb, format=image.format)
95
96 # Upload images to S3
97 file_key = unique_key + ext
98 logging.debug('Uploading image')
99 image_url = bucket.upload_from_filename(file_key, temp_name, metadata)
100 83
101 thumb_url = None 84 thumb_url = None
102 if thumb: 85 if thumb:
103 logging.debug('Uploading thumbnail') 86 logging.debug('Uploading thumbnail')
104 thumb_key = '{}t{}'.format(unique_key, ext) 87 thumb_key = '{}t{}'.format(unique_key, ext)