🦍

LambdaでS3に画像をアップロードしたらリサイズする処理(Python)

2024/11/04に公開

AWS 便利ですよねー。
私はほとんど経験がないのですが必要に駆られて最近触ってます。その中でも苦戦したのが表題の Lambda による画像のリサイズ処理です。

コードは Python で書かれていますが専門ではないのでこの記事はメモの意味も含めて作成しています。そのため、詳しい解説などはありません。予めご了承ください。

S3 フォルダ構成

今回の開発環境では S3 のフォルダ構成は下記のようになっています。
単純に bucket/contents 配下に画像などを格納しているだけです。

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

仕様

Lambda でリサイズ処理を実行する上での仕様は下記のようにしました。

contets/ 配下に画像がアップロードされたら M サイズと S サイズの画像をリサイズして複製し、ファイル名の末尾にそれぞれ "-m", "-s"を追加する(アスペクト比は維持)。そして、リサイズした画像はオリジナル画像と同じ contents/ に収納する

特にややこしい仕様ではないですね。今回は「M サイズ: 600px, S サイズ: 240px」としています。早速コードを見ていきましょう。

Lambda 関数

import boto3
import os
from PIL import Image
import io

s3_client = boto3.client('s3')

def resize_image(image_bytes, width):
    # PILでイメージを開く
    image = Image.open(io.BytesIO(image_bytes))

    # アスペクト比を計算
    aspect_ratio = image.height / image.width
    new_height = int(width * aspect_ratio)

    # リサイズ実行
    resized_image = image.resize((width, new_height), Image.Resampling.LANCZOS)

    # バッファに保存
    buffer = io.BytesIO()
    # 元の形式を保持
    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:
        # S3イベントから情報を取得
        bucket = event['Records'][0]['s3']['bucket']['name']
        key = event['Records'][0]['s3']['object']['key']

        # contentsフォルダ内の画像のみ処理
        if not key.startswith('contents/'):
            return {
                'statusCode': 200,
                'body': 'Skipped: Not a target image'
            }

        # リサイズ済み画像はスキップ
        if '-m.' in key or '-s.' in key:
            return {
                'statusCode': 200,
                'body': 'Skipped: Already resized image'
            }

        # 対象の拡張子をチェック
        if not key.lower().endswith(('.jpg', '.jpeg', '.png', '.webp')):
            return {
                'statusCode': 200,
                'body': 'Skipped: Not a supported image format'
            }

        # 元画像を取得
        response = s3_client.get_object(Bucket=bucket, Key=key)
        image_bytes = response['Body'].read()

        # ファイル名とパスを分離
        directory = os.path.dirname(key)
        filename = os.path.basename(key)
        name, ext = os.path.splitext(filename)

        # Mサイズ (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']
        )

        # Sサイズ (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

画像処理のライブラリは「Pillow」を使用しています。
実は私は Python については詳しくないので上記コードは cloude.ai に生成してもらい、チューニングしただけです。そのため説明は割愛します。

その他の Lambda の設定は下記のようにしています。

設定タブ → 一般設定

・関数名: ImageResize(なんでも良い)
・メモリ: 512MB
・エフェメラルストレージ: 512MB
・タイムアウト: 0 分 30 秒

S3 の設定

S3 から Lambda へイベント通知を行い、関数を実行する設定が必要です。
今回は 「contents/にオブジェクトが生成されたら(アップロードされたら)」 Lambda へイベント通知を行い上記の関数を実行するように設定を行います。

プロパティ

バケットを選択し 「プロパティ」 タブを選択します。次の画面で 「イベント通知」 セクションにある 「イベント通知を作成」 をクリックしイベントを追加します。

イベント通知セクション
イベント通知セクション

その次の画面にて下記のように設定します。

一般背的な設定

・イベント名;image-resit-trigger-jpg(なんでも良い)
・プレフィックス: contents/
・サフィックス: .jpg(リサイズ対象にしたいファイル拡張子)

イベントタイプ

・オブジェクトの作成;すべてのオブジェクト作成イベント

送信先

・Lambda 関数
・Lambda 関数から選択する(プルダウンから対象の関数を選択する)

以上で完了です。

あとは実際に S3 の contents/ フォルダに画像をアップロードすれば自動でリサイズした画像が生成されます。

下記はテストとして "resize-test.jpg" をアップロードした場合のキャプチャです。
ちゃんと"resize-test-m.jpg", "resize-test-s.jpg" が生成されていますね。

今回の記事は以上になります。粗く駆け足での説明になりましたがこの記事が誰かの役に立てば幸いです。

Discussion