背景
iphone のカメラで保存された画像はデフォで heic という形式になっており、web ブラウザは現状解釈してくれない。そのため、他のjpgなどの画像と同じように扱うためにはサーバ側で加工する必要がある。1 今回は、サーバ側でどうにかする手段をやってみたので残しとこうと思う。
内容
結論
lambda に imagemagick を仕込んだ lambda layer を連携させて、imagemagick を叩けばok
実装方法
lambda の runtime は node v12
lambda の管理には serverless framework
lambda layer はこのリポジトリのものを使う。ただ、 heic を扱うためのライブラリが入っていないので同リポジトリに上がっていたPRからビルド手段を持ってきました。imagemagick のビルドフラグで heic を追加したり、必要なモジュールである、libheif, libde265 をビルドできるようにしている。
LIBPNG_VERSION ?= 1.6.37
LIBJPG_VERSION ?= 9c
OPENJP2_VERSION ?= 2.3.1
LIBTIFF_VERSION ?= 4.0.9
BZIP2_VERSION ?= 1.0.6
LIBWEBP_VERSION ?= 0.6.1
IMAGEMAGICK_VERSION ?= 7.0.8-68
LIBHEIF_VERSION ?= 1.6.0
LIBDE265_VERSION ?= 1.0.3
TARGET_DIR ?= /opt/
PROJECT_ROOT = $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
CACHE_DIR=$(PROJECT_ROOT)build/cache
.ONESHELL:
CONFIGURE = PKG_CONFIG_PATH=$(CACHE_DIR)/lib/pkgconfig \
./configure \
CPPFLAGS=-I$(CACHE_DIR)/include \
LDFLAGS=-L$(CACHE_DIR)/lib \
--disable-dependency-tracking \
--disable-shared \
--enable-static \
--prefix=$(CACHE_DIR)
## libde265
LIBDE265_SOURCE=libde265-$(LIBDE265_VERSION).tar.gz
$(LIBDE265_SOURCE):
curl -LO https://github.com/strukturag/libde265/releases/download/v$(LIBDE265_VERSION)/$(LIBDE265_SOURCE)
$(CACHE_DIR)/lib/libde265.a: $(LIBDE265_SOURCE)
tar xf $<
cd libde265*
./autogen.sh
$(CONFIGURE)
make
make install
## libheic
LIBHEIF_SOURCE=libheif-$(LIBHEIF_VERSION).tar.gz
$(LIBHEIF_SOURCE):
curl -LO https://github.com/strukturag/libheif/releases/download/v$(LIBHEIF_VERSION)/$(LIBHEIF_SOURCE)
$(CACHE_DIR)/lib/libheif.a: $(LIBHEIF_SOURCE)
tar xf $<
cd libheif*
./autogen.sh
$(CONFIGURE)
make
make install
## libjpg
LIBJPG_SOURCE=jpegsrc.v$(LIBJPG_VERSION).tar.gz
$(LIBJPG_SOURCE):
curl -LO http://ijg.org/files/$(LIBJPG_SOURCE)
$(CACHE_DIR)/lib/libjpeg.a: $(LIBJPG_SOURCE)
tar xf $<
cd jpeg*
$(CONFIGURE)
make
make install
## libpng
LIBPNG_SOURCE=libpng-$(LIBPNG_VERSION).tar.xz
$(LIBPNG_SOURCE):
curl -LO http://prdownloads.sourceforge.net/libpng/$(LIBPNG_SOURCE)
$(CACHE_DIR)/lib/libpng.a: $(LIBPNG_SOURCE)
tar xf $<
cd libpng*
$(CONFIGURE)
make
make install
# libbz2
BZIP2_SOURCE=bzip2-$(BZIP2_VERSION).tar.gz
$(BZIP2_SOURCE):
curl -LO http://prdownloads.sourceforge.net/bzip2/bzip2-$(BZIP2_VERSION).tar.gz
$(CACHE_DIR)/lib/libbz2.a: $(BZIP2_SOURCE)
tar xf $<
cd bzip2-*
make libbz2.a
make install PREFIX=$(CACHE_DIR)
# libtiff
LIBTIFF_SOURCE=tiff-$(LIBTIFF_VERSION).tar.gz
$(LIBTIFF_SOURCE):
curl -LO http://download.osgeo.org/libtiff/$(LIBTIFF_SOURCE)
$(CACHE_DIR)/lib/libtiff.a: $(LIBTIFF_SOURCE) $(CACHE_DIR)/lib/libjpeg.a
tar xf $<
cd tiff-*
$(CONFIGURE)
make
make install
# libwebp
LIBWEBP_SOURCE=libwebp-$(LIBWEBP_VERSION).tar.gz
$(LIBWEBP_SOURCE):
curl -L https://github.com/webmproject/libwebp/archive/v$(LIBWEBP_VERSION).tar.gz -o $(LIBWEBP_SOURCE)
$(CACHE_DIR)/lib/libwebp.a: $(LIBWEBP_SOURCE)
tar xf $<
cd libwebp-*
sh autogen.sh
$(CONFIGURE)
make
make install
## libopenjp2
OPENJP2_SOURCE=openjp2-$(OPENJP2_VERSION).tar.gz
$(OPENJP2_SOURCE):
curl -L https://github.com/uclouvain/openjpeg/archive/v$(OPENJP2_VERSION).tar.gz -o $(OPENJP2_SOURCE)
$(CACHE_DIR)/lib/libopenjp2.a: $(OPENJP2_SOURCE) $(CACHE_DIR)/lib/libpng.a $(CACHE_DIR)/lib/libtiff.a
tar xf $<
cd openjpeg-*
mkdir -p build
cd build
PKG_CONFIG_PATH=$(CACHE_DIR)/lib/pkgconfig cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=$(CACHE_DIR) \
-DBUILD_SHARED_LIBS:bool=off \
-DBUILD_CODEC:bool=off
make clean
make install
## ImageMagick
IMAGE_MAGICK_SOURCE=ImageMagick-$(IMAGEMAGICK_VERSION).tar.gz
$(IMAGE_MAGICK_SOURCE):
curl -L https://github.com/ImageMagick/ImageMagick/archive/$(IMAGEMAGICK_VERSION).tar.gz -o $(IMAGE_MAGICK_SOURCE)
LIBS:=$(CACHE_DIR)/lib/libjpeg.a \
$(CACHE_DIR)/lib/libpng.a \
$(CACHE_DIR)/lib/libopenjp2.a \
$(CACHE_DIR)/lib/libtiff.a \
$(CACHE_DIR)/lib/libbz2.a \
$(CACHE_DIR)/lib/libwebp.a \
$(CACHE_DIR)/lib/libde265.a \
$(CACHE_DIR)/lib/libheif.a \
$(CACHE_DIR)/lib/libwebp.a
$(TARGET_DIR)/bin/identify: $(IMAGE_MAGICK_SOURCE) $(LIBS)
tar xf $<
cd ImageMa*
PKG_CONFIG_PATH=$(CACHE_DIR)/lib/pkgconfig \
./configure \
CPPFLAGS=-I$(CACHE_DIR)/include \
LDFLAGS="-L$(CACHE_DIR)/lib -lstdc++" \
--disable-dependency-tracking \
--disable-shared \
--enable-static \
--with-heic=yes \
--prefix=$(TARGET_DIR) \
--enable-delegate-build \
--without-modules \
--disable-docs \
--without-magick-plus-plus \
--without-perl \
--without-x \
--disable-openmp
make clean
make all
make install
libs: $(LIBS)
all: $(TARGET_DIR)/bin/identify
リポジトリ内にあるMakefileをみてもらえればわかるが、これらのビルド処理はlambda 実行マシンに近いdocker image 内で実行されるのでlinux 環境は必要ない。
make all
ですべてが解決する。lambda layer を登録したらその arn
を serverless.yml に追記して変更を反映する。
functions:
preview-img:
runtime: nodejs12.x
handler: bin/index.preview
memorySize: 1536
layers:
- arn:aws:lambda:ap-northeast-1:xxxxxxxxx:layer:image-magick:4
events:
- http:
path: preview
method: post
cors:
origin: "*"
maxAge: 86400
api gateway も一応画像の content-type を絞っておく
provider:
#
#
#
apiGateway:
minimumCompressionSize: 0 # gzip で返却できるように
binaryMediaTypes:
- image/png
- image/jpeg
- image/jpg
- image/heic
ここまで設定が終わればあとは処理を書くだけ。node は不慣れなので書き方が雑ですがこんな風にやった。
lambda の中でファイル書き込みを行いときは /tmp
に書くというのがお作法のようでそれに習っている。
const { execSync } = require('child_process');
const fs = require('fs');
module.exports.preview = (event, context, callback) => {
buf = new Buffer(event.body, 'base64')
heicName = '/tmp/' + context.awsRequestId + '.heic'
jpgName = '/tmp/' + context.awsRequestId + '.jpg'
console.time('writeFileSync');
fs.writeFileSync(heicName, buf, function (err) {
console.log(`err: ${err}`);
if (err) throw err;
});
console.timeEnd('writeFileSync');
cmd = '/opt/bin/convert' + ' ' + heicName + ' ' + jpgName
console.log(`cmd: ${cmd}`);
console.time('convert');
try {
stdout = execSync(cmd);
console.log(`cmd stdout: ${stdout.toString()}`)
} catch (err) {
console.log(`cmd err: ${err}`)
}
console.timeEnd('convert');
stdout = execSync('ls -la /tmp')
console.log(`ls -la: ${stdout.toString()}`)
// return response
};
その他学んだこと
ほんとは go でやりたかったけど、lambda で使われるマシンが Amazon Linux2 ではないのでダメだった。node10以降, python3 系は対応している。
他にも便利そうなlambda layer は awesome にまとまってる。ffmpeg とかもある。
memorySize: 1536 を振っていても、2~3 MB くらいの画像だと1.5秒かかるのでもうちょい早くならんのかなぁというところ
免責
記事内容の運用により派生した損害を含むあらゆる結果については一切の責任を持ちません。