
On the Floor
(Note to readers: this is a highly technical tutorial aimed at intermediate to advanced Rails developers who wish to add video encoding capabilities to their application.)
Online video is increasingly popular, so managers, clients, and most importantly users, are clamouring for integrated video capabilities on websites.
However, serving video online can be a tricky proposition. There are only a few video formats that are useful for the web, and many (or most) devices and video editing software suites produce video that falls outside of these formats. Additionally, you may have requirements such as a particular bitrate, a consistent size, or the generation of thumbnail images.
Sites like Vimeo and YouTube offer easy upload and encoding of videos, but very limited control. Integrating FFMPEG directly into your application also has its drawbacks, such as extensive CPU usage, and leaves you dealing with the headache of supporting a myriad of video formats and codecs.
There's an obvious market opportunity for services that do the hard work of researching codecs, developing encoders, and maintaining infrastructure for you. Several exist, but it was a relative newcomer that caught our attention: Zencoder.
Why Zencoder? In a nutshell, Zencoder offers a robust platform, an excellent and easy-to-utilize API, a superb web interface, reasonable pricing with pay-per-encoded-minute plans, and best of all, easy access to core developers who are willing to work with you to develop superb video encoding solutions.
No matter how easy they try and make it, however, video encoding is not an easy addition to any Rails project. This tutorial walks you through the process, focusing on all of the hard parts and providing plenty of source code that you can use in your projects. Views and standard controller actions (e.g. delete) are excluded - your Rails skills should be at a level where these are trivial for you.
Requirements
This relies on using Paperclip for file attachments, although the integration with Zencoder would work equally well using any other method for file uploads. The assumption that you have installed Paperclip and are reasonably familiar with it is made throughout this tutorial.
In our content management system, we use SWFUpload to upload the original video files, as they are often very large (up to a couple of gigabytes), so a regular HTTP upload is not suitable. We'll leave the details of that implementation up to you as there are plenty of tutorials on how to use SWFUpload and related technologies, and you may also be using something else to get the videos up to your server before transfer to S3 via Paperclip (e.g. FTP).
This also demonstrates using S3 to host the video files as this is the easiest way to retrieve the output files that are produced by Zencoder; as well, it's one of the cheapest ways to host large files. If you aren't interested in the S3 integration portion of this tutorial, you can skip the S3-focused portions and read just the Zencoder-related portions if you wish.
You'll need an account with S3 (https://s3.amazonaws.com/) and with Zencoder (http://zencoder.com - free accounts available for testing purposes) to complete this tutorial.
Integrating Your Video Model With S3 And Paperclip
Start with the model for which you want to encode video - in this case, we'll be using the obvious choice Video. We'll add in the Paperclip fields that are necessary for storing information about the video and a thumbnail image that we'll ask Zencoder to create for us.
Start by getting the Amazon S3 gem with gem install aws-s3.
In config/environment.rb, inside your Rails::Initializer.run do |config| block, add:
config.gem "aws-s3", :lib => "aws/s3"
Underneath the config block (or in an initializer if you prefer), add:
require 'aws/s3'
Now, assuming that you have a model called Video already, add fields to it for handling the attachments and some additional information that we'll be retrieving or setting with script/generate migration add_video_fields:
Now it's time to tell the Video model to use S3 for its attachments. In this case, we'll be using S3 specifically for the video attachments, allowing other storage (such as the file system) to be utilized for other attachments as necessary. If you wish, you can modify these instructions to use S3 for all attachments. In the next gists, comments will indicate where and what adjustments you would make to utilize S3 for all attachments instead.
Create (or modify if it exists already) the file config/initializers/paperclip_defaults.rb:
(Don't forget to restart your app after making this change, as without a restart these new settings won't be loaded.)
Note that this references a file called config/s3.yml. We need to create that as well if it does not exist, and put our S3 access keys into it. We'll add the standard development, test and production buckets.
development: bucket: YOUR-PROJECT-NAME_development access_key_id: # obviously, your actual S3 keys will go in here secret_access_key: test: bucket: YOUR-PROJECT-NAME_test access_key_id: secret_access_key: production: bucket: YOUR-PROJECT-NAME access_key_id: secret_access_key:
Now we need to declare that Video has attached files for the video and the thumbnail image. Open up app/models/video.rb and add somewhere near the top:
Now it's time to set up your buckets in S3. There are lots of tools for this, but Amazon has released a decent web-based console that you can use.
Create buckets that correspond with the ones you've defined in config/s3.yml, so in this case, buckets for YOUR-PROJECT-NAME_development, YOUR-PROJECT-NAME_test, and YOUR-PROJECT-NAME. While you're at it, create a bucket for the encoded files that Zencoder will create (we'll store the bucket information in a separate config file that we create just for Zencoder). Call it YOUR-PROJECT-NAME_encoded.
Note that bucket names must be unique across all of S3.
Integrating With Zencoder
Now the fun begins. We'll be taking a lightning quick tour, source code provided, for how to integrate with Zencoder for video encoding that will impress your clients like practically nothing you've done before. We're not going to dwell too much on the technical details for how the integration works, for that, you can read their API.
Start by granting Zencoder the appropriate permissions to the S3 buckets we just created. Grant "list" and "upload/delete" permissions to the buckets (why the console does not display "READ", "WRITE", "READ/WRITE" and so on is beyond me) like so: select the bucket in the web-based console, click on Properties, select Add more permissions, enter "aws@zencoder.com" as the grantee, then check the appropriate checkboxes and click Save.
You should do this for four buckets (development, test, production, and encoded).
Now you need to add a method that will accept notifications from the Zencoder service to the controller, which I'm assuming is called videos_controller. (This is also where you'll have your new, edit, create, update, etc. methods for videos, which I'm assuming you are fully capable of creating yourself - you will need at minimum new and create actions so you can upload the original videos). Just drop in this dummy method:
# capture notifications from the Zencoder service about video encoding def encode_notify end
If your videos controller is under access control (e.g. it is part of an admin panel), you're going to want to skip the before_filters or whatever you are using for access control, for the encode_notify action. You're also going to want to drop the CSRF protection for that method:
protect_from_forgery :except => [:encode_notify]
Of course, you can also create a separate controller just to capture notification requests if you don't want to follow this approach.
Add in the route for this action as well - it should be a :post request.
We've now set up enough stuff that we can create a configuration file for Zencoder. Create a YAML file called zencoder.yml in the config folder, and drop in some settings for the encoded bucket and some other key information about your app and the way we want videos to be encoded. It should look like this, as a minimum:
s3_output: bucket: YOUR-PROJECT-NAME_encoded access_key_id: YOUR-ACCESS-KEY-ID secret_access_key: YOUR-SECRET-ACCESS-KEY settings: notification_url: http://YOUR-ENCODE-NOTIFY-URL-HERE, e.g. http://myproject.com/videos/encode_notify
Note that for your app to automatically receive updates from Zencoder about video status, your encode_notify path must be accessible by Zencoder, i.e. the app must be online. However, Zencoder provides a handy alternative way to receive updates about video status that works well in development mode, which we'll cover in a minute.
Now we'll add a private method to our video model to retrieve these settings from the configuration file:
def zencoder_setting
@zencoder_config ||= YAML.load_file("#{RAILS_ROOT}/config/zencoder.yml")
end
We can use in our video model to retrieve settings, as in zencoder_setting["s3_output"]["access_key_id"].
Next up, we're going to integrate with the Zencoder API. To do this, we're going to build a simple class based on HTTParty. Start by installing HTTParty with config.gem 'httparty' in your config.rb and then rake gems:install, or just gem install httparty.
Now drop a file into lib called zencoder.rb. Here's some code you can use that will take care of the basics. This has several limitations, including but probably not limited to just one output format and just one thumbnail, but you can modify for your purposes as you see fit (be sure to put in your API key):
Your video model is going to need some code that will deal with S3 and the Zencoder class we just created. This example model has everything you need for that in it, with everything else that you'll probably want in your app removed (for example, stuff dealing with user associations, commenting, swfupload, etc.) The comments provide useful information about how the methods work:
You'll also need code in your controller that will trigger the video encoding, as well as a fleshed out encode_notify method that will accept notifications from Zencoder.
In our case, because we are using SWFUpload for uploading videos, all of our videos go through a single method, regardless of whether the video is a new or an updated video. That lets us drop in @video.encode! just in that one method.
Here are example methods that show encoding and the encode notification:
So long as you've ensured that the critical bit here, @video.encode!, exists in whatever method you are using to upload your video (convention would indicate 'create' and 'update', but as stated previously we're using SWFUpload due to large file sizes), encoding with Zencoder should commence automatically.
When it's done, Zencoder will attempt to notify your application that it is complete. This will fail, however, because in all likelihood your application will not be accessible by Zencoder when you're working locally.
What you need is a different way to notify your application that encoding is complete, one that works on your local development machine. Luckily, Zencoder has provided this with the zencoder-fetcher gem: gem install zencoder-fetcher
You'll use this gem like so: zencoder_fetcher -u http://localhost:3000/ENCODE-NOTIFY-PATH API-KEY. This will notify your application of the status of any encoding jobs.
In your views, you can instantiate a video and display its status with video.encoded_state If its encoded state is "finished", then display the video! It's accessible at video.output_url. And, of course, you can display thumbnails with Paperclip's standard syntax, e.g. video.thumbnail(:original) for a full-sized thumbnail of the frame.
I realize this is a ton of code and a lot of concepts. If you have trouble, feel free to add a comment below - I'll get an email when you do and if I'm able I'd be happy to help.
Subscribe 
Follow us on
Twitter 
Archives
February 2012January 2012
December 2011
November 2011
October 2011
September 2011
August 2011
July 2011
May 2011
April 2011
March 2011
February 2011
January 2011
December 2010
November 2010
October 2010
September 2010
August 2010
July 2010
June 2010
May 2010
April 2010
March 2010
February 2010
January 2010
December 2009
November 2009
October 2009
September 2009
June 2009
March 2009
January 2009
December 2008
November 2008
September 2008
August 2008









Comments
Wow! Good stuff. I'm actually in the process of implementing a similar process using Zencoder so this will be a huge help. I'll update if I have any issues and/or suggestions. Thanks for the great post!
Any chance we can see your javascript and _form - i have seen lots of swfupload tutorial and they all approach different to this - Keen to get them both working though so would really help.
Thanks for great tut!
Hey Monkey,
Glad you liked the tutorial. Yes, you can see the Javascript and HTML we're using - in fact, you can actually see a public upload version of this at:
http://kingshitmag.com/videos/new
You should be able to get a good idea of how we're handling it there.
Note that the site I linked to is live, of course, and this area is intended for the submission of skateboarding videos.
Note that if you are doing this in Rails 3 you will need to add the lib/zencoder.rb to you app in application.rb's autoload paths. EG:
config.autoload_paths += Dir["#{config.root}/lib/**/"]
Adrian, what would you recommend as the best way to add more outputs to the zen jobs? EG: doing WebM OGV and etc.
Hi Mike, thanks for the note re. Rails 3.
To answer your question re. multiple outputs, well, since I wrote this tutorial a Zencoder gem has been released:
https://github.com/zencoder/zencoder-rb
Looking that over might be a good place to start, in terms of a simple way to interact with the API. However, to modify my approach to handle different outputs should be fairly straightforward.
First of all, you'd want to modify the file attachment code to support additional files in the file formats that you wish to use.
Secondly, you'd want to modify my Zencoder class to request multiple outputs. You can read how to do that at:
https://app.zencoder.com/docs/guides/getting-started/creating-multiple-outputs
Thirdly, you're going to want to adjust the Video class to handle notifications for multiple outputs (see https://app.zencoder.com/docs/guides/getting-started/notifications) as well as the remove_encoded_video method.
Add a Comment