Quick Tutorial today to get a simple Video Model setup and going with Flowplayer today. We want to be able to upload videos and convert them to flash .flv files. For this I'll be using ffmpeg. Other plugins in this tutorial include acts_as_state_machine and Rick Olsen's attachment_fu. Lets get started!
Note: You can find the source code for this app on github at http://github.com/balgarath/video-app/tree/master. EDIT: I've been working on this, so if you want to see the code specific to the tutorial, click on the Tutorial branch on github.
1. Setup
First, lets create an empty app
rails videoapp -d mysql
Do all your basic setup...(edit database.yml, remove public/index, ...). In routes.rb, add this line:
map.root :controller => "videos"
map.resources :videos
The first line maps '/' to the videos controller, and the next establishes RESTful routes for videos.
Now we need to figure out what flash video player we want to use. For this tutorial, I will be using FlowPlayer, an open-source flash video player. You will need to download flowplayer.zip from http://flowplayer.org/download/index.html. Unzip it to /public. Grab the /example/flowplayer-3.0.2.min.js file and put it in /public/javascripts/flowplayer.js. Put example/style.css in /public/stylesheets.
Next we need to install attachment_fu to handle file downloads.
./script/plugin install http://svn.techno-weenie.net/projects/plugins/attachment_fu/
And you will need ffmpeg for converting uploaded file to .swf. This works for me in Ubuntu...
sudo apt-get install ffmpeg
And last, lets install the acts_as_state_machine plugin. We will be using it to keep track of the current state of conversion for the video:
./script/plugin install http://elitists.textdriven.com/svn/plugins/acts_as_state_machine/trunk/
2. Database/Model Setup
Now we are ready to set up the model. First, run this command to generate the files for you:
./script/generate model Video
This last line will also generate a create_video migration in db/migrate/. Open up that file and put this in it:
class CreateVideos < ActiveRecord::Migration
def self.up
create_table :videos do |t|
t.string :content_type
t.integer :size
t.string :filename
t.string :title
t.string :description
t.string :state
t.timestamps
end
end
def self.down
drop_table :videos
end
end
Content_type, size, and filename are used by the attachment_fu plugin. The state field will be used by the act_as_state_machine plugin to keep track of video converting.
Lets move on to the model; open up /app/model/video.rb and add this into it:
has_attachment :content_type => :video,
:storage => :file_system,
:max_size => 300.megabytes
#turn off attachment_fu's auto file renaming
#when you change the value of the filename field
def rename_file
true
end
This is for the attachment_fu plugin. I came across a feature of attachment_fu: when you change the filename of an attachment, the old file automatically gets moved to whatever the new filename is. Since I am creating a new file and changing the filename attribute of the video the reflect that, I don't need this on...so I just overrode the method in the Model.
Since we will be using the acts_as_state_machine plugin to keep track of the state of conversion for videos, lets go ahead and add in the states we will be using. Add this to video.rb:
#acts as state machine plugin
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 :failure do
transitions :from => :converting, :to => :error
end
Note: I got some of this part from Jim Neath's tutorial.
Now we can use @video.convert!, @video.converted!, @video.failed! to change the state of a particular Video. The last code we need to add to the model is this:
# 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
def convert_command
#construct new file extension
flv = "." + id.to_s + ".flv"
#build the command to execute ffmpeg
command = <<-end_command
ffmpeg -i #{ RAILS_ROOT + '/public' + public_filename } -ar 22050 -ab 32 -s 480x360 -vcodec flv -r 25 -qscale 8 -f flv -y #{ RAILS_ROOT + '/public' + public_filename + flv }
end_command
logger.debug "Converting video...command: " + command
command
end
# This updates the stored filename with the new flash video file
def set_new_filename
update_attribute(:filename, "#{filename}.#{id}.flv")
update_attribute(:content_type, "application/x-flash-video")
end
A lot of stuff going on here. Convert will be called from the create action. When called, it sets the state of the video to 'convert' and runs ffmpeg to convert the file to flash(.flv). It will then mark the file as either 'converted' or 'failed'.
Now that that is all done, we can create our database and run the migrations:
rake db:create
rake db:migrate
3. Controller/Views
Now we can generate our controller, model, and view files:
./script/generate controller videos index show new
Open up /app/controllers/videos_contoller.rb and put in this code:
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
def delete
@video = Video.find(params[:id])
@video.destroy
redirect_to :action => 'index'
end
end
In the create method, notice that if the video save is true, @video.convert gets called, which convert the video to .flv.
Views
Create the file /app/views/layouts/application.html.erb and put this in it:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Video Tutorial</title>
<%= stylesheet_link_tag 'style' %>
<%= javascript_include_tag 'flowplayer' %>
</head>
<body>
<h1>Video Tutorial</h1>
<% flash.each do |key,value| %>
<div id="flash" class="flash_<%= key %>" >
<span class="message"><%= value %></span>
</div>
<% end -%>
<%= yield :layout %>
</body>
</html>
Now for our index view (/app/views/videos/index.html.erb)
<h2>Videos</h2><br />
<%= link_to 'New Video', new_video_url %><br />
<% for video in @videos do %>
<p><%= link_to video.title, video %></p>
<% end %>
/app/views/videos/new.html.erb:
<h2>New Video</h2><br />
<% form_for(@video, :html => { :multipart => true }) do |f| %>
<%= f.error_messages %>
<table>
<tr>
<td><%= f.label :title %>: </td><td><%= f.text_field :title %></td>
</tr>
<tr>
<td><%= f.label :description %>: </td><td><%= f.text_area :description %></td>
</tr>
<tr>
<td><%= f.label :video %>: </td><td><%= f.file_field :uploaded_data %></td>
</tr>
<tr><td><%= f.submit 'Submit' %> - <%= link_to 'Back', videos_path %></td></tr>
</table>
<% end %>
This is the upload form. Notice I used f.file_field :uploaded_data...this is for attachment_fu to work. Next is /app/views/videos/show.html.erb:
<h1><%= @video.title %></h1>
<p><%= @video.description %></p>
<a
href="<%= @video.public_filename %>"
style="display:block;width:400px;height:300px"
id="player">
</a>
<!-- this will install flowplayer inside previous A- tag. -->
<script>
flowplayer("player", "/flowplayer/flowplayer-3.0.3.swf");
</script>
And that should do it. ./script/server and try uploading a movie file and see if it works. Also, you could probably mess around some with the call to ffmpeg and increase video quality. There are some good posts if you search for 'ffmpeg' over at the FlowPlayer Forums, and if you purchase a commercial license for the player, you can remove the advertising and get some new features, as well as support. Thanks for reading!
Update:
For a quick way to put the video conversion on a background process, I was able to use Tom Anderson's Spawn plugin. Note that this wouldn't be a very efficient way to scale it if you expect to have a lot of users uploading at the same time, as it forks another process to handle the conversion. This does work well if there aren't a bunch of videos getting uploaded at the same time. If you do need to scale the uploading, I recommend using Ap4r(Asynchronous Processing for Ruby). Here we go!
First, lets install the Spawn plugin from github:
./script/plugin install git://github.com/tra/spawn.git
And next, change the convert method in Video.rb to this:
# This method is called from the controller and takes care of the converting
def convert
self.convert!
#spawn a new thread to handle conversion
spawn do
success = system(convert_command)
logger.debug 'Converting File: ' + success.to_s
if success && $?.exitstatus == 0
self.converted!
else
self.failure!
end
end #spawn
end
And thats it! Now when you upload a video, you don't have to wait for the server to convert the video file over to flash.
24 comments:
Really nice tutorial. I've implemented a few systems like this, and this works well before you need to worry about distributing jobs across machines. (Which we did with http://zencoder.tv, and which is a lot of work.)
Also check out the Ruby RVideo gem if you want to abstract out the ffmpeg command to flexible recipes.
Jonathan,
thanks for the input, I'll have to check out the RVideo gem and see about putting it into the tutorial. I also want to look at handling conversion in a separate process, so that the user's request can finish being handled as soon as the file finishes uploading, and not have to wait on conversion. Does RVideo handle doing any of this?
Hey cool.
Speaking of RVideo, there's also RTranscoder. It supports mencoder and mp4creator.
Thanks for the tutorial. I went thru the steps but the flowplayer does not play the video. ffmpeg seems to convert the video. But when downloaded the converted video it won't play in a flv player either. I have tried this on a wmv and mpeg video file. Do I need to tweak the ffmeg args?
@Navjeet: looks like that skip_filter I put in doesn't quite work. try adding the following to video.rb:
def rename_file
true
end
The problem is that when you convert the file to flv and then tell attachment_fu to point at the new file, attachment_fu automagically renames the old file to the new file new, resulting in the old file(mpeg, whatever) getting the .flv extension, but still being of the old file type. This should fix it, and I'll get the tutorial and github files updated soon
I followed your instructions and got most of the stuff to work.
It appears to upload the video but I can't find any flv files or the original mov files on my server. Suggesting that it just refreshes the page after a few seconds.
So it doesn't appear to be uploading anything.
Adding the rename_file as suggested did the trick. It is working now. Thanks.
Actually the video part is working but not the audio, any suggestions
@Navjeet - you may need to install ffmpeg by hand with the correct options, depending on your system. On Ubuntu Hardy and Intrepid, 'sudo apt-get install ffmpeg' gives me what I need.
From what I've read Paperclip is waay better than attachment_fu.
http://thoughtbot.com/projects/paperclip/
Nice post
i followed the tutorials but it doesnt seem to upload the video. Coz when it redirects to index, there are no videos listed which is supposed to appear on the page.
Tutorial always helpful for us and we get lots of information through tutorial. This tutorial is also very informative for me. Keep sharing more information.
Hi Ralph,
thanks for your excellent tutorial.
I have followed the tutorial, but unfortunately, the show.html.erb page doesn't display the video using FlowPlayer.
I get a white page, although it does appear to have loaded the .flv file into the page. When I click on the page it downloads the .flv file to my computer.
I'm sure I'm making a basic mistake.
Any ideas would be greatly appreciated.
Regards
Walter
Hello,
I've gone through this article and everything works fine for me.. when I am watching the video.. it is taking the old format(like DAT, MPEG) whereas it should show the converted flv format, so that the flowplayer will play that video
show.html.erb
@video.public_filename
If I m using that syntax it is taking the old file..
Any ideas would be highly appreciable.
Thanks
Puneet
Hi i have followed this to the T and it works perfectly on Development... when i upload and configure it on my production server... which is on ubuntu it looks like it is trying to upload the video but it hangs... just continuely loading and it doesn't create a DB entry or nothing... nothing in the logs and i have searched everywhere for the possible problem. any ideas what this could be?
Coops: I'm assuming your production environment is not on localhost...the video uploads can sometimes take quite a while(depends on your upload bandwidth, which for most people is very low). How big was the video you tried to upload? Have you tried a small one(<1-2 MB)? If the multi-part post request doesn't finish, no records will be created.
excellent work....
Thanks for the tutorial.
Great tutorial, worked perfectly the first time! Thanks!
-Kelly
Thanks so much for sharing this tutorial...
Great Work and nice tutorial.
Iraqi Dinar
The Flash Video Tutorial with Rails, ffmpeg, FlowPlayer is very useful to me.make extra money online Juy
its very good tutorial
Hi,
i am using rails 1.2.6 i have did all syeps in this tutorial, if run i am getting the error:----
NoMethodError in VideosController#index
undefined method `has_attachment' for Video:Class
RAILS_ROOT: ./script/../config/..
Application Trace | Framework Trace | Full Trace
/home/innoppl10/.rvm/gems/ruby-1.8.7-p357@faxjax/gems/activerecord-1.15.6/lib/active_record/base.rb:1238:in `method_missing'
/home/innoppl10/faxjax_rails/app/models/video.rb:3
/home/innoppl10/faxjax_rails/app/controllers/videos_controller.rb:4:in `index'
could u plz tell, wats the prob is..........
Post a Comment