PDF をアップロードして中に含まれる JPEG だけを保存: hkob の雑記録 (3)

はじめに

hkob の雑記録の第3回目は Notion から離れ、PDF から JPEG を抽出する Rails aciton を記載した記録です。今年度の入試から Web 出願に変更となり、システムから受検票が PDF として取得できます。この PDF には JPEG の写真が埋め込まれているので、なんとかこれだけを取り出して利用したいと思いました。今回はその記録です。

pdf-reader

調べたところ、Ruby gem には pdf-reader というライブラリがありました。

rubygems.org

pdf-reader gem

GitHub はこちらです。

github.com

このリポジトリの中に、extract_images.rb というサンプルプログラムが用意されていました。

github.com

これを参考にすれば、Rails のファイルアップローダにこれを組み込むことができそうです。すなわち、PDF ファイルをアップロードしたら、中に保存されている JPEG データだけを取り出して内部に保存する処理を記載することにしました。

upload_face_picture

Rails のコントローラに用意した upload_face_picture アクションは以下のように記述しました。 @objects は before_action で事前に取得しています。

def upload_face_picture
    object_hash = @objects.index_by &:number
    begin
      _store_upload params[:qqfile], object_hash
    rescue StandardError
      render json: {error: $ERROR_INFO.message.to_s}
    else
      render json: {success: true}
    end
  end

ここから呼ばれる _store_upload は以下のように記述しました。中身は extract_images を参考にメソッドに落としています。ここで PDF::Reader.new は StreamIO にするために StringIO クラスを使いました。ここで、PDF に JPEG データが含まれていた場合、PDF の中に書かれている受検番号を取得します。その受検番号をキーに、object_hash に保存されている object (Suisen または Gakuryoku ) を取得します。object には jukensei_id が記録されているので、その名前のファイルとして stream データ (この場合は JPEG画像) を保存しています。

  def _store_upload(file, object_hash)
    FileUtils.mkdir_p Rails.root.join("private/pictures")
    if request.xhr?
      if request.body.size == request.headers["CONTENT_LENGTH"].to_i
        file_data = request.body.read
      end
    elsif file.instance_of?(File)
      file_data = file.read
    end
    reader = PDF::Reader.new StringIO.new(file_data)
    page = reader.page(1)
    xobjects = page.xobjects
    xobjects.each do |_, stream|
      if stream.hash[:Subtype] == :Image && stream.hash[:Filter] == :DCTDecode
        number = (page.text.match /受検番号 +(\d+)/)[1]&.to_i
        if (object = object_hash[number])
          jukensei_id = object.jukensei_id
          file_name = Rails.root.join "private/pictures/n#{jukensei_id}.jpg"
          File.open(file_name, "wb") { |f| f.write stream.data }
        end
      end
    end
  end

おわりに

こういう作業は記録しておかないと絶対忘れるので、雑記録として残しました。

hkob.notion.site