Middlemanでサムネイル画像を作る方法

middleman

Middlemanでこのようなサムネイル画像を作る方法をご紹介します。このようなサムネイル画像をMiddlemanでも作ることができます。

Middlemanで作ったサムネイル画像(メイン)

サイドバーによくある小さなサムネイルももちろん可能です。

Middlemanで作ったサムネイル画像(サイドバー)

サムネイルを生成するためのgem

Middlemanのサムネイル生成のgemで有名なのが次の2つです。

両方試したのですが、私は前者を採用しました。というのは、後者はサムネイルを生成する画像があるディレクトリを指定する必要があったためです。画像を特定のディレクトリにまとめている人はよいのですが、自分の場合は下のような記事ごとのディレクトリに画像を保存してありました。

source/articles/2016-11/02-tom-yum-goong-noodle/images/

なので、保存ディレクトリを気にしなくてよい、前者を採用しました。

middleman-simple-thumbnailerの使い方

導入方法

Gemfileに以下を追記しbundle installを実行します。

1
gem 'middleman-simple-thumbnailer'

config.rbに以下を追記すれば使えるようになります。

1
activate :middleman_simple_thumbnailer

うまくbundle installできなかったり、正常に動かない場合

お使いの端末にGraphicsMagickというモジュールをインストールする必要がある場合があります。

Macでhomebrewをお使いのかたは下のようにインストールできます。

1
brew install graphicsmagick

使い方

View側でimage_tagというヘルパーメソッドを次のように使うことができます。

1
= image_tag image, resize_to: '50x50', class: 'thumbnail'

image_tagはもともとMiddlemanにあるヘルパーメソッドですが、middleman-simple-thumbnailerをインストールすると、resize_toというオプションが追加されます。これに生成したいサムネイルのサイズを指定してあげるだけです。

image_tagがやってくれることはざっくり次の2点です。

  • imageで指定した画像のresize_toで指定したサイズのバージョンを作る
  • src属性に生成したサムネイルのパスを指定したimgタグを出力する

縦と横の両方それぞれ指定できるわけではない

上の例では '50x50'と指定していますが、縦と横のサイズが違う場合、どちらも50pxにしてくれるというわけではないようです。長い方を50にし、縦横の比率を変えずに短いほうの長さが決まるようです。

なので、縦横比を変えたい場合は、CSSのheigtやwidth属性で制御するのがよいでしょう。

記事の最初にでてくる画像をサムネイルにする方法

先ほどの例では、imageのところに、いちいちサムネイル化したい画像のパスを指定しなければなりません。通常のサイトやブログ運営では、記事の一番最初に出てくる画像を勝手にサムネイルにしてくれたら楽だと思います。その方法をご説明します。

まず、記事のオブジェクトを引数にとると、メインの画像のパスを返してくれるヘルパーメソッドを作ります。ヘルパーメソッドはconfig.rb内で、helpers doの中で定義することができます。

1
2
3
4
5
6
7
8
9
helpers do
  def main_img_path(article)
    imgs = Nokogiri::HTML.parse(article.body).xpath('//img')
    blank_thumbnail = "/images/blank_thumbnail.png"
    return blank_thumbnail if imgs.blank?
    img_src = imgs.first.attr('src')
    img_src =~ /^http.*$/ ?  blank_thumbnail : img_src
  end
end

Nokogiriというライブラリでhtmlをパースし、最初に出てくるimgタグのsrc属性を取得しています。また画像を使っていない場合や、外部サイトの画像を使っている場合には、空白用の画像のパスを返すようにしています。

View側では下のように、記事のオブジェクトを引数にして使います。

1
  = image_tag main_img_path(article), resize_to: '300'

記事内の画像が表示されなくなってしまう場合

記事をKramdownというMarkdownパーサーを使っている場合などに、上の手順を行うと記事内の画像が表示されなくなる場合があります。

Kramdownだと下のように相対パスを指定した場合、current_articleからの相対パスと解釈してくれて適切なパスに変換してくれます。

1
![サンプル画像](sample.jpg)

下のように適切なパスを出力してくれる。

1
<img alt="サンプル画像" src="/articles/2016-11/09-middleman-thumbnail/images/sample.jpg">

ですが、上の手順で、記事の一番上の画像のパスを取り出す処理にて、対象の記事がcurrent_articleでない時に、記事を生成することになります。生成される画像のパスは下のようにサイト全体のimagesディレクトリからの相対パスにしてしまいます。

1
<img alt="サンプル画像2" src="/images/images/sample.jpg">

その記事のURLにアクセスした時に、current_articleがその記事になるので適切に生成し直してくれるのでは?と思うかもしれませんが、一度生成された記事はキャッシュの中に残りそれが出力されてしまいます。Middlemanではキャッシュのクリアを任意にすることができないようです。

なので、記事を表示するテンプレートにて、適切なパスに置換する処理を加えます。まずヘルパーメソッドに下のように追記します。

1
2
3
4
5
6
7
def render_with_correct_img(article)
  doc = Nokogiri::HTML(article.body)
  doc.xpath("//img[substring(@src, 1, 15) ='/images/images/']").each do |elm|
    elm["src"] = "/" + article.path.gsub(/index\.html/,'') + 'images/' + elm.attr("src").split("/").last
  end
  doc.css('body')[0].inner_html
end

記事を出力するView側にて下のようにします。

1
= render_with_correct_img article

また、この問題が発生している人は、先ほど作ったmain_img_path内で取得するパスもおかしい可能性が高いです。main_img_path内でもrender_with_correct_imgを使って下のように修正します。2行目が修正されています。

1
2
3
4
5
6
7
def main_img_path(article)
  imgs = Nokogiri::HTML(render_with_correct_img(article)).xpath('//img')
  blank_thumbnail = "/images/blank_thumbnail.png"
  return blank_thumbnail if imgs.blank?
  img_src = imgs.first.attr('src')
  img_src =~ /^http.*$/ ?  blank_thumbnail : img_src
end

100px以下のサイズを指定するとエラーが起きる

これは自分のパソコンだけかもしれませんが、resize_toで100以下を指定すると、次のようなエラーが発生しました。

1
MiniMagick::Error at /bootcamp-vs-parallels-11-vs-fusion-8/`gm mogrify -resize 100 /var/folders/vc/3xzdspjn5_j53wlgmf23prsh0000gn/T/mini_magick20161108-56972-1ucpfe.gif` failed with error:Ruby/Users/taktak/blog/webfood/vendor/bundle/ruby/2.1.0/gems/mini_magick-4.5.1/lib/mini_magick/shell.rb: in run, line 18 WebGET localhost/bootcamp-vs-parallels-11-vs-fusion-8/

なので、小さくても150程度にし、さらに小さくしたい場合はcss側で調整するようにしたほうがいいです。

build時に同じサムネイルを何度も生成するのを防ぐ

1つの記事のサムネイルって通常いろいろなページから参照されますよね。タグページ、カテゴリーページ、他の関連記事からも。このgemだとそのような様々なページでimage_tagが呼ばれるたびに、生成するようになっています。development時はアクセスする瞬間だけなので、気になりませんが、build時は全ページ分行うので、同じ記事のサムネイルを上書きして生成する処理が何度も走ってしまいます。するとものすごくbuildに時間がかかります。通常5分程度で終わっていたのが、このgemをいれたら20分程度になってしまいました。

なので、自分はこのgemに少し手を入れ、同じファイル名がすでにある場合は生成しない、という処理にしました。手を入れるといっても、実際のファイルを修正しなくても、config.rbからclass_eval
を使うことで可能です。元ファイルから問題のinitializeメソッドをまるまるコピーし、6行目を追加しています。

1
2
3
4
5
6
7
8
9
10
11
12
MiddlemanSimpleThumbnailer::Extension.class_eval do
  def initialize(app, options_hash={}, &block)
    super
    app.after_build do |builder|
      MiddlemanSimpleThumbnailer::Image.all_objects.each do |image|
        next if File.exist? "build" + image.resized_img_path
        builder.say_status :create, "#{image.resized_img_path}"
        image.save!
      end
    end
  end
end

※ middleman-simple-thumbnailer (1.0.2)の場合

ちなみに、config.rbの追記する場所は、acitivateする次の部分より上に書かないと反映されません。

1
activate :middleman_simple_thumbnailer

これで同じパス、ファイル名のサムネイルは1度しか生成されなくなり、大幅にbuild時間が短くなりました。

middleman-s3_syncでサムネイル画像を同期してくれない

Amazon S3を利用していて、ファイルの同期にmiddleman-s3_syncというgemを使っている場合は、なぜかサムネイル画像は同期してくれません。

なので、middleman-s3_syncを使うのはやめて、Dragon Diskというツールを使うことにしました。

S3との同期ツールは次の記事でまとめています。

Amazon S3と同期させるためのツール7選

さいごに

いかかでしたでしょうか?きれいにサムネイルを表示できましたでしょうか?

ちなみに、この作業で使ったそれぞれのライブラリのバージョンは以下です。

ライブラリ名 バージョン
middleman-core 3.4.0
middleman-blog 3.6.0.beta.2
middleman-simple-thumbnailer 1.0.2
羊毛や小麦