Generating preview for pdf and images using Carrierwave in Rails

23:29 Anil Maurya 1 Comments


This blog post will cover how to generate the preview of pdf files using Carrierwave gem.

Carrierwave gem has an inbuilt "manipulate!" method which reads the file and loops over every page. This "manipulate!" method works as expected for a pdf having 300-400 pages but when I uploaded a pdf having 1000+ pages my machine crashed!!!
That is when I started debugging the code and found out that there is an issue with the manipulate method.

Here is my original code.

class MyUploader < CarrierWave::Uploader::Base
  include CarrierWave::RMagick

  def cover
    manipulate! do |frame, index|
      frame if index.zero? # take only the first page of the file
    end
  end

  version :preview do
    process :cover
    process :resize_to_fit => [310, 438]
    process :convert => :jpg

    def full_filename (for_file = model.source.file)
      super.chomp(File.extname(super)) + '.jpg'
    end
  end

end
source: http://makandracards.com/makandra/20305-howto-provide-a-single-page-preview-for-pdf-txt-with-carrierwave

Above code works only when the number of pages is small but for large number of pages the machine crashes.
The implementation for manipulate! method is shown below.

  def manipulate!(options={}, &block)
    cache_stored_file! if !cached?

          read_block = create_info_block(options[:read])
          image = ::Magick::Image.read(current_path, &read_block)
          frames = ::Magick::ImageList.new

          image.each_with_index do |frame, index|
             frame = yield *[frame, index, options].take(block.arity) if block_given?
             frames << frame if frame
          end 
          frames.append(true) if block_given?

          write_block = create_info_block(options[:write])
          if options[:format] || @format
             frames.write("#{options[:format] || @format}:#{current_path}", &write_block)
          else
             frames.write(current_path, &write_block)
    end 
    destroy_image(frames)
           rescue ::Magick::ImageMagickError => eraise CarrierWave::ProcessingError, I18n.translate(:"errors.messages.rmagick_processing_error", :e => e, :default => I18n.translate(:"errors.messages.rmagick_processing_error", :e => e, :locale => :en))
  end

Notice it will read the whole file each time we need to generate the preview of a single page and there lies our problem.


The solution: USE RMAGICK.

class MyUploader < CarrierWave::Uploader::Base
  include CarrierWave::RMagick

  def convert_to_image(height, width)
    image = ::Magick::Image.read(current_path + "[0]")[0]
    image.resize_to_fit(height,width).write(current_path)
  end

  version :preview do
    process :covert_to_image(100, 100)
    process :convert => :jpg

    def full_filename (for_file = model.source.file)
      super.chomp(File.extname(super)) + '.jpg'
    end
  end

end
In the above code, it will read only the first page of the uploaded pdf and create the preview instead of processing all the pages.
current_path + "[0]" will read the first frame/page of the uploaded pdf.

image = ::Magick::Image.read(current_path + "[0]")[0]
Once we have the first page of pdf then we just need to resize it to the required height and width and save at the desired path.

image.resize_to_fit(height,width).write(current_path)

This solved my problem.

1 comment :

  1. awesome post presented by you..your writing style is fabulous and keep update with your blogs Ruby on Rails Online Training India

    ReplyDelete