Preview-Fu on Ruby on Rails with Paperclip, ffmpeg and STI (Part 2)

In Part 1 i've showed how to set up the magic in the models. Here is how to use it in the views:


Presenting the Previews

I use a little helper for convenient preview calling in the views. I use shadowbox for displaying the bigger views in a nice way. At this point I use a simple mp3-player for audio-prehear. But I will soon switch to the JW-Player, shadowbox uses.

Anyone knowing a nice audio-3d-visualisation plugin for JW-Player? ;)

 

def preview(object)
    preview = case object.preview.class.to_s
              when "Audio"
                swf_tag "player_mp3_mini", :size => "150x20", :flashvars => { :mp3 => object.preview.body.url }, :parameters => { :bgcolor => "#ff0000" }
              when "Image"
                link_to image_tag(object.preview.body.url(:thumb)), object.preview.body.url, :rel => "shadowbox"
              when "Video"
                link_to image_tag(object.preview.body.url(:thumb)), object.preview.body.url, :rel => "shadowbox;height=230;width=420", :class => "video"
              end
  end

 

Rails tags each asset with a number so we can use never-ending expiration dates for them, which is fine. But the JW-Player recognizes the kind of media it plays on the suffix and assumes it's a playlist, if it can't find a suffix it knows. So we must tell it, that he should play a video. Here is the helper I use to generate the Javascript for the javascript_tag

 

def video_preview_setup
    script = "
window.onload = function() {

// set up all anchor elements with a 'video' class to work with Shadowbox
Shadowbox.setup('a.video', {
flashVars: { 'type' : 'video'},
autoplayMovies: true
});

};"
  end

 

You may note that this helper does nothing than containing a sting. But I like it this way because a) I didn't see it in the view and b) I use hamland it's a pain to generate multi-collumn strings in it, it's simply not designed for this.

 

Ok, that's enough for this time. I hope it could help a little out there :)

 

Preview-Fu on Ruby on Rails with Paperclip, ffmpeg and STI

Introduction

One of the projects I'm working for handles a lot of different kinds of files.

The system generates previews for the commodities of the sites users. The commodities where uploaded per uploadify and managed with paperclip in a class. For more information on this topic have a look at this excellent demo app.

For design reasons, I capsuled the previews of all commodities in a single class.

 

class Preview < ActiveRecord::Base
  belongs_to :work
  has_many :commodities 
end
# == Schema Information
#
# Table name: previews
#
# id :integer not null, primary key
# work_id :integer
# type :string(12)
# body_file_name :string(255)
# body_content_type :string(42)
# body_file_size :integer
# body_updated_at :datetime
# created_at :datetime
# updated_at :datetime
#

There are three kinds of Previews: Audio, Video and Image, each of these is a subclass of Preview and exists in the same table. Each of these classes have a has_attached_files :body declaration though each kind of preview needs to handle the stored files different.

Setting paperclip up

Moreover, some generated previews are stored in different fileformats than the original file from which the preview is generated. Paperclip assumes that the processed files are from the same fileformat as the original. So we must tell it, that this isn't so. We do this with this initializer in config/initializers/paperclip.rb

 

Paperclip.interpolates :extension do |attachment, style_name|
  case attachment.instance.class.to_s
    when "Video"
      if style_name.to_s == 'original'
        'flv'
      else
        'jpg'
      end
    when "Audio"
      if style_name.to_s == 'original'
        'mp3'
      end
    else
      File.extname(attachment.original_filename).gsub(/^\.+/, "")
  end
end

This tells paperclip that video previews are flvs and the tumbnails jpg. I also wanted each audio preview to be a mp3. Since I store the commodities on another place in the app, I override the original style with a processed style.

Begin with the magic

Naturally, you must tell paperclip how to handle the :extension symbol. You do that with the :url and :path arguments of has_attached_file. Take a look, for example, at the audio class:

 

class Audio < Preview
 
  has_attached_file :body,
                    :styles => { :original => 'k128' },
                    :path => ':rails_root/public/previews/:id/:style.:extension',
                    :url => '/previews/:id/:style.:extension',
                    :processors => [:audio_prehear]
end

As I said before, I overwrite the original style though the file is copied from the local file system in most cases. The called Processor is in lib/paperclip_processors/audio_prehear.rb

 

module Paperclip
  class AudioPrehear < Processor

    attr_accessor :resolution, :whiny

    def initialize(file, options = {}, attachment = nil)
      super
      @file = file
      @whiny = options[:whiny].nil? ? true : options[:whiny]
      @basename = File.basename(@file.path, File.extname(@file.path))
    end

    def make
      target = File.dirname(@file.path) + "/" + @basename + ".mp3"

      convert File.expand_path(file.path), target
      dst = File.open target
    end

    def convert ( infile, outfile )
      cmd = "-y -i #{infile} -ab 128k #{outfile}"
      begin
       success = Paperclip.run('ffmpeg', cmd, [ 0, 1 ])
      rescue PaperclipCommandLineError
        raise PaperclipError, "There was an error processing the preview for #{@basename}" if whiny
      end
    end
  end
end

Note, that you need ffmpeg in your path or you set the following option in config/initializers/paperclip.rb:

paperclip.options[:command_path] = "/path/to/ffmpeg"

It is useful to link all tools, which are needed by your paperclip processors, in this directory.

 

Going to advanced

So, when we are coming to video previews, we need two kinds of processors. One for the preview Video and one for the Thumbnails. If we simply use the :processors argument of has_attached_files all these processors will be executed for all styles. But that isn't what we want. So I dig into paperclip and at the moment, as I feared, I had to hack it, I realized, that the wizards at thoughtbot already implemented this feature!

 

class Video < Preview
  has_attached_file :body,
                    :path => ':rails_root/public/previews/:id/:style.:extension',
                    :url => '/previews/:id/:style.:extension',
                    :styles => { :original => { :resolution => "flash", :processors => [ :video_preview ] }, :thumb => { :geometry => "130x130#", :processors => [ :video_thumbnail ]} }

end

We could simply ad a :processors symbol to a style-Hash to bind a processor to a style. This way, the original style get processed by the VideoPreview processor and the Thumbnail by the VideoThumbnail processor. I used the Thumbnail generator introduced by Rob Anderton, an insightful read on the topic. I based my VideoPreview and AusioPrehear on it.

See how to use that in the views in the second part.