iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🦍

Image Resizing on S3 Upload with AWS Lambda (Python)

に公開

AWS is so convenient, isn't it?
I have very little experience with it, but I've been using it lately out of necessity. One of the things I struggled with was the image resizing process using Lambda, as mentioned in the title.

The code is written in Python, but since it's not my specialty, I'm creating this article partly as a memo. Therefore, there are no detailed explanations. Please be aware of this in advance.

S3 Folder Structure

In this development environment, the S3 folder structure is as follows.
I'm simply storing images and other files under bucket/contents.

/bucket-name/
  ┗ contents
      ├ abc.jpg
      ├ xyz.png
      ┗ movie.mp4
      ...

Specifications

The specifications for executing the resizing process in Lambda are as follows:

When an image is uploaded under contents/, resize and duplicate the image into M and S sizes, adding "-m" and "-s" to the end of the filenames respectively (maintaining the aspect ratio). Then, store the resized images in the same contents/ folder as the original image.

It's not a particularly complicated specification. This time, I've set "M size: 600px, S size: 240px". Let's take a look at the code right away.

Lambda Function

import boto3
import os
from PIL import Image
import io

s3_client = boto3.client('s3')

def resize_image(image_bytes, width):
    # Open image with PIL
    image = Image.open(io.BytesIO(image_bytes))

    # Calculate aspect ratio
    aspect_ratio = image.height / image.width
    new_height = int(width * aspect_ratio)

    # Execute resizing
    resized_image = image.resize((width, new_height), Image.Resampling.LANCZOS)

    # Save to buffer
    buffer = io.BytesIO()
    # Maintain original format
    format = image.format if image.format else 'JPEG'
    resized_image.save(buffer, format=format, quality=85)
    buffer.seek(0)

    return buffer.getvalue()

def lambda_handler(event, context):
    try:
        # Get information from S3 event
        bucket = event['Records'][0]['s3']['bucket']['name']
        key = event['Records'][0]['s3']['object']['key']

        # Process only images in the contents folder
        if not key.startswith('contents/'):
            return {
                'statusCode': 200,
                'body': 'Skipped: Not a target image'
            }

        # Skip already resized images
        if '-m.' in key or '-s.' in key:
            return {
                'statusCode': 200,
                'body': 'Skipped: Already resized image'
            }

        # Check for target extensions
        if not key.lower().endswith(('.jpg', '.jpeg', '.png', '.webp')):
            return {
                'statusCode': 200,
                'body': 'Skipped: Not a supported image format'
            }

        # Get the original image
        response = s3_client.get_object(Bucket=bucket, Key=key)
        image_bytes = response['Body'].read()

        # Separate filename and path
        directory = os.path.dirname(key)
        filename = os.path.basename(key)
        name, ext = os.path.splitext(filename)

        # Create M size (600px)
        m_image = resize_image(image_bytes, 600)
        m_filename = f"{name}-m{ext}"
        m_key = f"{directory}/{m_filename}"
        s3_client.put_object(
            Bucket=bucket,
            Key=m_key,
            Body=m_image,
            ContentType=response['ContentType']
        )

        # Create S size (240px)
        s_image = resize_image(image_bytes, 240)
        s_filename = f"{name}-s{ext}"
        s_key = f"{directory}/{s_filename}"
        s3_client.put_object(
            Bucket=bucket,
            Key=s_key,
            Body=s_image,
            ContentType=response['ContentType']
        )

        return {
            'statusCode': 200,
            'body': 'Successfully resized image'
        }

    except Exception as e:
        print(f'Error: {str(e)}')
        raise e

The image processing library used is "Pillow".
Actually, I am not very familiar with Python, so I just had claude.ai generate the above code and tuned it. Therefore, I will skip the explanation.

Other Lambda settings are as follows.

Settings tab → General settings

  • Function name: ImageResize (anything is fine)
  • Memory: 512MB
  • Ephemeral storage: 512MB
  • Timeout: 0 min 30 sec

S3 Settings

Settings are required to send event notifications from S3 to Lambda and execute the function.
In this case, we will configure it to send an event notification to Lambda and execute the above function "when an object is created (uploaded) in contents/".

Properties

Select the bucket and choose the "Properties" tab. On the next screen, click "Create event notification" in the "Event notifications" section to add an event.

Event notifications section
Event notifications section

Configure the settings on the next screen as follows.

General configuration

  • Event name: image-resit-trigger-jpg (anything is fine)
  • Prefix: contents/
  • Suffix: .jpg (the file extension you want to target for resizing)

Event types

  • Object creation: All object creation events

Destination

  • Lambda function
  • Choose from your Lambda functions (select the target function from the dropdown)

That's it for the configuration.

Now, if you actually upload an image to the S3 contents/ folder, a resized image will be generated automatically.

Below is a screenshot when "resize-test.jpg" was uploaded as a test.
You can see that "resize-test-m.jpg" and "resize-test-s.jpg" have been correctly generated.

That's all for this article. It was a rough and quick explanation, but I hope this article is helpful to someone.

Discussion