見出し画像

Cloud Functionsで画像をリサイズする

今回は画像のリサイズ処理について書きます。

ユーザーが画像を投稿する場合、かなりでかいサイズの画像が保存されてしまうので、そのせいでUXが悪くなる可能性があるので、Cloud Functionsでリサイズしてしまいましょう。

準備

画像のリサイズ処理については、Firebaseが公式にサンプルを出しています。

ですが、このサンプルでは素のJavaScriptでかかれていて個人的に、TypeScriptで書きたいので今回はTypeScriptで書きます。

また、このサンプルコードでは、画像のリサイズライブラリは「ImageMagick」を使っています。ですが、いろいろ記事を見ているとImageMagickよりSharpの方が良さそうだったので、今回はSharpを使います。

functionsディレクトリに移動して、ライブラリを導入します。

まずはFirebase Storageを導入

npm i @google-cloud/storage

次に、Sharpを導入

npm i sharp

package.jsonが下記になっていることを確認します。バージョンに関しては適宜変更してください。

{
  "name": "functions",
  "scripts": {
    "lint": "tslint --project tsconfig.json",
    "build": "tsc",
    "serve": "npm run build && firebase serve --only functions",
    "shell": "npm run build && firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
  },
  "main": "lib/index.js",
  "dependencies": {
    "@google-cloud/storage": "^1.5.1",
    "child-process-promise": "^2.2.0",
    "mkdirp": "^0.5.1",
    "mkdirp-promise": "^4.0.0",
    "firebase-admin": "~6.0.0",
    "firebase-functions": "^2.0.3",
    "sharp": "^0.18.4"
  },
  "devDependencies": {
    "@types/sharp": "^0.17.6",
    "tslint": "~5.8.0",
    "typescript": "~2.8.3"
  },
  "private": true
}

実装

次に実装です。今回はFirebase Storageに画像が保存されたのをトリガーにしています。

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as Storage from '@google-cloud/storage';
import * as sharp from 'sharp';
import * as path from 'path';
import * as os from 'os';

const THUMB_MAX_WIDTH = 200;
const THUMB_MAX_HEIGHT = 200;

const THUMB_PREFIX = 'thumb_';

// 初期設定
admin.initializeApp(functions.config().firebase);
const firestore = admin.firestore();

// sharpで最大幅のみを指定してリサイズ
const resizeImage = (tmpFilePath: string, destFilePath: string, width: number, height: number): Promise<any> => {
  return new Promise((resolve, reject) => {
    sharp(tmpFilePath)
      .resize(width, height)
      .toFile(destFilePath, (err, _) => {
        if (!err) {
          resolve();
        } else {
          reject(err);
        }
      });
  });
};

// 画像のリサイズ処理
export const thumbnailImage = functions.storage.object().onFinalize((object) => {
  // ファイルパスの取得
  const filePath = object.name;
  // ContentTypeの取得
  const contentType = object.contentType;
  // ディレクトを取得
  const fileDir = path.dirname(filePath);
  // ファイル名を取得する
  const fileName = path.basename(filePath);

  // 画像以外だったらなにもしない
  if (!contentType.startsWith('image/')) {
    console.log("これは画像ではありません");
    return;
  }

  // すでにリサイズ済みだったら何もしない
  if (fileName.startsWith(THUMB_PREFIX)) {
    console.log("すでにリサイズ済みです");
    return;
  }

  const storage = new Storage();
  const bucket = storage.bucket(object.bucket);
  const file = bucket.file(filePath);
  const metadata = { contentType: contentType };

  // 一時ディレクトリ
  const tempLocalFile = path.join(os.tmpdir(), filePath.split('/').pop());
  // リサイズ後の一時ファイル場所
  const thumbFilePath = path.normalize(path.join(fileDir, `${THUMB_PREFIX}${fileName}`));
  const tempLocalThumbFile = path.join(os.tmpdir(), thumbFilePath.split('/').pop());

  (async () => {
    console.log("一時ローカルファイルは:" + tempLocalFile);
    console.log("ディレクトリは" + fileDir);
    // // 一時ディレクトに保存する
    await file.download({destination: tempLocalFile});
    console.log("一時リサイズファイルは:" + tempLocalThumbFile);
    // 画像をリサイズする
    await resizeImage(tempLocalFile, tempLocalThumbFile, THUMB_MAX_WIDTH, THUMB_MAX_HEIGHT);
    console.log("保存先のバケットは" + path.join(fileDir, `${THUMB_PREFIX}${fileName}`));
    // リサイズされたサムネイルをバケットにアップロード
    await bucket.upload(tempLocalThumbFile, { destination: path.join(fileDir, `${THUMB_PREFIX}${fileName}`), metadata: metadata });
  })()
  .then(() => console.log("リサイズに成功しました"))
  .catch(err => console.log("エラーが発生しました" + err));

});

これをデプロイすると、リサイズされた画像がFirebase Storageにアップロードされます。

投げ銭はいりません。それより無料でできる拡散をしてください!! 感想をツイートしていただけることが一番嬉しいです!!