Node.jsでHEICを他のフォーマットに変換する方法
Node.jsでの画像編集
Node.jsにおいて画像処理を行うのにメジャーなライブラリはおそらくSharpになるかと思います。
このSharpですが、PNG,JPG、GIFなど多数のフォーマットをサポートしており非常に便利です。リサイズ、回転、色反転、フォーマット変換など非常に多くの機能を兼ね備えています。多くの画像関連の要件はこのモジュールで満たせると思います。
HEIC
HEICはiPhoneなどで使用されているフォーマットで圧縮効率がいいことが特徴のフォーマットになります。iPhoneで使用されているため、みなさんも意識せずに使用しているかと思います。
しかし、このHEICは以下の問題があります。
- 比較的新しいフォーマットであることと特許の問題もあり、PCやスマホのソフトが対対応していない場合がある。*特許の問題についてはこちらのリンクを参照
- 上記の問題でSharpもnpm installをただ行うだけではHEIC形式を扱うことができません。
- Safari以外では基本的にそのまま表示させることもできません。CanIUseを参考に
なので、HEIC含めて画像編集を行いたい、または画像をユーザーにアップロードさせてそのまま表示させたいなどの要件の際にはHEICへの対処が必要です。
回避策
実はSafariから画像をアップロードする場合には、inputのacceptをaccept="image/jpg"
などと指定すると自動で変換がかかります。iPhoneの場合、Chromeなどでも実態はSafariなので同じ挙動です。
なお、変換はブラウザで行われるようなので変換前の情報を取得することができません。
どうしてもHEICを受け付けないといけないとき
上記の回避策では不十分な場合、HEICを変換する方法は以下があります。
ブラウザで行いたい場合
heic2any
このライブラリはweb workerでHEICフォーマットを他のフォーマットに変換をしてくれるライブラリになります。
私の環境起因かもしれないですが5メガ程度のファイルは変換できましたが10メガ近いファイルだとブラウザでエラーが表示されました。(メモリ使用量がすごい。Issueをみるとメモリリークが発生するが対応されていない?)
サーバーで行いたいとき
heic-convert
HEICの変換が可能。10メガ程度のファイルを変換したところ、メモリの使用量がrss1.5Gb程度となり、変換の時間もM2 Macで15秒ほどかかった。Lambdaでメモリを最大(CPUはメモリに合わせて変わる)にしても40秒以上かかった。
サーバー側でより効率的に変換を行いたいとき
上記の結果でheic-convertではちょっと要件的に実用に耐えないため、一番最初に言及したSharpで試してみることにしました。
まずはローカルでHEICを変換できるようにするには以下のようにしてください。
- 変換に必要なソフトウェアをインストールします
brew install vips libheif libde256 x265
- sharpのインストール
インストール時に再度libvipsをビルドすることでMacに入っているlibheifなどを使用できます。
*通常のnpm install sharpは事前にビルド済みのlibvipsなどをダウンロードして使用します。npm install --build-from-source node-addon-api node-gyp sharp
これでローカルのMacでHEICの変換ができるようになるはずです。
早速コードで試してみましょう。メモリ効率のいいstreamを使用してみます。
import fs from 'node:fs';
import { pipeline } from 'node:stream';
import { promisify } from 'node:util';
import sharp from 'sharp';
const HEICToJPG = async () => {
const fileStream = fs.createReadStream('./test.heic');
// defaultは80
const transform = sharp().jpeg({ quality: 100 });
const writeStream = fs.createWriteStream('./test.jpg');
const asyncPipeline = promisify(pipeline);
await asyncPipeline(fileStream, transform, writeStream);
};
環境によって違いはあるとは思いますが、私の環境ではM2 Macで10メガ程度のHEICで3~4秒ほどで、十分実用に耐えるスピード感に耐える環境でした。
サーバーで動かす際も事前にbrew installでinstallしたソフトをinstallすることで対応可能です。
Lambdaで動かしたい場合
Lambdaで動かしたい場合では、以下を使うと非常に簡単にHEICに対応したSharpを含んだLambda Layerを構築できます。
使い方は以下です。- cloneする
- sample-buildproject.yamlを実行すると、codebuildが作成される
- codebuildを実行するとLambda Layerが構築される
- 構築されたLambda LayerをLambdaで紐づける
Lambdaのアップロード用のファイルを作成するときに以下のようにesbuildを使うと便利です。
esbuild index.ts --bundle --outfile=index.js --platform=node --external:sharp
こうすることで、Lambdaにアップロードするファイルを生成でき、さらにバンドルからsharpを除外しているのでLambda Layerを見に行きます。
Discussion