🗒️

Rails+ CarrierWave+MiniMagickでPDFの最初のページのサムネイルを作る

2023/12/10に公開

概要

表題の通りで、検索すると色々それっぽいのは出てくるのですが恐らくバージョンが古いものの解説なのかどれもダメで、試行錯誤の結果うまく保存できたのでメモを残しときます。

  • rails (7.1.1)
  • carrierwave (3.0.5)
  • mini_magick (4.12.0)
  • image_processing (1.12.2)

コード

class FileItemUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick

  version :thumb do
    # ここに`full_filename`を作るとそれがサムネイルのファイル名になります。
    # このUploaderは画像とPDF両対応なのでPDFの時だけ拡張子を変更しています。
    # `minimagick!`で`convert: :jpg`してるので拡張子変わってくれそうですが
    # 残念ながら変わりません。
    def full_filename(for_file)
      name = super(for_file)
      name = name.sub(/\.pdf$/, '.jpg') if for_file.ends_with?('.pdf')
      name
    end
    
    # このメソッドでPDFの1ページ目を画像に変換しています。
    process :pdf_first_page_to_jpg, if: :pdf?
    
    # サムネイルのサイズ
    process resize_to_limit: [280, 280]
  end

  private

    def pdf?(new_file)
      new_file.content_type == 'application/pdf'
    end

    def pdf_first_page_to_jpg
      # 以前は`manipulate!`を使っていましたが`minimagick!`に変わりました。
      # 引数には`ImageProcessing::Builder`がきます。
      # `ImageProcessing::Builder`については
      # すぐ下にリンクを貼りましたのでそちらを見てください。
      minimagick! do |pdf|
        pdf
	  # `append`は`convert`コマンドに渡す引数を渡せます。
	  # この二つのオプションを指定しないとものによっては背景が真っ黒になります。
          .append('-background', 'white')
          .append('-alpha', 'remove')
	  # `loader`の指定は`convert path/to/file.pdf[0] path/to/file.jpg`
	  # のようなコマンドを生成します。これでページ指定ができるようです。
          .loader(page: 0)
          .convert('jpg')
      end
    end
end

https://github.com/janko/image_processing/blob/master/doc/minimagick.md#append

昔ながらのmanipurate!は同じファイル名を想定しているため、そこでコンバートするとうまくいきません。一度透過なしで1ページのみのPDFに変換し、もう一度convert: :jpgを呼べば可能ですが、ソースを読んでたらminimagick!の存在を知りこれに落ち着きました。

https://github.com/carrierwaveuploader/carrierwave/blob/20c6d753f8720c63718de44595351086d71b15bd/lib/carrierwave/processing/mini_magick.rb#L292-L317

308行目あたりを見るとファイル名が変わっていた時のためにゴニョゴニョしていますね。

なんか、よく見ると気持ち悪い処理ですね。jpgに変換したものを一度~.pdfという元のファイル名に戻してCarrierWave::SanitizedFile.move_toでまた~.jpgに戻してますね。CarrierWave::SanitizedFileを新しく生成しなおした方が直感的な気もしますが、負荷的な問題かもしれないですね。

Discussion