Amazon S3 にある大量の既存画像を AWS Lambda で一気に変換する


# やりたいこと

* Amazon S3 に貯めてきた大量の画像を、すべてリサイズ＆加工したい.
  * 正方形画像を同比率のままリサイズし、左右に帯を付けたい.
* でも、加工処理のためのサーバをたてたり、画像ファイルを出し入れしたくない.
* あっ、AWS Lambda でいけんじゃね.

# 課題

Lambda Function はイベント駆動での処理を想定した仕組みなので、「S3 に新しく追加/更新した画像を、その場で変換」にはうまくフィットするのだが、「既に S3 に入っている大量の画像を逐次変換」にはフィットしないように思える.

# 解決策

[Invoke API](http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/API_Invoke.html) をアクセスしてやれば良い.

# サンプル

## 画像加工処理

[AWS Lambda ウォークスルー 2: AWS CLI を使用して Amazon S3 イベントを処理する (Node.js) - AWS Lambda](http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/walkthrough-s3-events-adminuser.html) のサンプルを参考に、画像の取得元と、加工処理、保存先をそれぞれ以下のように書き換えた.

```ResizeImage.js
// dependencies
var async = require('async');
var AWS = require('aws-sdk');
var gm = require('gm').subClass({ imageMagick: true }); // Enable ImageMagick integration.
var util = require('util');
var path = require('path');

// get reference to S3 client 
var s3 = new AWS.S3();
 
exports.handler = function(event, context) {
	// Read options from the event.
	console.log("Reading options from event:\n", util.inspect(event, {depth: 5}));
	var srcBucket = event.Records[0].s3.bucket.name;
	// Object key may have spaces or unicode non-ASCII characters.
    var srcKey    =
    decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, " "));
    // 格納先は、元画像と同じ場所に.
	var dstBucket = srcBucket;
	var dstKey    = path.dirname(event.Records[0].s3.object.key) + "/resized.jpg";

	// Infer the image type.
	var typeMatch = srcKey.match(/\.([^.]*)$/);
	if (!typeMatch) {
		console.error('unable to infer image type for key ' + srcKey);
		return;
	}
	var imageType = typeMatch[1];
	if (imageType != "jpg") {
		console.log('skipping non-image ' + srcKey);
		return;
	}

	// Download the image from S3, transform, and upload to a different S3 bucket.
	async.waterfall([
		function download(next) {
			// Download the image from S3 into a buffer.
			s3.getObject({
					Bucket: srcBucket,
					Key: srcKey
				},
				next);
			},
		function transform(response, next) {
			gm(response.Body).size(function(err, size) {
				var width  = 640;
				var height = 480;

				// Transform the image buffer in memory.
				// 比率をそのままにリサイズし、左右に黒帯をつける.
				this.background('#000000')
                    .resize(width, height)
                    .gravity("Center")
                    .extent(width, height)
					.toBuffer(imageType, function(err, buffer) {
						if (err) {
							next(err);
						} else {
							next(null, response.ContentType, buffer);
						}
					});
			});
		},
		function upload(contentType, data, next) {
			// Stream the transformed image to a different S3 bucket.
			s3.putObject({
					Bucket: dstBucket,
					Key: dstKey,
					Body: data,
					ContentType: contentType
				},
				next);
			}
		], function (err) {
			if (err) {
				console.error(
					'Unable to resize ' + srcBucket + '/' + srcKey +
					' and upload to ' + dstBucket + '/' + dstKey +
					' due to an error: ' + err
				);
			} else {
				console.log(
					'Successfully resized ' + srcBucket + '/' + srcKey +
					' and uploaded to ' + dstBucket + '/' + dstKey
				);
			}

			context.done();
		}
	);
};
```

## 起動

S3 に保存されている画像に対して、[ステップ 2.3: デプロイパッケージをアップロードしてテストする（Amazon S3 | Node.js） - AWS Lambda](http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/walkthrough-s3-events-adminuser-create-test-function-upload-zip-test.html#walkthrough-s3-events-adminuser-create-test-function-upload-zip-test-manual-invoke) にあるようなイベントデータ JSON を組み立てながら順次 invoke をコールしてやる.

# ハマったこと

> Important
> フォルダーそのものではなく、フォルダーの内容を zip 圧縮します。

って書いてあるのに、フォルダそのまま zip してアップロードしてしまった件.

# こういう利用方法はアリなのか

[よくある質問 - AWS Lambda | AWS](https://aws.amazon.com/jp/lambda/faqs/) には以下のようにあるので、通常の利用範囲内のように思える.

> Q: アプリケーションで AWS Lambda 関数を直接トリガーするにはどうすればよいですか?

> AWS Lambda の「invoke」API でカスタムイベントを使用して、Lambda 関数を呼び出すことができます。関数の所有者または所有者が許可を与えた他の AWS アカウントのみ関数を呼び出すことができます。詳しくは、Lambda Developers Guide をご覧ください。

# 参考

* [AWS Lambda とは - AWS Lambda](http://docs.aws.amazon.com/ja_jp/lambda/latest/dg/welcome.html)
* [AWS - S3に画像をアップロードしたらLambdaでサムネイルを生成する(node-imagemagick) - Qiita](http://qiita.com/ryo0301/items/7f9fd8024987526cbc30)