Hori Blog

フリーランスでバックエンドエンジニアとして活動している Ryota Hori のブログです。ドメイン駆動設計や Go の話など。

Hugo のサムネ作成、画像追加、画像最適化を自動化するシェル芸

CMS ではなく Markdown で記事を作成していると画像周りがどうにもダルいのでスクリプト化しました。

自分用のオレオレ処理ですが、貼っておけば何かの役に立つかもしれないので残しておきます。

準備

画像の編集をするのに ImageMagick が必要です。

また、画像の最適化では

を使っています。それぞれ Mac であれば Homebrew で入れられるかと思います。

構成

build 用のファイルなどを除くとざっくりこんな感じのディレクトリ構成で Hugo を使っています。

hugo-files
▸ content/post/
▸ data/
▸ layouts/
▸ static/images/
▸ static_src/images/
▸ themes/
  addImage.sh*
  config.toml
  createBanner.sh*
  createImageWithText.sh*
  imageOptim.sh*

static/images/ 以下に post の slug と同じディレクトリを作成し、その中に記事の画像をまとめて入れています。

画像は圧縮などの最適化を行ってから使用するので、元データは static_src/images/ にバックアップしています。

スクリプト

スクリプトは以下の4つを使用しています。

scripts
addImage.sh
createBanner.sh
createImageWithText.sh
imageOptim.sh

createImageWithText.sh

createImageWithText.sh は以前作った画像の上に黒帯背景で白文字を描画するためのスクリプトです。

サムネイルで楽をするために使っています。

ImageMagick で黒帯背景に白文字を載せた画像を生成するシェル芸

addImage.sh

addImage.sh は画像を Hugo の構成内に追加するときに叩くスクリプトです。

addImage.sh src title name

のフォーマットで、

  • src : 元画像
  • title : Hugo の slug
  • name : 画像の名前(拡張子は不要)

です。

src に指定した画像を static/images/ static_src/images/ に入れた上で、 static/images/ の方には最適化を行います。

なお、 blog に吐いたデータだけでなく作成用のリポジトリ自体も GitHub に上げているので、 static_src/images/ に入れるバックアップ用の画像も convert-strip オプションで Exif 情報を削っています。

addImage.sh
#!/bin/bash

set -e
set -u

usage="Usage: addImage.sh src title name"

if [ ! $# -eq 3 ]; then
  echo $usage
  exit 1
fi

src="$1"
title="$2"
name="$3"
ext="${src##*.}"

dstDir="static/images/${title}"
dst="${dstDir}/${name}.${ext}"
backupDir="static_src/images/${title}"
backup="${backupDir}/${name}.${ext}"

mkdir -p "$dstDir"
mkdir -p "$backupDir"

convert "$src" -strip "$backup"
convert "$src" -strip "$dst"

./imageOptim.sh "$dst"

本当は引数ではなくオプションを取るようにしたほうが汎用性が高いのですが、自分でゴリゴリ使う用なので及第点としています。

cp ではなく -strip 付きの convertsrc から画像を複製しているので、 src はファイルパスだけでなく URL でも取れるようになっています。

imageOptim.sh

imageOptim.sh は画像の最適化を行うスクリプトです。

拡張子をファイル名から判別して

で圧縮しています。対応しているのは jpg png です。

mogrify -resize '2560x1440>' "$1"

2560x1440 から縦横のどちらかでもはみ出たら、アスペクト比を保ちつつ収まるサイズに縮小しています。

imageOptim.sh
#!/bin/bash

set -e
set -u

usage="Usage: imageOptim.sh src"

function resize () {
  if [ ! $# -eq 1 ]; then
    echo "resize() needs 1 args"
    exit 1
  fi
  mogrify -resize '2560x1440>' "$1"
}

if [ ! $# -eq 1 ]; then
  echo $usage
  exit 1
fi

src="$1"
ext="${src##*.}"

beforeSize=$(wc -c <"$src")

echo "optimize image: $src"

if [ $ext = 'jpg' ]; then
  resize "$src"
  jpegoptim --strip-all --max=90 "$src"
elif [ $ext = 'png' ]; then
  resize "$src"
  pngquant --ext ".${ext}" --force --speed 1 "$src"
else
  echo "unknown filetype $ext"
  exit 1
fi

afterSize=$(wc -c <"$src")

echo "optimize image: optimized ${beforeSize} to ${afterSize} ($(expr 100 \* $afterSize / $beforeSize)%)"

createBanner.sh

createBanner.sh は src 画像からサムネイルを作って画像追加までを行う、最もオレオレなスクリプトです。

一時ファイルの処理が甘いのですが、使い方の参考にはなると思うので気にせず載せます。

createBanner.sh
#!/bin/bash

set -e
set -u

usage="createBanner.sh src.jpg 'title' 'text'"

if [ ! $# -eq 3 ]; then
  echo $usage
  exit 1
fi

srcImage="$1"
title="$2"
text="$3"
ext="${srcImage##*.}"

# conf
aspectX=16
aspectY=9
name="banner"

size=($(identify -format "%w %h" "$srcImage"))
srcW=${size[0]}
srcH=${size[1]}

tmp1=$(mktemp "/tmp/${0##*/}.${srcImage//\//_}.$(date).tmp1.tmp.${ext}")
# TODO trap rm

if [ $(expr $srcH \* $aspectX) -gt $(expr $srcW \* $aspectY) ]; then
  # crop height
  w=$(expr $srcW - $srcW % $aspectX)
  h=$(expr $w / 16 \* 9)
  echo $w
  echo $h
  echo $tmp1
  convert "$srcImage" -gravity center -crop "${w}x${h}+0+0" "$tmp1"
elif [ $(expr $srcH \* $aspectX) -lt $(expr $srcW \* $aspectY) ]; then
  # crop width
  h=$(expr $srcH - $srcH % $aspectY)
  w=$(expr $h / 9 \* 16)
  echo $w
  echo $h
  echo $tmp1
  convert "$srcImage" -gravity center -crop "${w}x${h}+0+0" "$tmp1"
fi

tmp2=$(mktemp "/tmp/${0##*/}.${srcImage//\//_}.$(date).tmp2.tmp.${ext}")
# TODO trap rm

echo "srcImage = $srcImage"
echo "text = $text"
echo "tmp1 = $tmp1"
echo "tmp2 = $tmp2"

./createImageWithText.sh "$tmp1" "$text" "$tmp2"
./addImage.sh "$tmp2" "$title" "$name"

rm -f "$tmp1"
rm -f "$tmp2"

中央らへんのごちゃごちゃと計算しているところがややこしいですね。

アスペクト比を 16:9 にするためにどちらが長いかを計算してはみ出たところを中央から crop しています。

16:9 にしたものを $tmp1 に保存し、 createImageWithText.sh で文字を入れてサムネ化したものを $tmp2 に保存。

生成された文字入り画像 $tmp2 を元画像として addImage.sh で画像を追加しています。

以上

CMS ではアップロードするだけでいいのに Hugo では色々と自分で作業しなければいけない画像の追加が自動化できたので、とりあえず満足です。

Shougo/deoplete.nvimShougo/denite.nvim のソースとして画像を取得できるようにすると便利そうな気配がするので、気が向いたらやります。

addImage.sh の出力を image の path にして pbcopy するくらいでも良いかもしれないですね。