この記事は羅針盤 アドベントカレンダー 2024の6日目の記事です。
qiita.com
5日目の記事は クリエイティブ職向け macOS 個人的おすすめツール 8つ - 羅針盤 技術航海日誌 でした。
こんにちは、羅針盤の森川です。
最近のマイブームはマッシュルームです。生のままスライスしてオリーブオイルと黒胡椒を振るだけでワインが美味しく飲めます。
マッシュルームは安くていい感じなんですが、オリーブオイルが高騰しすぎて泣きそうです。
2024年色んなことがありましたね(2回目)。
気づいたら世界の97%がWebP対応していました。うぇっぴー!
PageSpeed Insights でも指摘されるので、SEOが重要なWebサイトの画像はもうJPGはやめてWebPにすることにしました。
少量の画像変換だったらGoogleのSquooshでいいんですが、毎回何枚、何十枚となるとなかなか面倒です。
webP化
cwebp
コマンドを使うとJPEG、PNGをWebPに変換できます。
cwebp -q 80 image.png -o image.webp
これを使って一括変換すればいいんですが、「これ使ってください、お願いします〜♪」って渡された画像ファイルちゃん達にはJPEGだけでなく、たまにHEICが混じってたり、縦横比がおかしかったり、文字化けしていたりとなんだか不穏です。 文字化けはもはやどうしようもないですが、他を解決するためにはどうしたらいいでしょうか?
tl;dr こちらを使ってください(ただしmac用)
#!/bin/bash ######################################################################## # 概要: # 指定ディレクトリ内の画像ファイルをWebP形式に変換し、リサイズするスクリプト ######################################################################## ### 設定値: 環境変数で指定する ### # 入力元ディレクトリ WEBP_INPUT_DIR=${WEBP_INPUT_DIR-'.'} # 出力先ディレクトリ・プリフィクス WEBP_OUTPUT_DIR=${WEBP_OUTPUT_DIR-'output/'} # 出力サイズ: 横幅 WEBP_TARGET_WIDTH=${WEBP_TARGET_WIDTH-500} # 出力サイズ: 正方形にするか (0: しない, 1: する) WEBP_TARGET_SQUARE=${WEBP_TARGET_SQUARE-0} # 一時ディレクトリ(基本的には変更不要) WEBP_TEMP_DIR=${WEBP_TEMP_DIR-'/tmp/webp'} # ============================================== # メイン処理 function run { check_dependency copy_temp_to_square convert_webp } # 依存ツールの確認 function check_dependency { type cwebp >/dev/null 2>&1 || { echo >&2 "[error] 'cwebp' not installed. (e.g. 'brew install webp')"; exit 1; } type identify >/dev/null 2>&1 || { echo >&2 "[error] 'imagemagick' not installed. (e.g. 'brew install imagemagick')"; exit 1; } } # 正方形化と一時ディレクトリへのコピー function copy_temp_to_square { mkdir -p ${WEBP_TEMP_DIR} for img in `find_image ${WEBP_INPUT_DIR}`; do local width=(`identify -format '%w' $img`) local height=(`identify -format '%h' $img`) local file_name=$(basename $img) # 拡張子有り local file_name_base=${file_name%.*} # 拡張子抜き local file_ext=${img##*.} # 拡張子のみ # 小文字に統一 file_ext=`echo $file_ext | tr '[:upper:]' '[:lower:]'` if [ ${width} -eq ${height} -o "${WEBP_TARGET_SQUARE}" = "0" ]; then if [ "${file_ext}" = "heic" ]; then # HEICの場合はjpgに変換 echo "[info] convert HEIC: ${file_name}" convert $img ${WEBP_TEMP_DIR}/${file_name_base}.jpg else # 既に正方形・または正方形化指定なしの場合はそのままファイルコピー cp $img ${WEBP_TEMP_DIR}/${file_name} fi continue fi # 正方形化 local crop_size=${width} if [ ${width} -gt ${height} ]; then # heightに合わせてcrop crop_size=${height} fi local output_file_name=${file_name} if [ "${file_ext}" = "heic" ]; then # HEICの場合はjpgに変換 output_file_name=${file_name_base}.jpg fi echo "[info] crop: ${file_name}" convert $img -gravity center -crop ${crop_size}x${crop_size}+0+0 ${WEBP_TEMP_DIR}/${output_file_name} done } # WebP変換 function convert_webp { mkdir -p ${WEBP_OUTPUT_DIR} for img in `find_image ${WEBP_TEMP_DIR}`; do echo "[info] convert: ${img}" local width=(`identify -format '%w' $img`) if [ ${width} -gt ${WEBP_TARGET_WIDTH} ]; then local resize_opt="-resize ${WEBP_TARGET_WIDTH} 0" fi cwebp -metadata icc -sharp_yuv -q 80 $img ${resize_opt} -o ${WEBP_OUTPUT_DIR}$(basename ${img%.*}).webp > /dev/null 2>&1 rm -f $img done } # 指定ディレクトリから画像ファイルを検索 function find_image { local dir=${1-'.'} find -E ${dir} -type f -iregex ".*\.(png|jpg|jpeg|gif|heic)" } # 実行 run
とりえあず webp(cwebp
) と imagemagick (convert
と identify
) が入っていればなんとか使えると思います。
簡単に解説しておくと、copy_temp_to_square
で画像を一時ディレクトリに保存します。正方形化が必要な場合は正方形にするし、HEICの場合はJPEGに変換します。
一時ディレクトリに保存された補正済みのJPEG/PNGファイルを convert_webp
で、対象のディレクトリにWebP化して保存しています。
プルリクエストでチェック
画像専用のリポジトリを用意していて、そこでプルリクエスト出してもらってチェックしています。 とりあえずファイルサイズが大きいものが無いか、GitHub Actionsで機械的に判定します。
name: Large File Warning on: pull_request: types: [opened, synchronize] jobs: lfs_warning: runs-on: ubuntu-latest steps: - name: Checkout Repository uses: actions/checkout@v4 - name: LFS Warning uses: ppremk/lfs-warning@v3.2 with: # accepts 'b', 'mb', 'gb' only filesizelimit: '307200b' # 300kb labelName: 'warning/large-file' labelColor: 'ff69b4'
ここでは300KBにしていますが、画面全体に広がる画像等ではファイルサイズが超えることもあるのでそこは仕方ないです。 トリミング漏れやWebP漏れが無いか自分で気付きやすいようにするためのワークフローです。
なお、WebPはGitHubで見づらいので後日Chrome Extensionを紹介します。
GCSへrsync
GCSへのファイル同期もGitHub Actionsを使います。
まずはステージングと本番共通の reuseable workflow から説明します。
# _common-rsync-static.yml name: Rsync data to GCS (reuseable workflow) on: workflow_call: inputs: TARGET_DIR: required: true type: string ENV_NAME: required: true type: string ENV_URL: required: true type: string secrets: github-token: required: true GCP_PROJECT_ID: required: true GCP_PROJECT_NUMBER: required: true jobs: rsync_gcs: runs-on: ubuntu-latest timeout-minutes: 8 permissions: id-token: write contents: read deployments: write steps: - name: Checkout Repository uses: actions/checkout@v4 # ※1 - name: Check if on main branch for prod dir if: ${{ inputs.TARGET_DIR == 'prod' && github.ref != 'refs/heads/main' }} run: | echo "'prod' dir can only run on the main branch." exit 1 # ※2 - name: Create deployment on GitHub uses: chrnorm/deployment-action@releases/v2 id: deployment with: token: "${{ secrets.GITHUB_TOKEN }}" environment: ${{ inputs.ENV_NAME }} environment-url: ${{ inputs.ENV_URL }} initial-status: "in_progress" # ※3 - id: "auth" uses: "google-github-actions/auth@v2" with: workload_identity_provider: projects/${{ secrets.GCP_PROJECT_NUMBER }}/locations/global/workloadIdentityPools/github-actions-pool/providers/github-actions-provider service_account: image-deploy-github-actions@${{ secrets.GCP_PROJECT_ID }}.iam.gserviceaccount.com project_id: ${{ secrets.GCP_PROJECT_ID }} # ※3 - name: "Set up Cloud SDK" uses: "google-github-actions/setup-gcloud@v2" with: project_id: ${{ secrets.GCP_PROJECT_ID }} # ※4 # 先頭が . 以外のファイル・ディレクトリを全てアップロード(差分は削除される) - name: Rsync to GCS run: | gsutil -m rsync -x '(^|/)\.' -r -d ./static.example.com/image gs://static.example.com/${{ inputs.TARGET_DIR }} # ※2 - name: Update deployment status (success) if: success() uses: chrnorm/deployment-status@v2 with: token: "${{ secrets.GITHUB_TOKEN }}" state: 'success' deployment-id: ${{ steps.deployment.outputs.deployment_id }} # ※2 - name: Update deployment status (failure) if: failure() uses: chrnorm/deployment-status@v2 with: token: "${{ secrets.GITHUB_TOKEN }}" state: 'failure' deployment-id: ${{ steps.deployment.outputs.deployment_id }}
前提として、ここではステージングと本番とで同じGCSバケットを使っています。(同一ドメインにしたいので)
最初のプリフィクスで分けています(説明の分かりやすさのため /prod/
, /dev/
にしました)
- ※1 では本番のディレクトリ
/prod/
は mainブランチ のみで可能にしています。間違えて変なブランチを本番で使えないようにしています。 - ※2 ではGitHub Deploymentsのための設定です。無くてもいいですが、あると分かりやすいので便利。
- ※3 ではWorkload Identity でのGCP認証をしています。サービスアカウント名やプロバイダー名は固定にしていますが必要に応じて変えてください。
- ※4 では リポジトリ内の /static.example.com/image/ というディレクトリ配下にあるファイルを全てGCSへアップロードしています。バケット名(ドメイン)は適宜変えてください。
次に各環境のワークフローですが、ここでは開発・ステージング用のサンプルになります。 (本番も内容は一緒です。)
name: Rsync data to GCS (dev) on: workflow_dispatch: jobs: rsync_gcs_development: uses: <my-org-name>/<repo-name>/.github/workflows/_common-rsync-static.yml@main with: TARGET_DIR: dev ENV_NAME: development ENV_URL: https://console.cloud.google.com/storage/browser/static.example.com secrets: github-token: ${{ secrets.GITHUB_TOKEN }} GCP_PROJECT_ID: ${{ secrets.GCP_PROJECT_ID }} GCP_PROJECT_NUMBER: ${{ secrets.GCP_PROJECT_NUMBER }}
TARGET_DIR
は/prod/
か/dev/
が入るプリフィクスの部分ですENV_NAME
は GitHub Deployment で使う環境名ですENV_URL
も GitHub Deployment で使うだけなのできちんと設定しなくてもいいですsecrets.GCP_PROJECT_ID
とsecrets.GCP_PROJECT_NUMBER
はGCPのウェルカムページにあるプロジェクトIDと番号を入れてください
https://console.cloud.google.com/welcome
さて、今日はこれで寝ます。 素敵なWebP生活を。おやすみなさい。