こんにちはクアッド株式会社のsegawaです。
Quad inc Advent Calendar 2017の1日目の記事です。
WebPとは
WebP(ウェッピー)はGoogleの開発した画像フォーマットで、PNGやJPGよりも25%ほど画像サイズを小さくできるとうたわれています。
実際には quality=80
だとJPGの元画像と比較して半分以下までサイズを減らすことができました。
PCでの対応ブラウザはChromeとOperaのみですが、macのChrome/Safari/FireFox、WindowsもIE11/Firefoxで表示は可能でした。
プロジェクトではiPhoneアプリとAdobe AIRアプリに表示するために利用しました。
PythonでWebPに変換する
公式で提供している cwebp をOSコマンド実行します。
PillowやImageMagickでの変換を試してみましたが、私の環境では上手くいかず時間の関係でこちらを採用しました。
cewbpのダウンロード
上記ページ内の「downloads repository」のリンクからダウンロードページに飛ぶので
libwebp-0.6.0-linux-x86-64.tar.gz
をダウンロードして解凍します。
※Lambdaの実行環境はAmazon Linuxです
cwebpの使い方
解凍してできたbinディレクトリのなかにエンコーダがあります。
またlibフォルダの中にある libwebp.a
も使います。
cwebpはJPG/PNG/TIFFに対応しています。
cwebp [options] input_file -o output_file.webp
なお、gif画像の場合はgif2webpを利用します。
gif2webp [options] input_file.gif -o output_file.webp
サンプル
quality を 80 にしています。
これ以下だと、画像が荒れて見えました。
def convert_to_webp(infile):
filename, ext = os.path.splitext(infile)
quality = 80
out = filename + ".webp"
cmd = "cwebp -quiet -q {} {} -o {}".format(quality, infile, out)
try:
result = subprocess.check_call(cmd, shell=True)
print('convert result => ' + str(result))
except Exception as e:
print(e)
return out
AWS Lambdaに実装
libにパスを通しておく
Lambdaでcwebpを実行するにはパスを通す必要があるため、以下のようにパスを設定しておきます。
import os
os.environ["PATH"] = os.environ.get("PATH") + ':' + os.path.join(os.path.abspath(os.path.dirname(__file__)), 'lib')
os.environ["LD_LIBRARY_PATH"] = os.environ.get("LD_LIBRARY_PATH") + ':' + os.path.join(
os.path.abspath(os.path.dirname(__file__)), 'lib')
zipファイルの作成
Lambdaで cwebp を実行するには、Pythonプログラムと共にエンコーダをzipでまとめてアップロードする必要があります。
/webp_converter.zip
└webp_converter.py
├/lib
├cwebp
└libwebp.a
あわせて使用するPythonライブラリも含める必要があるので、zip圧縮するディレクトリ内にインストールしておきます。
今回は requests
を利用しています。
pip install requests -q -t .
Lambdaにzipをアップロード
Lambdaへの反映方法はいくつかありますが、ここでは作成したzipファイルをAWSCLIでアップロードします。
# aws lambda create-function \
--function-name webp_converter \
--runtime python3.6 \
--role arn:aws:iam::************:role/lambda_basic_execution \
--handler webp_converter.lambda_handler \
--timeout 60 \
--memory-size 128 \
--publish \
--environment Variables={ENV=staging} \
--zip-file <zipファイルのパス>
コードサンプル
以下のコードはパラメータで受け取ったURLの画像をダウンロードし、Lambda上で変換、S3に保存するサンプルです。
画像フォーマットの判定等が抜けているので非対応の画像を渡すとエラーになります。
# -*- coding: utf_8 -*-
import os
import requests
from boto3.session import Session
os.environ["PATH"] = os.environ.get("PATH") + ':' + os.path.join(os.path.abspath(os.path.dirname(__file__)), 'lib')
os.environ["LD_LIBRARY_PATH"] = os.environ.get("LD_LIBRARY_PATH") + ':' + os.path.join(
os.path.abspath(os.path.dirname(__file__)), 'lib')
s3_client = None
def lambda_handler(event, context):
global s3_client
if not s3_client:
session = Session(
aws_access_key_id="*********", # S3のアクセスキー
aws_secret_access_key="**************", # S3のシークレット
region_name="********" #S3のリージョン
)
s3_client = session.resource('s3')
try:
url = event['url']
s3_path = event['s3_path']
# 画像ダウンロード
save_path = download_img(url)
# ダウンロードした画像WebPに変換
webp_path = convert_to_webp(save_path)
# S3に保存
save_to_s3(webp_path, s3_path)
except Exception as e:
print(e)
raise e
else:
print('SUCCESS')
return {"result": "OK"}
def download_img(url):
save_path = "/tmp/" + url.split("/")[-1]
r = requests.get(url, stream=True)
if r.status_code == 200:
with open(save_path, 'wb') as file:
for chunk in r.iter_content(chunk_size=1024):
file.write(chunk)
return save_path
def convert_to_webp(infile):
filename, ext = os.path.splitext(infile)
quality = 80
out = filename + ".webp"
cmd = "cwebp -quiet -q {} {} -o {}".format(quality, infile, out)
try:
result = subprocess.check_call(cmd, shell=True)
print('convert result => ' + str(result))
except Exception as e:
print(e)
return out
def save_to_s3(webp_path, s3_path):
if not os.path.exists(webp_path):
print('file is NOT exists : ' + webp_path)
save_file_name = s3_path + '/' + webp_path.split("/")[-1]
data = open(webp_path, 'rb')
s3_client.meta.client.put_object(
ACL='public-read',
Body=data,
Bucket="**********", # S3のバケット
Key=save_file_name,
ContentType='image/webp'
)
def remove_file(original_path, webp_path):
os.remove(original_path)
os.remove(webp_path)
if __name__ == '__main__':
lambda_handler({}, {})
1処理あたり数秒で終わるので、大量に画像を変換するのにLambdaは便利ですね😃