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.
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.
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.
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.