I am available for freelance work! Click here to email me.

Converting Videos with Rails: Converting the Video

June 3rd, 2008

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 behavior 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:

rake db:migrate

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

(Possibly) Related Posts

Recommend Me

If you found this post or anything else on this site of any use, then please take the time to recommend me on Working with Rails.

You can follow any responses to this entry through the RSS 2.0 feed. Trackback from your own site.

  • Thank's a lot. It works great. :)
  • Chard
    Just a quick reminder, you need to use the swfobjects.js that comes with the flash player zip from JW FLV that you downloaded. Save you some head scratching if you don't see the player loading.
  • Hey, it works! amazing!
    But i have a problem, why it have a confirmation to overwrite file (already exist) on terminal?
    Thanks a lot
  • Zeba
    Hi...
    I tried implementing this...but my video is not getting uploaded using paperclip.
    I have installed paperclip as a gem...as the plugin installation could not be found...
    If I use File_column plugin to upload the video it gets uploaded successfully...but the convert function gives an error...!!!!
    I cant understand where am I going wrong....
    Please can you help me....
    Thanks....
  • Hello, Jim.

    I have a problem - after loading a file on a server, it remains in a folder /tmp with name CGI ***** and nothing occurs... There Are ideas why that occurs? The same most with any loading files in RoR...

    Best regards, Sergey...
  • This is a great article.

    What where you going to use for background processing? I used backgroundrb a while back and it worked well for my purposes.

    Looking forward to part 3!
  • You know what, I think ffmpeg can be installed now (at least on ubuntu hardy) simply via:

    sudo apt-get install ffmpeg
  • Nirvana
    Nice job but I have one issue. I am unable to convert wmv file into flv with ffmpeg. Any suggestion......
  • Very nice writeup, I look forward to the next installment.

    I had some trouble testing uploads. Turns out I'd found my first Rails bug. I'm not holding my breath for the patch to make it into Rails anytime soon since it only applies to Windows, but if anyone's interested, it's available from http://github.com/bryanash/rails/tree

    Thanks again!
    Bryan
  • Jim, great articles.

    I think the old acts_as_state_machine plugin has been replaced by the AASM gem. You can find it over on GitHub:

    http://github.com/rubyist/aasm

    Keep up the good work!
  • Nice writeup! Looking forward to the next one.

    I made one modification to my implementation of this:

    Not adding "@video.convert" to the controller. Instead adding "after_create :convert" to the model. This will only convert new videos.

    I have a form in which the user can add an arbitrary number of videos to a related model so I added "after_save :convert" and then modified 'convert' to not bother with videos that were already converted:

    def convert
    # don't convert the converted!!!
    unless self.state.eql?("converted")
    self.convert!
    success = system(convert_command)
    if success && $?.exitstatus == 0
    self.converted!
    else
    self.failure!
    end
    end
    end

    Thanks again!
  • I actually did a screencast on this subject complete with active messaging background process. My more recent incarnation is without ActiveMessaging, and uses backgroundrb along with paperclip for the file upload, but i have to update the screencast for that stuff. You can see the old screencast, along with all the project files on blog.skiptree.com under the presentations link. A nice wrapper for ffmpeg is RVIDEO. It makes the ffmpeg interaction much prettier.
  • Sam
    Any idea when part 3 will be released (background processing)?
  • Steveo
    Hi Jim,

    Great article, but I have a quick question -- what video formats are accepted using this plugin?

    Thanks,
    Steveo
  • Matz
    Hi,
    maybe this new plugin is a possible solution to do background processes?

    http://opensource.imedo.de/pages/show/background

    BR

    Matz
  • Great article.

    If there are folks like me who would rather not deal with maintaining FFMPEG, we've had a great deal of success off-loading the video encoding to Hey!Watch (www.heywatch.com). They've got a nice Ruby library and they put the files on to S3 after encoding.

    We use it to encode videos on our Church CMS (www.citygates.org).
  • Matz
    Thx for the quick response, you saved my day!

    BR

    Matz
  • Hi Matz,

    I've updated the article to show how to display the video in a view :)
  • Matz
    Hi Jim,

    great article!!! Thx for that. But how would display the video in a view? If you say a pic is displayed like that : ....how would you write this for a .flv- Video?

    Many thx and warm regards

    Matz
blog comments powered by Disqus
Purify - Issue Tracker

Jim Neath is a 26 year old Freelance Ruby on Rails developer from Manchester, UK.

Recommend Me

Categories