1

I'm converting our backend file upload to work with Shrine. I managed to get the image upload and thumbnailing up and running pretty easily but I've been struggling to do the same with PDF files.

The upload itself works, however, I can't manage to generate a thumbnail/preview for the file. I'm using Shrine alongside ImageProcessing and vipslib.

I tried using the thumbnail method provided by vips but that seems to work just with image files and I also tried to follow this SO with no success.

Let me give you now some context:

This is the Shrine initializer

require "shrine"
require "shrine/storage/file_system"
require "shrine/storage/google_cloud_storage"


Shrine.storages = {
  cache: Shrine::Storage::GoogleCloudStorage.new(bucket: ENV['CACHE_BUCKET']),
  store: Shrine::Storage::GoogleCloudStorage.new(bucket: ENV['STORE_BUCKET'])
}

Shrine.plugin :activerecord
Shrine.plugin :cached_attachment_data # for retaining the cached file across form redisplays
Shrine.plugin :restore_cached_data # re-extract metadata when attaching a cached file
Shrine.plugin :determine_mime_type

And this is the Uploader

class DocumentUploader < Shrine
  require 'vips'

  def generate_location(io, context)
    "documents/#{Time.now.to_i}/#{super}"
  end


  plugin :processing
  # plugin :processing # allows hooking into promoting
  # plugin :versions   # enable Shrine to handle a hash of files
  # plugin :delete_raw # delete processed files after uploading
  # plugin :determine_mime_type
  #
  process(:store) do |io, context|
    preview = Tempfile.new(["shrine-pdf-preview", ".pdf"], binmode: true)
    begin
      IO.popen *%W[mutool draw -F png -o - #{io.path} 1], "rb" do |command|
        IO.copy_stream(command, preview)
      end
    rescue Errno::ENOENT
      fail "mutool is not installed"
    end

    preview.open # flush & rewind

    versions = { original: io }
    versions[:preview] = preview if preview && preview.size > 0
    versions
  end
end

As mentioned, the uploader, at the moment is what breaks and doesn't generate the preview. The previous version of the file looked like this:

class DocumentUploader < Shrine
  require 'vips'

  def generate_location(io, context)
    "documents/#{Time.now.to_i}/#{super}"
  end


  plugin :processing
  # plugin :processing # allows hooking into promoting
  # plugin :versions   # enable Shrine to handle a hash of files
  # plugin :delete_raw # delete processed files after uploading
  # plugin :determine_mime_type
  #
  process(:store) do |io, context|
    thumb = Vips::Image.thumbnail(io.metadata["filename"], 300)
    thumb
  end
end

I have seen very little documentation online about this topic.

Update: Answering questions

The command vips pdfload spits out the usage information and it indeed says that PDF would be loaded using libpoppler.

I installed the tar file straight from their download page and the version is 8.7.0 running on a Debian system.

Thanks about the license info - will look into that as well!

ilrock
  • 573
  • 8
  • 24
  • It sounds like your libvips has been configured without PDF support. Try `vips pdfload` at the command-line and see if you get a help page. What platform are you using, what version of libvips, how did you install it, and where did you get the binary? – jcupitt Apr 25 '19 at 14:23
  • Also, libvips uses libpoppler for PDF load, and it's GPL (everything else in libvips is LGPL). You may need to think about licence compliance. – jcupitt Apr 25 '19 at 14:31
  • Thanks for your help! I just updated the question with mode details – ilrock Apr 25 '19 at 14:58
  • Sorry, I hadn't actually read your new uploader, heh. You're shelling out to mutool, but that's not necessary, libvips handles PDF transparently if poppler is enabled. The SO answer you've been working from predates Shrine adding libvips as a backend. I'll add an answer with some links. – jcupitt Apr 25 '19 at 15:38

1 Answers1

1

After hours of struggle, yesterday I finally got the thing to work.

The solution at the end was pretty simple. I used the versioning plugin offered by shrine and kept the original version in there.

class DocumentUploader < Shrine
  include ImageProcessing::Vips

  def generate_location(io, context)
    "documents/#{Time.now.to_i}/#{super}"
  end


  plugin :processing # allows hooking into promoting
  plugin :versions   # enable Shrine to handle a hash of files
  plugin :delete_raw # delete processed files after uploading


  process(:store) do |io, context|
    versions = { original: io } # retain original

    io.download do |original|
      pipeline = ImageProcessing::Vips.source(original)
      pipeline = pipeline.convert("jpeg").saver(interlace: true)
      versions[:large]  = pipeline.resize_to_limit!(800, 800)
      versions[:medium] = pipeline.resize_to_limit!(500, 500)
      versions[:small]  = pipeline.resize_to_limit!(300, 300)
    end

    versions # return the hash of processed files
  end
end
jcupitt
  • 10,213
  • 2
  • 23
  • 39
ilrock
  • 573
  • 8
  • 24
  • Clarification: `convert` is JUST setting the format to write as, it does not convert the image https://github.com/janko/image_processing/blob/master/doc/vips.md#convert Your pipeline would work equally well as `ImageProcessing::Vips.source(original).resize_to_limit!(800, 800).saver(interlace: true).convert("jpeg")` – jcupitt Apr 26 '19 at 10:27
  • Nice then, will give that a go! Thanks a lot for taking the time to help! – ilrock Apr 26 '19 at 10:37
  • No need to change your code -- I don't think there would be any difference. You might want to update this answer though, it seems slightly misleading now. – jcupitt Apr 26 '19 at 10:39
  • I edited the conversion reference from your answer, hope that's OK. – jcupitt Apr 27 '19 at 08:37