Lambdaで画像のサムネイルを作る [Stream yields empty buffer対策済]

S3に画像をアップロードしたときにその画像のサムネイルを自動生成する…という類のものです。はじめは、本家のチュートリアルを参考にしてやってみたんですが、`[Stream yields empty buffer]`というエラーが出て行き詰まり、解決を探しても見つからなかったので、結局自作しました。

ちなみに、AWS Lambdaでサムネイルを作る本家チュートリアルは[こちら](http://docs.aws.amazon.com/lambda/latest/dg/with-s3-example-deployment-pkg.html) 

`[Stream yields empty buffer]`を調べて行き着いたのがこれら
- [AWS Developer Forums: Stream yields empty buffer error when ...](https://forums.aws.amazon.com/thread.jspa?messageID=609872)
- [node.js - GraphicsMagick: toBuffer() Stream yields empty buffer](http://stackoverflow.com/questions/35433249/graphicsmagick-tobuffer-stream-yields-empty-buffer)
- [node.js - gm Error in aws lambda: string yields empty buffer](http://stackoverflow.com/questions/37757132/gm-error-in-aws-lambda-string-yields-empty-buffer)
- [node.js - Got Error: Stream yields empty buffer when resizing images](http://stackoverflow.com/questions/38084463/got-error-stream-yields-empty-buffer-when-resizing-images-from-s3-with-local-no)

で、結局以下のように自作した。

~~~javascript:index.js
var AWS = require('aws-sdk');
var gm = require('gm').subClass({ imageMagick: true });
var util = require('util');
var s3 = new AWS.S3();

exports.handler = function(event, context, callback) {

  var bucket = event.Records[0].s3.bucket.name; // 変換元画像のバケット名
  var key = event.Records[0].s3.object.key;     // 変換元画像のキー名
  var thumbnailBucket = bucket + "-thumbnail";  // 変換後画像のバケット名(ここでは末尾にthumbnailを付け足すことにしてある)
  var thumbnailKey = key;                       // 変換後画像のキー名(ここでは変換前と同じにしてある)

  // 拡張子を抽出
  var typeMatch = key.match(/\.([^.]*)$/);
  if (!typeMatch) {
    callback("Could not determine the image type.");
    return;
  }

  // 拡張子がjpg, jpeg, pngのものだけ許可する
  var imageType = typeMatch[1];
  if (imageType != "jpg" && imageType != "jpeg" && imageType != "png") {
    callback('Unsupported image type: ${imageType}');
    return;
  }

  // S3の画像にアクセス
  s3.getObject({Bucket: bucket, Key: key}, function(err, data){
    if (err) { context.done('error getting object', err); }
       
    // 画像処理 (http://qiita.com/komakomako/items/a33ff4e610e378d986ff と同じ)
    gm(data.Body)
    .resize(null, '350')
    .borderColor('black')
    .border('245', '245')
    .gravity('Center')
    .crop('490', '350')
    .stream(function(err,stdout,stderr){ // ストリームで出力する
      if(err){
        console.log("gm process error");
        console.log(err,stdout,stderr);
        context.fail(err);
      }
      var chunks = []; // ストリームのチャンクを溜め込むための配列
      stdout.on('data',function(chunk){
        console.log('pushed');
        chunks.push(chunk); // チャンクを溜め込む
      });
      stdout.on('end',function(){
        console.log('end');
        var buffer = Buffer.concat(chunks); // 最後まで溜め込んだら結合してバッファに書き出す
        var params = {
          Bucket: thumbnailBucket,
          Key: thumbnailKey,
          ContentType: imageType,
          Body: buffer
        };
        s3.putObject(params, function(err, data) { // S3に書き出す
          if(err){
            console.log("gm upload error");
            context.fail(err);
          }
          context.succeed({
            "error":false
          });
        });
      });

      stderr.on('data',function(data){
        console.log('stderr data: ' +  data);
      });
    });
  });
};
~~~


## Lambdaへのデプロイの仕方

外部ライブラリ`gm`を使用しているので、上記の`index.js`と同じディレクトリで以下のコマンドを打ってこれらをインストール

~~~sh
npm install gm
~~~


すると、以下のようなディレクトリ構造になる。

~~~
- hogehoge/
    - index.js
    - node_modules/
~~~

次に`index.js`と同じディレクトリで以下のコマンドを打ってzipを作る。

~~~
zip a.zip *
~~~

すると、以下のようなディレクトリ構造になる。

~~~
- hogehoge/
    - a.zip
    - index.js
    - node_modules/
~~~

この`a.zip`をLambdaのブラウザコンソールからアップロードすればおｋ