I am using rails, active storage and minimagick to try to convert a multi-page PDF into individual PNG images and then store the images with active storage.
I have been able to successfully load a PDF and convert the pages into png images using MiniMagick. However, I have only been able to get Minimagic to store the png images in the app/assets/images directory (as opposed to storing the png images as active storage attachments).
Does anyone know how get MiniMagick to store images with active storage?
Here is the code I'm using:
# Doc model with active storage attachments
class Doc < ApplicationRecord
has_one_attached :pdf #PDF file to upload
has_many_attached :pdf_pages #PNG image for each page of the PDF
end
# PrepareDocs controller manages saving a PNG image of each PDF page.
# NOTE: at this point, the PDF has already been attached to the @doc instance.
# Now we are trying to create the PNG images of each page of the PDF
class PrepareDocsController < ApplicationController
def show
# Load @doc
@doc = Doc.find(params[:id])
# Get PDF from @doc
mini_magic_pdf = MiniMagick::Image.open(ActiveStorage::Blob.service.send(:path_for, @doc.pdf.key))
# Save first page of doc.pdf as a PNG (later I will update this to convert every page to a PNG, but I'm starting on page 1 for now)
MiniMagick::Tool::Convert.new do |convert|
# Prepare formatting for PNG image
convert.background "white"
convert.flatten
convert.density 150
convert.quality 100
# Save PNG Image - This successfully creates a PNG of the first page, but I don't want to store it in my assets.
convert << mini_magic_pdf.pages.first.path
convert << "app/assets/images/page_1.png" # This probably needs to be changed so that we are not storing in the app/assets/imdages directory
# [??? insert additional code to attach the png image to @doc.pdf_pages using active storage instead of to the images directory?]
# NOTE: if I try to access the newly created png in my views, I get "incorrect signature" OR "asset not available in asset pipeline" errors
end
end
end
Any help would be greatly appreciated!! Thanks!
UPDATE: I got it to work and here is the final code:
# Doc model with active storage attachments
class Doc < ApplicationRecord
has_one_attached :pdf #PDF file to upload
has_many_attached :pdf_pages #PNG image for each page of the PDF
end
# PrepareDocs controller manages saving a PNG image of each PDF page.
# NOTE: at this point, the PDF has already been attached to the @doc instance.
# Now we are trying to create the PNG images of each page of the PDF
class PrepareDocsController < ApplicationController
def show
# Load @doc
@doc = Doc.find(params[:id])
# path to current pdf (ie @doc.upoad)
pdf_path = ActiveStorage::Blob.service.path_for(@doc.pdf.key)
# set minimagick image wrapper for pdf stored in @doc.uplaod
magick = MiniMagick::Image.open(pdf_path)
# run repeat block to save each page as an individual image
magick.pages.each_with_index do |page, index|
# set file name to "page_N"
file_name = "page_#{(index+1).to_s}"
# set path for tempfile that you are about to create (using rails post ruby 2.5 approach. Pre 2.5 ruby uses make_tmpname; post 2.5 ruby uses create; I like rails post 2.5 version)
converted_file_path = File.join(Dir.tmpdir, "#{file_name}-#{Time.now.strftime("%Y%m%d")}-#{$$}-#{rand(0x100000000).to_s(36)}-.png")
# create png and save to tempfile path
MiniMagick::Tool::Convert.new do |convert|
# prep format
convert.background "white"
convert.flatten
convert.density 300
convert.quality 100
# add page to be converted
convert << page.path
# add path of page to be converted
convert << converted_file_path
end
# attach using active storage - NOTE: this needs to be done AFTER the convert block
@doc.pdf_pages.attach(io: File.open(converted_file_path), filename: file_name, content_type: "image/png")
# remove tempfile
FileUtils.rm(converted_file_path)
end
end
end
NOTE: I found 3 different methods for creating tempfiles (see here) depending on the ruby version and / or preference:
# pre ruby 2.5
converted_file_path = File.join(Dir.tmpdir, Dir::Tmpname.make_tempname(["page_1", ".png"], nil))
# ruby 2.5 and later
converted_file_path = File.join(Dir.tmpdir, Dir::Tmpname.create(["page_1", ".png"]) {} )
# rails version of pre ruby 2.5
converted_file_path = File.join(Dir.tmpdir, "#{file_name}-#{Time.now.strftime("%Y%m%d")}-#{$$}-#{rand(0x100000000).to_s(36)}-.png")
app/assets
is a directory for your asset sources, images from there are copied to public/assets
during asset compilation (usually done on deploy), so to be able to access created images - store them somewhere in public
, like public/converted/page_1.png
(it will have path /converted/page_1.png
)
MiniMagick is just a wrapper around ImageMagick command line utilities, so you need to save the result in a temporary location before uploading to activestorage and then use something like:
converted_file_path = File.join(Dir.tmpdir, Dir::Tmpname.make_tmpname(["prefix-", ".png"], nil))
... # do convertation
@doc.pdf_pages.attach(io: File.open(converted_file_path), filename: 'page1.png')
FileUtils.rm(converted_file_path)