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

GitHub はこちらです。
このリポジトリの中に、extract_images.rb というサンプルプログラムが用意されていました。
これを参考にすれば、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
おわりに
こういう作業は記録しておかないと絶対忘れるので、雑記録として残しました。