annotate core/images/upload.py @ 1197:ba23e79438f4

Another attempt to write BytesIO to a file.
author Brian Neal <bgneal@gmail.com>
date Sun, 07 May 2023 19:23:35 -0500
parents e3a7e876aca7
children 3a03c2b2df05
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@885 6 from base64 import b64encode
bgneal@1195 7 import datetime
bgneal@696 8 import logging
bgneal@696 9 from io import BytesIO
bgneal@1196 10 import os
bgneal@696 11 import os.path
bgneal@1195 12 import shutil
bgneal@696 13 import uuid
bgneal@696 14
bgneal@1195 15 from django.conf import settings
bgneal@1195 16 from django.contrib.sites.models import Site
bgneal@696 17 from PIL import Image
bgneal@696 18
bgneal@971 19 from .utils import orient_image
bgneal@1195 20 from core.s3 import S3Bucket
bgneal@700 21
bgneal@696 22
bgneal@696 23 logger = logging.getLogger(__name__)
bgneal@696 24
bgneal@696 25
bgneal@1195 26 def process_upload(user, filename):
bgneal@1195 27 if settings.USER_PHOTOS_LOCAL_UPLOAD:
bgneal@1195 28 url, thumb_url = _process_local_upload(
bgneal@1195 29 filename,
bgneal@1195 30 new_size=settings.USER_PHOTOS_MAX_SIZE,
bgneal@1195 31 thumb_size=settings.USER_PHOTOS_THUMB_SIZE)
bgneal@1195 32 else:
bgneal@1195 33 now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
bgneal@1195 34 metadata = {'user': user.username, 'date': now}
bgneal@1195 35
bgneal@1195 36 bucket = S3Bucket(access_key=settings.USER_PHOTOS_ACCESS_KEY,
bgneal@1195 37 secret_key=settings.USER_PHOTOS_SECRET_KEY,
bgneal@1195 38 base_url=settings.USER_PHOTOS_BASE_URL,
bgneal@1195 39 bucket_name=settings.USER_PHOTOS_BUCKET)
bgneal@1195 40
bgneal@1195 41 url, thumb_url = upload(filename,
bgneal@1195 42 bucket=bucket,
bgneal@1195 43 metadata=metadata,
bgneal@1195 44 new_size=settings.USER_PHOTOS_MAX_SIZE,
bgneal@1195 45 thumb_size=settings.USER_PHOTOS_THUMB_SIZE)
bgneal@1195 46 return (url, thumb_url)
bgneal@1195 47
bgneal@1195 48
bgneal@885 49 def make_key():
bgneal@885 50 """Generate a random key suitable for a filename"""
bgneal@885 51 return b64encode(uuid.uuid4().bytes, '-_').rstrip('=')
bgneal@885 52
bgneal@885 53
bgneal@964 54 def upload(filename, bucket, metadata=None, new_size=None, thumb_size=None):
bgneal@700 55 """Upload an image file to a given S3Bucket.
bgneal@696 56
bgneal@700 57 The image can optionally be resized and a thumbnail can be generated and
bgneal@700 58 uploaded as well.
bgneal@696 59
bgneal@700 60 Parameters:
bgneal@964 61 filename - The path to the file to process. The filename should have an
bgneal@964 62 extension, and this is used for the uploaded image & thumbnail
bgneal@964 63 names.
bgneal@700 64 bucket - A core.s3.S3Bucket instance to upload to.
bgneal@700 65 metadata - If not None, must be a dictionary of metadata to apply to the
bgneal@700 66 uploaded file and thumbnail.
bgneal@700 67 new_size - If not None, the image will be resized to the dimensions
bgneal@700 68 specified by new_size, which must be a (width, height) tuple.
bgneal@700 69 thumb_size - If not None, a thumbnail image will be created with the
bgneal@700 70 dimensions specified by thumb_size, which must be a (width,
bgneal@700 71 height) tuple. The thumbnail will use the same metadata, if
bgneal@700 72 present, as the image. The thumbnail filename will be the
bgneal@700 73 same basename as the image with a 't' appended. The
bgneal@700 74 extension will be the same as the original image.
bgneal@700 75
bgneal@700 76 A tuple is returned: (image_url, thumb_url) where thumb_url will be None if
bgneal@700 77 a thumbnail was not requested.
bgneal@696 78 """
bgneal@696 79
bgneal@737 80 logger.info('Processing image file: %s', filename)
bgneal@700 81
bgneal@885 82 unique_key = make_key()
bgneal@964 83 ext = os.path.splitext(filename)[1]
bgneal@696 84
bgneal@964 85 # Re-orient if necessary
bgneal@964 86 image = Image.open(filename)
bgneal@964 87 changed, image = orient_image(image)
bgneal@964 88 if changed:
bgneal@964 89 image.save(filename)
bgneal@696 90
bgneal@964 91 # Resize image if necessary
bgneal@964 92 if new_size:
bgneal@964 93 image = Image.open(filename)
bgneal@964 94 if image.size > new_size:
bgneal@964 95 logger.debug('Resizing from {} to {}'.format(image.size, new_size))
bgneal@964 96 image.thumbnail(new_size, Image.ANTIALIAS)
bgneal@964 97 image.save(filename)
bgneal@837 98
bgneal@964 99 # Create thumbnail if necessary
bgneal@964 100 thumb = None
bgneal@964 101 if thumb_size:
bgneal@964 102 logger.debug('Creating thumbnail {}'.format(thumb_size))
bgneal@964 103 image = Image.open(filename)
bgneal@964 104 image.thumbnail(thumb_size, Image.ANTIALIAS)
bgneal@964 105 thumb = BytesIO()
bgneal@964 106 image.save(thumb, format=image.format)
bgneal@696 107
bgneal@964 108 # Upload images to S3
bgneal@964 109 file_key = unique_key + ext
bgneal@964 110 logging.debug('Uploading image')
bgneal@964 111 image_url = bucket.upload_from_filename(file_key, filename, metadata)
bgneal@696 112
bgneal@700 113 thumb_url = None
bgneal@700 114 if thumb:
bgneal@700 115 logging.debug('Uploading thumbnail')
bgneal@700 116 thumb_key = '{}t{}'.format(unique_key, ext)
bgneal@700 117 thumb_url = bucket.upload_from_string(thumb_key,
bgneal@700 118 thumb.getvalue(),
bgneal@700 119 metadata)
bgneal@696 120
bgneal@737 121 logger.info('Completed processing image file: %s', filename)
bgneal@696 122
bgneal@700 123 return (image_url, thumb_url)
bgneal@1195 124
bgneal@1195 125
bgneal@1195 126 def _process_local_upload(filename, new_size=None, thumb_size=None):
bgneal@1195 127 # Re-orient if necessary
bgneal@1195 128 image = Image.open(filename)
bgneal@1195 129 changed, image = orient_image(image)
bgneal@1195 130 if changed:
bgneal@1195 131 image.save(filename)
bgneal@1195 132
bgneal@1195 133 # Resize image if necessary
bgneal@1195 134 if new_size:
bgneal@1195 135 image = Image.open(filename)
bgneal@1195 136 if image.size > new_size:
bgneal@1195 137 logger.debug('Resizing from {} to {}'.format(image.size, new_size))
bgneal@1195 138 image.thumbnail(new_size, Image.ANTIALIAS)
bgneal@1195 139 image.save(filename)
bgneal@1195 140
bgneal@1195 141 # Create thumbnail if necessary
bgneal@1195 142 thumb = None
bgneal@1195 143 if thumb_size:
bgneal@1195 144 logger.debug('Creating thumbnail {}'.format(thumb_size))
bgneal@1195 145 image = Image.open(filename)
bgneal@1195 146 image.thumbnail(thumb_size, Image.ANTIALIAS)
bgneal@1195 147 thumb = BytesIO()
bgneal@1195 148 image.save(thumb, format=image.format)
bgneal@1195 149
bgneal@1195 150 # Copy file and thumbnail to our user upload files directory.
bgneal@1195 151 upload_dir = os.path.join(settings.MEDIA_ROOT,
bgneal@1195 152 settings.USER_PHOTOS_LOCAL_UPLOAD_DIR)
bgneal@1195 153 unique_name = uuid.uuid4().hex
bgneal@1195 154 ext = os.path.splitext(filename)[1]
bgneal@1195 155 image_name = '%s%s' % (unique_name, ext)
bgneal@1195 156 thumb_name = '%st%s' % (unique_name, ext)
bgneal@1195 157 image_path = os.path.join(upload_dir, image_name)
bgneal@1195 158 thumb_path = os.path.join(upload_dir, thumb_name)
bgneal@1196 159 perms = 0644
bgneal@1195 160
bgneal@1195 161 shutil.copy(filename, image_path)
bgneal@1196 162 os.chmod(image_path, perms)
bgneal@1196 163
bgneal@1195 164 if thumb:
bgneal@1196 165 thumb.seek(0)
bgneal@1197 166 with open(thumb_path, 'wb') as out:
bgneal@1197 167 out.write(thumb.getvalue())
bgneal@1196 168 os.chmod(thumb_path, perms)
bgneal@1195 169
bgneal@1195 170 # Generate URLs for the image and thumb.
bgneal@1196 171 image_url = _user_photo_url(image_name)
bgneal@1196 172 thumb_url = _user_photo_url(thumb_name)
bgneal@1196 173
bgneal@1196 174 return (image_url, thumb_url)
bgneal@1196 175
bgneal@1196 176
bgneal@1196 177 def _user_photo_url(filename):
bgneal@1196 178 url_pattern = '%s://%s%s%s/%s'
bgneal@1195 179 site = Site.objects.get_current()
bgneal@1195 180
bgneal@1195 181 image_url = url_pattern % (
bgneal@1195 182 settings.SITE_SCHEME,
bgneal@1195 183 site.domain,
bgneal@1195 184 settings.MEDIA_URL,
bgneal@1195 185 settings.USER_PHOTOS_LOCAL_UPLOAD_DIR,
bgneal@1196 186 filename)