Help us understand the problem. What is going on with this article?

AWS Lambdaを使って敢えて静的に画像をリサイズする

More than 1 year has passed since last update.

動的リサイズについて世の中にはびっくりするほど沢山のいい記事があります。

サーバーレスアーキテクチャで画像の動的リサイズをしたら優勝しました 【SpeeeKaigiレポート#1】

サーバレスにサムネイル画像を配信する試み

などなど...

リクエスト <-> CDNでキャッシュ <-> ImageMagickでリサイズ <-> s3

基本的にこんな感じで実装されているわけですが、そこまでトラフィックがない場合はここまでやらなくてもいいかな―とも思ったりします。
(CDNのヒット率があまり高くないとか、そもそも試験導入したいとか)

でもまぁ、検索画面とかの商品サムネイル画像がオリジナルサイズだとSEO的にもよろしくない...
ということで手っ取り早くサムネイル画像を生成すべく、s3への画像アップロードをトリガーにして複数サイズのサムネイルを生成する処理をサクッと書いてみました。

Lambdaで関数を作る

ここをポチッと
Lambda_Management_Console_🔊.png


ここのロールはs3へ書き込みできるものを設定しておく必要があります。
(参考: AWS SDK (Node.js) で S3 putObjectするときに気をつけること)
Lambda_Management_Console_🔊.png


ポチポチっとトリガーを設定
Lambda_Management_Console_🔊.png


サムネイルの元画像がアップロードされるバケットを選択して、イベントタイプを「オブジェクトの作成(すべて)」に設定し追加します。
Lambda_Management_Console_🔊.png


関数を選んで
Lambda_Management_Console_🔊.png


コードを書いていきます
Lambda_Management_Console_🔊.png

参考: AWS Lambdaを使ってS3にアップロードされた画像をリサイズする
こちらのソースを参考にさせていただきました。

今回は、幅50~500pxで50px刻みのサムネイルを作るという要件で始めたので幾つか変更しています。

lambda
'use strict';

console.log('Loading function');

const aws = require('aws-sdk');
var fs = require('fs');
var im = require('imagemagick');
const s3 = new aws.S3({ apiVersion: '2006-03-01' });


exports.handler = (event, context, callback) => {
    const bucket = event.Records[0].s3.bucket.name;
    const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
    const params = {
        Bucket: bucket,
        Key: key,
    };
    s3.getObject(params, (err, data) => {
        if (err) {
            console.log(err);
            const message = `Error getting object ${key} from bucket ${bucket}. Make sure they exist and your bucket is in the same region as this function.`;
            console.log(message);
            callback(message);
        } else {
            console.log('CONTENT TYPE:', data.ContentType);
            callback(null, data.ContentType);
            var contentType = data.ContentType;
            var extension = contentType.split('/').pop();
            console.log(extension);
            console.log("file name:" + key);

            if (key.split('.')[0].indexOf("-thumbnail-") == -1) {
                im.resize({
                    srcData: data.Body,
                    format: extension,
                    width: 500
                }, function(err, stdout, stderr) {
                    if (err) {
                        context.done('resize failed', err);
                    } else {
                        var thumbnailKey = key.split('.')[0] + "-thumbnail-500." + key.split('.')[1];

                        s3.putObject({
                            Bucket: bucket,
                            Key: thumbnailKey,
                            Body: new Buffer(stdout, 'binary'),
                            CacheControl: 'max-age=604800',
                            ContentType: contentType
                        }, function(err, res) {
                            if (err) {
                                console.log("error putting object 500");
                                context.done('error putting object', err);
                            } else {
                                callback(null, "success putting object");
                                console.log("success putting object 500");
                            }
                        });
                    }
                });
            } else {
                var size = parseInt(key.split('.')[0].split('-thumbnail-')[1]);
                console.log("input size: " + size);
                var targetSize = (size - 50);
                console.log("target size: " + targetSize);
                if (targetSize > 0) {
                    im.resize({
                        srcData: data.Body,
                        format: extension,
                        width: targetSize
                    }, function(err, stdout, stderr) {
                        if (err) {
                            context.done('resize failed', err);
                        } else {
                            var thumbnailKey = key.split('.')[0].split('-thumbnail-')[0] + "-thumbnail-" + targetSize + "." + key.split('.')[1];

                            s3.putObject({
                                Bucket: bucket,
                                Key: thumbnailKey,
                                Body: new Buffer(stdout, 'binary'),
                                CacheControl: 'max-age=604800',
                                ContentType: contentType
                            }, function(err, res) {
                                if (err) {
                                    console.log("error putting object " + targetSize);
                                    context.done('error putting object', err);
                                } else {
                                    callback(null, "success putting object");
                                    console.log("success putting object " + targetSize);
                                }
                            });
                        }
                    });
                }
            }
        }
    });
};

まず、画像の生成をトリガーにしてLambdaが起動するため、サムネイルの作成自体(s3.putObject())でもこの処理が実行されてしまいます。
そのため、まずif (key.split('.')[0].indexOf("-thumbnail-") == -1)で今回がサムネイルの作成によって発火したものかを判定します。

また、初回のサムネイル作成では

im.resize({
    srcData: data.Body,
    format: extension,
    width: 500
}

と指定して500pxの画像を生成し、その後はファイル名からサイズを判定して50pxずつ小さくする、という方法を取っています。
(Javascriptで10回分べた書きしたりループ回したりではなく、コールバックをs3のオブジェクト生成をトリガーにするイメージでやりました。)

その後はテストを軽く書いておしまいです。

実行結果

実際にアップロードしてみるとサムネイルが作成されているのがわかります。
S3_Management_Console_🔊.png

まとめ

動的リサイズも良いのですが、そこまでサムネイルサイズのパターンが多くなく、おためしで導入する場合はこちらでも良いかもしれません。

既存のアップロードされているファイルも
BUCKET="my-bucket"; aws s3 ls s3://$BUCKET --recursive | grep -v "\-thumbnail\-" | awk -v BUCKET="${BUCKET}" '{system("aws s3api copy-object --bucket " BUCKET " --copy-source " BUCKET "/" $4 " --key " $4 " --metadata-directive REPLACE --cache-control \"max-age=86400\" --content-type \"image/jpeg\"")}'
こんな感じでaws-cliからコピーし直してあげれば全てサムネイルが生成できます。
(参考: S3バケットに含まれる全オブジェクトのCache-Controlを書き換えるワンライナー)

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした