How to generage HEIC previews in Rails using ActiveStorage

Plus how to deploy to Heroku

Posted by John Thomas 23-May-2020

Apple started using the HEIC format for images a few years ago and its adoption continues to grow. Rails ActiveStorage Previews don't support HEIC files out of the box, so you will need to create your own Previewer if you want to generate previews for these files in your Rails apps.

Creating a Rails Previewer is actually pretty simple, as long as you subclass the base ActiveStorage::Previewer class. Here is the scaffolding I used for the HEIC previewer.

# app/previewers/heic_previewer.rb

class HEICPreviewer < ActiveStorage::Previewer
  class << self
    def accept?(blob)
    end
  end

  def preview
  end
end

The accept? method should return true for file formats that this previewer knows about. This is how ActiveStorage knows to use this Previewer. So for HEIC files, we can implement this method like this:

# app/previewers/heic_previewer.rb

class HEICPreviewer < ActiveStorage::Previewer
  CONTENT_TYPE = 'image/heic'.freeze

  class << self
    def accept?(blob)
      blob.content_type == CONTENT_TYPE
    end
  end

  def preview
  end
end

We check to see if the blob's content type is 'image/heic' and if so, return true.

Implementing the preview is a little trickier since we have to figure out how to actually generate a preview (PNG) from the HEIC file. One solution is to use the Vips image processor, but you need to make sure that Vips is set up with HEIC support. Basically libvips needs to be compiled on a machine that has libheif already installed in order to add HEIC support to libvips. We can get into how to set that up a little later. For now, lets assume that libvips is installed and configured correctly. To use Vips in ruby, we can use the recommended image_processing gemm which gives us a standardized way to convert and images using Vips or ImageMagick.

When implementing the preview method in our Previewer, we will need to get access to the original HEIC file, use Vips to convert it to a PNG and then yield that PNG file back to ActiveStorage::Previewer to use. All that looks like this:

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

The download_blob_to_tempfile method is defined on the base ActiveStoreage::Previewer and that does what you would think, download the original file to a temp file so we have access to convert it. Then we use that temp file as the source for a ImageProcessing::Vips object and convert it to a PNG. That generates a PNG in memory as an IO object, which we yield back along with the filename and content type.

Finally we need to handle the situation when dependencies are not installed. If we don't libvips available, then we should not return true from the accept? method. So adding that into our Previewer, the whole thing looks like:

class HEICPreviewer < ActiveStorage::Previewer
  CONTENT_TYPE = 'image/heic'.freeze

  class << self
    def accept?(blob)
      blob.content_type == CONTENT_TYPE && vips_exists?
    end

    def vips_exists?
      return @vips_exists unless @vips_exists.nil?

      require "image_processing/vips"
      @vips_exists = Vips.at_least_libvips?(0, 0)
    rescue StandardError
      @vips_exists = false
    end
  end

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

Now the only thing left to do, is to actually register that previewer with ActiveStorage in your application:

# config/initializers/active_storage.rb
Rails.application.config.active_storage.previewers << HEICPreviewer

How to install libvips with HEIC support on a Mac

I don't personally own a Mac so I have not tested this, but it looks like you can just do:

brew install vips

According to the [Vips formula] (https://formulae.brew.sh/formula/vips), that depends on libheif so HEIC support should be included.

How to install libvips with HEIC support on Linux

In order to get HEIC support, you will need to install libheif using whatever package manager you choose. apt get install libheif-dev or pacman -S libheif for example. Then you can build libvips from git following the official docs

How to install libvips with HEIC support on Heroku

This is a little tricky since you need to build libvips with libheif and have libheif installed on your Heroku dynos.

Add libheif to your Heroku dyno

The easiest way to do this is to:

heroku buildpacks:add --index 1 https://github.com/heroku/heroku-buildpack-apt
  • Add an Aptfile in the root directory of your app with libheif-dev
# Aptfile
libheif-dev

Build libvips with HEIC support, and add that library to your Heroku dyno

This can be done using a Heroku buildpack. I created one that you can add like this:

heroku buildpacks:add  https://github.com/thomas07vt/heroku-buildpack-vips

This buildpack was forked from https://github.com/brandoncc/heroku-buildpack-vips. Brandon did all the legwork, I just added libheif-dev to the docker build script to add HEIC support. I also built libvips on the latest version (8.9.2 at the time of this article).