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@885: from base64 import b64encode bgneal@1195: import datetime bgneal@696: import logging bgneal@696: from io import BytesIO bgneal@1196: import os bgneal@696: import os.path bgneal@1195: import shutil bgneal@696: import uuid bgneal@696: bgneal@1195: from django.conf import settings bgneal@1195: from django.contrib.sites.models import Site bgneal@696: from PIL import Image bgneal@696: bgneal@971: from .utils import orient_image bgneal@1195: from core.s3 import S3Bucket bgneal@700: bgneal@696: bgneal@696: logger = logging.getLogger(__name__) bgneal@696: bgneal@696: bgneal@1195: def process_upload(user, filename): bgneal@1195: if settings.USER_PHOTOS_LOCAL_UPLOAD: bgneal@1195: url, thumb_url = _process_local_upload( bgneal@1195: filename, bgneal@1195: new_size=settings.USER_PHOTOS_MAX_SIZE, bgneal@1195: thumb_size=settings.USER_PHOTOS_THUMB_SIZE) bgneal@1195: else: bgneal@1195: now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') bgneal@1195: metadata = {'user': user.username, 'date': now} bgneal@1195: bgneal@1195: bucket = S3Bucket(access_key=settings.USER_PHOTOS_ACCESS_KEY, bgneal@1195: secret_key=settings.USER_PHOTOS_SECRET_KEY, bgneal@1195: base_url=settings.USER_PHOTOS_BASE_URL, bgneal@1195: bucket_name=settings.USER_PHOTOS_BUCKET) bgneal@1195: bgneal@1195: url, thumb_url = upload(filename, bgneal@1195: bucket=bucket, bgneal@1195: metadata=metadata, bgneal@1195: new_size=settings.USER_PHOTOS_MAX_SIZE, bgneal@1195: thumb_size=settings.USER_PHOTOS_THUMB_SIZE) bgneal@1195: return (url, thumb_url) bgneal@1195: bgneal@1195: bgneal@885: def make_key(): bgneal@885: """Generate a random key suitable for a filename""" bgneal@885: return b64encode(uuid.uuid4().bytes, '-_').rstrip('=') bgneal@885: bgneal@885: bgneal@964: def upload(filename, 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@964: filename - The path to the file to process. The filename should have an bgneal@964: extension, and this is used for the uploaded image & thumbnail bgneal@964: 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@737: logger.info('Processing image file: %s', filename) bgneal@700: bgneal@885: unique_key = make_key() bgneal@964: ext = os.path.splitext(filename)[1] bgneal@696: bgneal@964: # Re-orient if necessary bgneal@964: image = Image.open(filename) bgneal@964: changed, image = orient_image(image) bgneal@964: if changed: bgneal@964: image.save(filename) bgneal@696: bgneal@964: # Resize image if necessary bgneal@964: if new_size: bgneal@964: image = Image.open(filename) bgneal@964: if image.size > new_size: bgneal@964: logger.debug('Resizing from {} to {}'.format(image.size, new_size)) bgneal@964: image.thumbnail(new_size, Image.ANTIALIAS) bgneal@964: image.save(filename) bgneal@837: bgneal@964: # Create thumbnail if necessary bgneal@964: thumb = None bgneal@964: if thumb_size: bgneal@964: logger.debug('Creating thumbnail {}'.format(thumb_size)) bgneal@964: image = Image.open(filename) bgneal@964: image.thumbnail(thumb_size, Image.ANTIALIAS) bgneal@964: thumb = BytesIO() bgneal@964: image.save(thumb, format=image.format) bgneal@696: bgneal@964: # Upload images to S3 bgneal@964: file_key = unique_key + ext bgneal@964: logging.debug('Uploading image') bgneal@964: image_url = bucket.upload_from_filename(file_key, filename, 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) bgneal@1195: bgneal@1195: bgneal@1195: def _process_local_upload(filename, new_size=None, thumb_size=None): bgneal@1195: # Re-orient if necessary bgneal@1195: image = Image.open(filename) bgneal@1195: changed, image = orient_image(image) bgneal@1195: if changed: bgneal@1195: image.save(filename) bgneal@1195: bgneal@1195: # Resize image if necessary bgneal@1195: if new_size: bgneal@1195: image = Image.open(filename) bgneal@1195: if image.size > new_size: bgneal@1195: logger.debug('Resizing from {} to {}'.format(image.size, new_size)) bgneal@1195: image.thumbnail(new_size, Image.ANTIALIAS) bgneal@1195: image.save(filename) bgneal@1195: bgneal@1195: # Create thumbnail if necessary bgneal@1195: thumb = None bgneal@1195: if thumb_size: bgneal@1195: logger.debug('Creating thumbnail {}'.format(thumb_size)) bgneal@1195: image = Image.open(filename) bgneal@1195: image.thumbnail(thumb_size, Image.ANTIALIAS) bgneal@1195: thumb = BytesIO() bgneal@1195: image.save(thumb, format=image.format) bgneal@1195: bgneal@1195: # Copy file and thumbnail to our user upload files directory. bgneal@1195: upload_dir = os.path.join(settings.MEDIA_ROOT, bgneal@1195: settings.USER_PHOTOS_LOCAL_UPLOAD_DIR) bgneal@1195: unique_name = uuid.uuid4().hex bgneal@1195: ext = os.path.splitext(filename)[1] bgneal@1195: image_name = '%s%s' % (unique_name, ext) bgneal@1195: thumb_name = '%st%s' % (unique_name, ext) bgneal@1195: image_path = os.path.join(upload_dir, image_name) bgneal@1195: thumb_path = os.path.join(upload_dir, thumb_name) bgneal@1196: perms = 0644 bgneal@1195: bgneal@1195: shutil.copy(filename, image_path) bgneal@1196: os.chmod(image_path, perms) bgneal@1196: bgneal@1195: if thumb: bgneal@1196: thumb.seek(0) bgneal@1195: shutil.copyfileobj(thumb, thumb_path) bgneal@1196: os.chmod(thumb_path, perms) bgneal@1195: bgneal@1195: # Generate URLs for the image and thumb. bgneal@1196: image_url = _user_photo_url(image_name) bgneal@1196: thumb_url = _user_photo_url(thumb_name) bgneal@1196: bgneal@1196: return (image_url, thumb_url) bgneal@1196: bgneal@1196: bgneal@1196: def _user_photo_url(filename): bgneal@1196: url_pattern = '%s://%s%s%s/%s' bgneal@1195: site = Site.objects.get_current() bgneal@1195: bgneal@1195: image_url = url_pattern % ( bgneal@1195: settings.SITE_SCHEME, bgneal@1195: site.domain, bgneal@1195: settings.MEDIA_URL, bgneal@1195: settings.USER_PHOTOS_LOCAL_UPLOAD_DIR, bgneal@1196: filename)