So you’ve installed FFMPEG. Now it’s time to move onto converting the video. For this we’re going to be using a couple of plugins.
Paperclip
We will be using the paperclip plugin to upload the videos onto our server. Details of how to install paperclip can be found in my previous article: Paperclip: Attaching Files in Rails. To install you can use any of the following:
git://github.com/thoughtbot/paperclip.git # Github
https://svn.thoughtbot.com/plugins/paperclip/trunk/ # SVN
http://rubyforge.org/projects/paperclip/ # Gem version
Acts As State Machine
Acts_as_state_machine allows you to turn your model into a Finite State Machine (FSM).
A finite state machine (FSM) or finite state automaton (plural: automata) or simply a state machine, is a model of behaviour composed of a finite number of states, transitions between those states, and actions. A finite state machine is an abstract model of a machine with a primitive internal memory.
So let’s install acts_as_state_machine:
ruby script/plugin install http://elitists.textdriven.com/svn/plugins/acts_as_state_machine/trunk/ acts_as_state_machine
Creating the Model
First off, let’s create our video model. We’ll call it video. Inventive, eh?
ruby script/generate model video
Open up your new video model and let’s edit it to look like this:
class Video < ActiveRecord::Base
# Paperclip
# http://www.thoughtbot.com/projects/paperclip
has_attached_file :source
# Paperclip Validations
validates_attachment_presence :source
validates_attachment_content_type :source, :content_type => 'video/quicktime'
end
Nothing spectacular. If you’ve used Paperclip before then nothing should surprise you here. The has_attached_file :source line tells our model that it has an uploaded file called source. This is where we will be storing our video files. The rest of the file uses the built in Paperclip validations.
Adding States
Underneath your current model content, we want to add the following:
# Acts as State Machine
# http://elitists.textdriven.com/svn/plugins/acts_as_state_machine
acts_as_state_machine :initial => :pending
state :pending
state :converting
state :converted, :enter => :set_new_filename
state :error
event :convert do
transitions :from => :pending, :to => :converting
end
event :converted do
transitions :from => :converting, :to => :converted
end
event :failed do
transitions :from => :converting, :to => :error
end
This will setup acts_as_state_machine along with the states and transitions we want to use. It’s all pretty simple.
Run the Migrations
Open up the xxx_create_videos.rb migration file and edit it so it looks like the following:
class CreateVideos < ActiveRecord::Migration
def self.up
create_table :videos do |t|
t.string :source_content_type
t.string :source_file_name
t.integer :source_file_size
t.string :state
t.timestamps
end
end
def self.down
drop_table :videos
end
end
The columns starting with :source_ are all files for Paperclip. The :state column is used with acts_as_state_machine.
Run the migrations:
Adding the Conversion Methods
In your video.rb file add the following methods to your model. These methods take care of the converting the video file:
# This method is called from the controller and takes care of the converting
def convert
self.convert!
success = system(convert_command)
if success && $?.exitstatus == 0
self.converted!
else
self.failure!
end
end
protected
# This method creates the ffmpeg command that we'll be using
def convert_command
flv = File.join(File.dirname(source.path), "#{id}.flv")
File.open(flv, 'w')
command = <<-end_command
ffmpeg -i #{ source.path } -ar 22050 -ab 32 -acodec mp3
-s 480x360 -vcodec flv -r 25 -qscale 8 -f flv -y #{ flv }
end_command
command.gsub!(/\s+/, " ")
end
# This update the stored filename with the new flash video file
def set_new_filename
update_attribute(:source_file_name, "#{id}.flv")
end
The Controller
I’m not going to post the views in this article as they should be straight forward. Just make sure to add :multipart => true to your form otherwise your file’s won’t be uploaded and you’ll look stupid.
class VideosController < ApplicationController
def index
@videos = Video.find :all
end
def new
@video = Video.new
end
def create
@video = Video.new(params[:video])
if @video.save
@video.convert
flash[:notice] = 'Video has been uploaded'
redirect_to :action => 'index'
else
render :action => 'new'
end
end
def show
@video = Video.find(params[:id])
end
end
After the video is saved in the create method, the convert method is called. This should convert the video to a flash video file, update the database entry and set the state of the model. The state will be set to converted if everything went to plan, or error if everything went to shit.
You can view the full source of video.rb here.
Displaying the Video in a View
When you want to display the video in a view, you will need two things. The first, is swfobject, an unobtrusive way to include flash into a page.
SWFObject is a small Javascript file used for embedding Adobe Flash content. The script can detect the Flash plug-in in all major web browsers (on Mac and PC) and is designed to make embedding Flash movies as easy as possible. It is also very search engine friendly, degrades gracefully, can be used in valid HTML and XHTML 1.0 documents, and is forward compatible, so it should work for years to come.
The second is a flash video player. I highly suggest JW FLV Media Player.
The JW FLV Media Player (built with Adobe’s Flash) is an easy and flexible way to add video and audio to your website. It supports playback of any format the Adobe Flash Player can handle (FLV, but also MP3, H264, SWF, JPG, PNG and GIF). It also supports RTMP and HTTP (Lighttpd) streaming, RSS, XSPF and ASX playlists, a wide range of flashvars (variables), an extensive javascript API and accessibility features.
Make sure you include the swfobject.js file on the page you wish to display your video on:
<%= javascript_include_tag 'swfobject' %>
Next, copy the mediaplayer.swf to public/flash/mediaplayer.swf
<div id="player">
You need to have <%= link_to 'Flash Player', 'http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash' %> installed to display this video
</div>
<script type="text/javascript">
var so = new SWFObject('/flash/mediaplayer.swf', 'mpl', '480', '360', '8');
so.addParam('allowscriptaccess', 'always');
so.addParam('allowfullscreen', 'true');
so.addVariable('height', '360');
so.addVariable('width', '480');
so.addVariable('file', '<%= @video.source.url %>');
so.write('player');
</script>
The contents of the div will be replaced with the flash player, if flash isn’t installed the user is shown a message with a link to download flash player. The <%= @video.source.url %> line will output the path to the flv video file.
There’s a setup wizard on the JW FLV Player site located here.
Final Note
You shouldn’t really be converting videos with the user request. You should be using a background worker to do it for you. I’ll be showing you how to do this in the next article in this series.
Give it a whirl. As far as I know it should work fine. I’ve tested it on my local host and it seems to work. Admittedly, there should be a lot more error checking and whatnot, but for a simple example I think it does the job.
If you have any suggestions, improvements etc that I could incorporate into this article then leave a comment and I’ll sort it out.
More In This Series
Paperclip is an awesome rails plugin by Jon Yurek at Thoughtbot. It is one of many plugins currently available that cater for file uploading and thumbnailing (see: Attachment_fu, file_column, etc). Now a quick quote from Jon:
For some reason, file attachment is annoying. I don’t know why, and I know a lot of people have attempted to solve the problem in the past, myself included. Yet it still is. Having gotten fed up with gotchas and design decisions that we didn’t agree with, I went and wrote Paperclip on the plane to RailsConf last year. We’ve been using it here in various forms since and IMHO it’s the way to handle uploads, and finally decided that it should be released.
Installing Paperclip
You can install Paperclip using a variety of different methods:
svn export https://svn.thoughtbot.com/plugins/paperclip/tags/rel_2-0-2
piston import https://svn.thoughtbot.com/plugins/paperclip/trunk
You can also grab Paperclip from the git repository.
Quick Note: If you’re a windows user, you’re going to need to go for the trunk version as this contains a fix to a problem that basically meant that Paperclip borked.
Basic Usage
class User < ActiveRecord::Base
# Paperclip
has_attached_file :photo,
:styles => {
:thumb=> "100x100#",
:small => "150x150>" }
end
Attached files don’t need to have a separate model (thank god). Your attachments are treated just like any other attribute. Images aren’t saved until your model is saved. There are a lot of bonus options but I’ll cover them towards the end of the article.
class AddPhotoToUser < ActiveRecord::Migration
def self.up
add_column :users, :photo_file_name, :string # Original filename
add_column :users, :photo_content_type, :string # Mime type
add_column :users, :photo_file_size, :integer # File size in bytes
end
def self.down
remove_column :users, :photo_file_name
remove_column :users, :photo_content_type
remove_column :users, :photo_file_size
end
end
Don’t forget to add these columns! Otherwise you’ll end up scratching your head and wondering where the hell you went wrong. The first part of the column names is the same as whatever you’re called your attached file. In our case that’s photo. Now update your database:
Now that your database is sorted, we can start working on adding some content. In your view you can add a file field like you would normally:
<% form_for :user, :html => { :multipart => true } do |f| %>
<%= f.file_field :photo%>
<% end %>
Don’t forget the :multipart => true part or everything will fail. Then you will cry.
Now in your controller, you don’t need to do a thing (hooray).
def create
@user = User.create(params[:user])
end
You should now be able to upload user photos to your hearts content.
To display your user’s photos all you need to do is call:
<%= image_tag @user.photo.url %>
<%= image_tag @user.photo.url(:thumb) %>
The first call will display the original image. The second one will display the thumbnail image. Easy, yes?
Paperclip Validations
At the moment Paperclip has two different validation types, validates_attachment_presence and validates_attachment_content_type.
validates_attachment_presence
validates_attachment_presence gives your ActiveRecord style validations to check to see if your paperclip model has an attachment present.
validates_attachment_presence :avatar
validates_attachment_content_type
validates_attachment_content_type lets your check the type of file that has been uploaded by checking it’s mime type.
validates_attachment_content_type :avatar, :content_type => 'image/jpeg'
Paperclip Options
Now that you’ve seen how easy to use and awesome Paperclip is, let’s have a look at some of the additional settings you can use:
has_attached_file :photo, :url => "/:class/:attachment/:id/:style_:basename.:extension"
Using :url you set when your images can be accessed from. The above setting would mean that your files are located at URLs similiar to /user/photo/1/thumb_originalfilename.jpg
has_attached_file :photo, :default_url => "/:class/:attachment/missing_:style.png"
The :default_url option is used if there is no attached file for a model. If a user doesn’t have any uploaded avatar you could use this option to set a default avatar to show.
has_attached_file :photo, :styles => { :normal => "100x100#", :small => ["70x70>", :jpg] }
:styles is a hash of thumbnail styles. The styles use the standard ImageMagick geometry rules. Paperclip also adds the ’#’ option which will create square thumbnails that are nicely cropped.
has_attached_file :photo, :default_style => :thumb
:default_style is pretty straight forward. You can select a default style from your style list that will be called, instead of the original file, when you use @user.photo.url
has_attached_file :photo, :path => ":rails_root/public/:class/:attachment/:id/:style_:basename.:extension"
Using :path you can select where the files are saved to on your box. If you change this, make sure to change the :url setting to relate to the new path.
has_attached_file :photo, :whiny_thumbnails => true
:whiny_thumbnails will raise an error if there is a problem creating thumbnails. Set to true by default.
Some Waffle
Paperclip is a great plugin. It has a smaller memory footprint than Attachment_fu, it doesn’t require the use of Rmagick (eugh) and it has all the options that I’ve wished that Attachment_fu had. Give it a try and let me know what you think
Again, maximum kudos to Jon Yurek and all the guys over at ThoughtBot.