We learn as we go, we write as we learn.

About michelada.io


By joining forces with your team, we can help with your Ruby on Rails and Javascript work. Or, if it works better for you, we can even become your team! From e-Commerce to Fintech, for years we have helped turning our clients’ great ideas into successful business.

Go to our website for more info.

Tags


Support HEIC images in a Rails app using minimagick, a custom Rails Previewer and dropzone.js [Part 1 - Backend]

1st February 2022

[PART 1 of 2]

Working with HEIC files is a challenge. As of today, there is not a single browser that renders HEIC/HEIF files. I had the challenge at work of being able to handle these type of files. I'm working with a Rails 6 app and here's what it took for being able to handle these files.

First of all, I followed this blog post. As is explained in the post, a custom Rails previewer is needed. After setting up the HEICPreviewer, and using Minimagick for doing the conversion to a png file, everything seemed to be working by running the test implemented. However, when running the app and uploading a HEIC picture, I was running into a ActiveStorage::UnpreviewableError. This took me into a deep dive in Rails Previewers. Here's what I learned.

After some time debugging, I understood the ActiveStorage::UnpreviewableError error. This error raises when preview is called on a blob that is not previewable. You may be wondering, how does Rails know when a blob is previeweable or not? You can find out yourself by running Rails.configuration.active_storage.previewers in a Rails console. By default, you get these previewers.

[ActiveStorage::Previewer::PopplerPDFPreviewer,ActiveStorage::Previewer::MuPDFPreviewer, ActiveStorage::Previewer::VideoPreviewer]

Previewers

Let's take one step back though. What are Rails Previewers anyway? By definition, a previewer extracts a preview image from a blob. As stated in the docs, ActiveRecord provides previewers for videos and PDF, as seen in the default previewers above. In the case of the PDF previewers, it extracts the first page. For video previewers, it extracts the first frame. This is what Previewers can do. It lets you handle distinct type files and do something with them.

With Mario's approach, I created a subclass of ActiveStorage::Previewer and created a HeicPreviewer. There are two methods that have to be implemented in any  ActiveStorage::Previewer subclass. These methods are:

def accept?(blob)
end

def preview(**options)
end

Here's an interesting bit on the accept? method:

This method checks if the blob passed to the method, is accepted in the Previewer. For instance, in the Previewer::PopplerPDFPreviewer  the accept? method checks if the blob is of content_type 'pdf' and if the @pdftoppm_exists

In our example with the HEIC file, Rails will go through each of the previewers available (listed above as out-of-the-box) and will run accept? on each previewer to know which previewer can handle it. It will take the first one that returns true to accept? as seen in this bit of code:

# Implemented in ActiveStorage::Preview model
#https://github.com/rails/rails/blob/83217025a171593547d1268651b446d3533e2019/activestorage/app/models/active_storage/preview.rb#L111

def previewer_class
  ActiveStorage.previewers.detect { |klass| klass.accept?(blob) }
end

Following our example, when passing a HEIC file, our declared HeicPreviewer will handle it since it will go through every Previewer defined and expect a true value returned from the accept? method. This is how our accept?method looks like in our HEIC previewer:

# As shown here https://mariochavez.io/desarrollo/2021/06/22/heic-support-active-storage

def accept?(blob)
  blob.content_type == "image/heic" && minimagick_exists?
end

Now, upon calling preview on our attachable,  the magic of MiniMagick kicks in and does the conversion to a png file, as shown in the following bit of code

# As shown here https://mariochavez.io/desarrollo/2021/06/22/heic-support-active-storage/

def preview(transformations = {})
    download_blob_to_tempfile do |input|
      io = ImageProcessing::MiniMagick.source(input).convert("png").call
      yield io: io, filename: "#{blob.filename.base}.png", content_type: "image/png"
    end
  end

How to use our HeicPreviewer

Now, for using the our previewer, we need to call preview on the image and pass a transformation to it. Let's look at an example

# In the view where you want to accept HEIC images
<%= image_tag image_preview(@user.picture) %> 

# In our users_helper.rb
def image_preview(image)
    if image.attached? && image.content_type == "image/heic"
      return image.preview(resize_to_limit: [300, 300]).processed.service_url
    end
end

By doing the call to preview, our HeicPreviewer will handle it and do the magic! 🎉

That's it for the part of it when it comes to creating a Previewer in Rails. In part 2 of this article, we'll look at a different use case in the frontend.

To be continued in part 2...

View Comments