Installing FFmpeg
In case you’re not familiar with FFmpeg, it is an extremely powerful, open source recorder, converter and streamer of audio and video. Using it just to thumbnail videos is an almost criminal underuse of its capabilities.
And installation is a rare treat too: it’s easier on Windows than on Unix! Although there are no official binaries for Windows (and compiling your own would be painful) there are, thankfully, a number of pre-compiled options available. I decided to use this one, which includes everything needed to make the thumbnails.
- Download and extract the latest build (I extracted the files to C:\Program Files\ffmpeg)
- Add the folder to your PATH environment variable (this is optional, but if you don’t you’ll need to use the Paperclip
:command_pathoption)
If you’re on Unix then you’re on your own! Although this page should help you get started…
The processor
Here’s the code:
module Paperclip
class VideoThumbnail < Processor
attr_accessor :time_offset, :geometry, :whiny
def initialize(file, options = {}, attachment = nil)
super
@time_offset = options[:time_offset] || '-4'
unless options[:geometry].nil? || (@geometry = Geometry.parse(options[:geometry])).nil?
@geometry.width = (@geometry.width / 2.0).floor * 2.0
@geometry.height = (@geometry.height / 2.0).floor * 2.0
@geometry.modifier = ''
end
@whiny = options[:whiny].nil? ? true : options[:whiny]
@basename = File.basename(file.path, File.extname(file.path))
end
def make
dst = Tempfile.new([ @basename, 'jpg' ].compact.join("."))
dst.binmode
cmd = %Q[-itsoffset #{time_offset} -i "#{File.expand_path(file.path)}" -y -vcodec mjpeg -vframes 1 -an -f rawvideo ]
cmd << "-s #{geometry.to_s} " unless geometry.nil?
cmd << %Q["#{File.expand_path(dst.path)}"]
begin
success = Paperclip.run('ffmpeg', cmd)
rescue PaperclipCommandLineError
raise PaperclipError, "There was an error processing the thumbnail for #{@basename}" if whiny
end
dst
end
end
end
In order for Paperclip to automatically load the processor it should be saved in the RAILS_ROOT/lib/paperclip_processors directory.
A Paperclip processor must conform to the following rules:
- it must be defined within the
Paperclipmodule as a subclass ofProcessor - its constructor (if overriding the one provided by the base class) must accept the file to be processed, a hash of style options and the attachment instance that the file is for
- it must define a
makeinstance method that returns aFileorTempfilecontaining the processed data
The VideoThumbnail processor accepts three options:
:time_offset- The number of seconds into the video to capture as a thumbnail (this should be a negative number and corresponds to the itsoffset option for FFmpeg).
:geometry- Accepts a wxh geometry string, ideally both the width and height should be even numbers however if they aren’t the processor will adjust them automatically.
:whiny- Determines whether or not thumbnailing errors are to be reported.
The make instance method of the thumbnailer actually does the work. The code itself is very straightforward:
- it creates a temporary file to store the thumbnail
- builds the options string to pass to FFmpeg
- calls FFmpeg, handling any errors
- it returns the temporary file (a JPEG image)
An example FFmpeg options string used by the thumbnailer looks like this:
-itsoffset -4 -i "C:/Temp/0001.avi" -y -vcodec mjpeg -vframes 1 -an -f rawvideo -s 320x240 "C:/Temp/0001.0001.jpg"
- -itsoffset
- The time offset (in seconds) to take the thumbnail.
- -i
- Specifies the input file (passed to the processor by Paperclip).
- -y
- Overwrites the output file if it exists.
- -vcodec mjpeg
- Specifies the output codec to use (in this case the mjpeg codec).
- -vframes 1
- The number of frames to output (we only want one frame as the output is a static image).
- -an
- Disables audio output.
- -f rawvideo
- Forces the output format to raw video (no encoding is necessary).
- -s 320x240
- Specifies the size of the thumbnail.
Patching Paperclip
Paperclip provides the :processors option that, unsurprisingly, allows you to specify the processors to run on a file. Today I submitted a patch that allows this option to accept a Proc that has now been applied to the plugin. By accepting a Proc it makes it possible to specify different processors depending on the type of file uploaded.
If you’re running on an older version of Paperclip and cannot upgrade to the latest version, you can monkey-patch it yourself:
module Paperclip
class Attachment
private
def solidify_style_definitions
@styles.each do |name, args|
@styles[name][:geometry] = @styles[name][:geometry].call(instance) if @styles[name][:geometry].respond_to?(:call)
@styles[name][:processors] = @styles[name][:processors].call(instance) if @styles[name][:processors].respond_to?(:call)
end
end
end
end
In the model I’ve defined a video? method that returns true if the uploaded file is a video and then I use this is in the :processors option like this:
:processors => lambda { |a| a.video? ? [ :video_thumbnail ] : [ :thumbnail ] }
Handling file extensions
The final piece of the puzzle is telling Paperclip to use the correct file extension when downloading the thumbnails. Using the :extension interpolation provided by Paperclip won’t work because it uses the file extension of the originally uploaded file (if we upload a ‘.avi’ file then Paperclip would try to load thumbnails with a ‘.avi’ extension too).
My solution was to define a custom interpolation called :content_type_extension in an initializer like this:
Paperclip::Attachment.interpolations[:content_type_extension] = proc do |attachment, style_name|
case
when ((style = attachment.styles[style_name]) && !style[:format].blank?) then style[:format]
when attachment.instance.video? && style_name.to_s == 'transcoded' then 'flv'
when attachment.instance.video? && style_name.to_s != 'original' then 'jpg'
else
File.extname(attachment.original_filename).gsub(/^\.+/, "")
end
end
This essentially does the same thing as the standard :extension interpolation but if the current style is not ‘original’ and the file is a video then it returns a ‘.jpg’ extension.
Putting it all together
So with a processor defined, Paperclip accepting Procs for the :processors option and a custom interpolation to handle the file extension, this is what a model would look like that provides video thumbnailing:
class Attachment < ActiveRecord::Base
has_attached_file :asset,
:styles => { :small => '36x36#',
:medium => '72x72#',
:large => '115x115#' },
:url => '/:class/:id/:style.:content_type_extension',
:path => ':rails_root/assets/:id_partition/:style.:content_type_extension',
:processors => lambda { |a| a.video? ? [ :video_thumbnail ] : [ :thumbnail ] }
def video?
[ 'application/x-mp4',
'video/mpeg',
'video/quicktime',
'video/x-la-asf',
'video/x-ms-asf',
'video/x-msvideo',
'video/x-sgi-movie',
'video/x-flv',
'flv-application/octet-stream',
'video/3gpp',
'video/3gpp2',
'video/3gpp-tt',
'video/BMPEG',
'video/BT656',
'video/CelB',
'video/DV',
'video/H261',
'video/H263',
'video/H263-1998',
'video/H263-2000',
'video/H264',
'video/JPEG',
'video/MJ2',
'video/MP1S',
'video/MP2P',
'video/MP2T',
'video/mp4',
'video/MP4V-ES',
'video/MPV',
'video/mpeg4',
'video/mpeg4-generic',
'video/nv',
'video/parityfec',
'video/pointer',
'video/raw',
'video/rtx' ].include?(asset.content_type)
end
end
In this example the video? method contains the content types that are considered a video file. In your application you will likely want to put this in a configuration file of some sort.
So there you have it: video thumbnailing using FFmpeg and Paperclip. As part of the same project we’ve also managed to implement transcoding video uploads to FLV format using FFmpeg in another Paperclip processor… but that’s a story for another time!


21 comments
Comment on Video thumbnails with FFmpeg and Paperclip by Michael Deering
February 23rd, 2009 @ 00:27 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by Michael Deering
February 23rd, 2009 @ 01:53 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by Rob Anderton
February 23rd, 2009 @ 08:44 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by Jon Yurek
February 23rd, 2009 @ 15:07 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by Moodang
February 26th, 2009 @ 15:31 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by Moodang
February 26th, 2009 @ 15:33 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by Rob Anderton
February 26th, 2009 @ 17:31 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by nap
February 28th, 2009 @ 02:30 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by Thomas Glasgow
April 21st, 2009 @ 21:13 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by Thomas Glasgow
April 21st, 2009 @ 22:56 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by Rob Anderton
April 21st, 2009 @ 23:06 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by Thomas Glasgow
April 22nd, 2009 @ 12:04 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by Thomas Glasgow
May 11th, 2009 @ 21:30 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by Rob Anderton
May 12th, 2009 @ 08:19 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by kadoudal
July 21st, 2009 @ 11:55 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by Marc Lainez
September 25th, 2009 @ 13:21 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by Leandro Pedroni
October 1st, 2009 @ 11:03 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by Bert Goethals
November 18th, 2009 @ 15:13 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by Rob Anderton
November 19th, 2009 @ 10:28 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by Glenn
November 21st, 2009 @ 16:09 – permalink
Comment on Video thumbnails with FFmpeg and Paperclip by Rob Anderton
November 27th, 2009 @ 10:38 – permalink
Leave a reply
You can use Markdown in your comment as well as plain HTML. You can use
<filter:jscode lang="ruby">and</filter:jscode>tags to surround code blocks (supported languages are css, html, javascript and ruby). Your email address will not be published.If your comment doesn’t appear immediately after posting it could have been marked as spam. Don’t worry: we regularly check for and approve incorrectly filtered comments so you shouldn’t have to wait too long for it to be shown.