S3にアップロードされたJPEG画像をLambdaでWebPへ変換する

# WebPとは
Googleが開発している画像フォーマットで、ウェッピーと読みます。
JPEGと比較してファイルサイズが25-34%小さくなり、採用することで表示の高速化が見込めます。
WebPに対応するプラットフォームは徐々に増加しています。

**参考リンク**

- <a href="https://developers.google.com/speed/webp/?hl=JA" target="_blank">WebP公式サイト</a>
- <a href="https://caniuse.com/#feat=webp" target="_blank">プラットフォーム別の対応状況</a>

# WebサイトのWebP対応方法

まだWebPに対応できていないプラットフォームが存在するため、JPEG(またはPNG)とWebPと両フォーマットの画像を用意する必要があります。
画像の使い分け方としては、HTMLタグで出し分ける方法と、HTTP RequestのAcceptヘッダの値に応じてサーバのレスポンスを変える方法があります。

**HTMLで出し分ける例**

```html
<picture>
  <source type="image/webp" src="A.webp">
  <img src="A.jpg">
</picture>
```

**サーバで出し分ける例**

![Qiita-Webp-Dashiwake.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13038/012a85a7-758a-1869-638a-052656c907d3.png)

# [実践]WebP画像を用意する方法

今回WebPの対応をする画像は、Amazon S3で管理しております。
画像はブラウザからサーバサイドアプリ経由でS3にアップロードされる仕組みになっています。

![Qiita-Webp-System-Kizon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13038/6408e17c-1208-b71c-a1de-382655d161d3.png)

既存の仕組みを変更せずWebP画像を用意したかったため、S3に画像がアップロードされるとAWS Lambdaで自動的に変換する仕組みを作ることにしました。

![Qiita-Webp-System-New.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13038/32e30781-f79c-4946-0b96-961bf3b45c41.png)

## Lambda functionの実装
### functionの作成
Management ConsoleでLambda functionを作成します。
Pythonで実装したかったので、RuntimeはPythonを選択しました。
![lambda-create-function.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13038/f0419e9d-bc4e-c44b-b2c0-c78a98062381.png)

### IAM Roleの設定
JPEGの画像がアップロードされるバケットの読み込み、WebPの画像をアップロードするバケットへの書き込みとそのオブジェクトのパーミッション変更ができる権限を設定します。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:PutObjectAcl"
            ],
            "Resource": "arn:aws:s3:::bucketName/path-to-image/*"
        }
    ]
}
```

### Codeの作成

S3のイベント情報から元画像をダウンロードし、変換したものをS3にアップロードする、という処理を記述します。

```python:lambda_function.py
from PIL import Image
import urllib.parse
import boto3
import os

s3 = boto3.client('s3')

def lambda_handler(event, context):
    bucket = event['Records'][0]['s3']['bucket']['name']
    source_key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
    destination_key = source_key + u'.webp'
    source_file = u'/tmp/' + os.path.basename(source_key)
    destination_file = source_file + u'.webp'

    try:
        s3.download_file(Bucket=bucket, Key=source_key, Filename=source_file)
        img = Image.open(source_file, 'r')
        img.save(destination_file, 'webp', quality = 80)
        s3.upload_file(Filename=destination_file,
                       Bucket=bucket,
                       Key=destination_key,
                       ExtraArgs={"ACL":"public-read","ContentType":"image/webp"})
        return source_key
    except Exception as e:
        print(e)
        raise e
```

### ライブラリのインストールとZip化
必要なライブラリ(webp)をインストールするため、lambda_function.py を作成したディレクトリで下記のコマンドを実行します。

```bash
pip install webp -t ./
```

ライブラリインストール後、作業中ディレクトリの内容は下記のようになります。

```bash
$ ls -1
PIL/
Pillow-6.0.0.dist-info/
_cffi_backend.cpython-36m-x86_64-linux-gnu.so*
_webp.abi3.so*
bin/
cffi/
cffi-1.12.3.dist-info/
lambda_function.py
numpy/
numpy-1.16.3.dist-info/
pycparser/
pycparser-2.19.dist-info/
webp/
webp-0.1.0a13.dist-info/
webp_build/
```

下記のコマンドを実行し、zipファイルを作成します。

```bash
zip -r convertJpegToWebp.zip ./*
```

### functionへの登録
Function codeパネルにてCode entry typeを`Upload a .zip file`とし、Function packageのUploadボタンを押してzipファイルをアップロードします。
![Screen Shot 0031-04-25 at 17.05.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13038/2a1b8848-b4de-16f5-dbfc-5799bec18ce0.png)

## S3にEvent Notificationを登録
変換元となる画像がアップロードされるS3バケットに、Lambdaを起動するEvent Notificationを登録します。
画像の登録をトリガーとしたいため、EventsにはAll object create eventsを選択し、対象とする画像のPrefix, SuffixをFilterに設定、TypeはLambdaを選択して作成したLambda functionを設定します。
![Screen Shot 0031-04-25 at 17.03.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13038/0703fbfb-e1d7-ecdd-e03e-062ac9359e5d.png)

以上で実装完了です。

元画像をアップロードした数秒後に、WebPの画像が作成されていることが確認できます。
<img width="1396" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13038/677a6438-0e74-5667-9147-bafd8e8b2880.png">


# まとめ
WebP対応するにあたり、未対応環境も考慮すると、WebPとJPEG/PNGの両方を用意しておくことは必須です。手作業で画像を用意しようとしたら大変ですが、自動化する仕組みを作ってしまえば手間無く用意できます。
ファイルサイズが2〜3割程度小さくなるので、画像の多いWebサイトであれば導入のメリットは大きいと思います。