For a recent Rails 3.1 project one of the features requested was to allow users to painlessly upload videos and have them all look consistent. We choose Zencoder for our video encoding service and Rackspace Cloud Files for storage. We were already using the gem CarrierWave for attachments and the jQuery plugin Uploadify for uploading files.
Having large files uploaded through Uploadify works really well. As soon as a file is selected, it starts to upload; this is a much better experience than making a user wait while a video uploads over a standard request. This post will outline all the steps to get a video uploaded, encoded, and displayed to your users.
This is a short rundown of what will happen:
- User uploads video using Uploadify
- Carrierwave uploads the video to Rackspace
- After upload is complete a video encoding job is submitted to Zencoder
- Zencoder retrieves the uploaded video file from Rackspace
- Zencoder encodes the video and replaces the uploaded file on Rackspace
- Zencoder calls back to the server to notify that the job has completed
- Encoded video is now available to user
The complete source code for this blog post is available on my Github account. You can just update it with your own Zencoder and Rackspace account credentials and it should work.
Start out with a fresh Rails 3.1 project.
Lets get all the dependencies out of the way first.
- Update your /app/assets/stylesheets/application.css manifest file to include the Uploadify stylesheet:
- Add the following gems to your Gemfile
Configure Rackspace & CarrierWave
Now that we have CarrierWave installed we need to configure it to use Rackspace to store uploaded videos. CarrierWave uses the gem called Fog to manage remote file storage. This allows CarrierWave to use any storage backend. Fog has support for Amazon S3 and Google Storage for Developers as well, either of those options can easily be substituted.
I’m going to assume you have already signed up for Rackspace Cloud Files:
- Log in and click on Your Account and then API Access and make note of your API key.
- Add a container called blog.uploads (or whatever your in the mood for).
- Click on the container you created in the listing and you should be able to change some properties:
- Check the box Publish to CDN.
- There should now be a CDN URL listed, make note of that.
- CarrierWave is best configured using a Rails initalizer. Under /config/initializers create a new file named carrierwave.rb and put the following code into it.
(Update it with the username you used when signing up for Rackspace Cloud Files, the API key fro your account. Under config.fog_host put the CDN URL.)
If you want files served over SSL just update the URL’s second subdomain to be https. Example:
- Over HTTP: http://c293023.r87.cf2.rackcdn.com
- Over HTTPS: https://c744687.r87.ssl.rackcdn.com
I’m going to assume you have a Zencoder account.
- Login and click API. Take note of the key.
- Create a Rails initalizer (/config/initalizers/zencoder.rb) for zencoder.
Add Rack middleware to allow file uploads through flash
Rails has built in cross site request forgery protection. When a form is submitted a token is sent along with it. Since Uploadify is flash based we need to create a Rack middleware that tacks this parameter on to the request from flash.
I followed the instructions in this post by Damian Galarza on how to do this. He gives a very thorough explanation of uploading to Rails using Uploadify, I’m gonna shorten it a bit to just include all the code that’s needed to get it up and running.
- Create a a middleware folder under /app and a new file named flash_session_cookie_middleware.rb
- Here is the middleware code: /app/middleware/flash_session_cookie_middleware.rb
- Update your (config/application.rb) and tell it to autoload the middleware.
- Edit your session_store (/config/initalizers/session_store.rb) initializer to include middleware.
Create a Video model
Create a model to store our uploaded video information. It will have an attachment column for the filename used by CarrierWave, a column to put the output id from Zencoder and a processed boolean flag.
- Create Model
- Migrate your database
- In your video.rb model add a method to update the processed flag.
Create Zencoder callback controller
This controller will handle the callback from Zencoder when an encoding job is complete. Zencoder sends data as JSON.
- Generate controller
- Update routes (/config/routes.rb)
- Update controller (/app/controllers/zencoder_callback_controller.rb)
Whats going on in the controller?
- Since Zencoder will be sending a POST request and won’t have the authenticity token we should skip checking it.
- Remove the controller and action parameters from the params hash since they aren’t needed, this will leave just the JSON from Zencoder.
- We had to unescape the string prior to parsing it with JSON.
- After parsing it you should be able to pull the output id and state. If the state is finished and a video was found via the output id then flag it as processed using the method we created on the video model.
Create CarrierWave uploader
One of the things I really like about CarrierWave is that it pushes all the attachment processing code off into it’s own reusable class called an uploader. Next up is creating an uploader to handle videos.
- Generate an uploader
- Update the generated uploader, make sure you remove the storage :file line, we configured this in the CarrierWave initalizer
- Update the extension whitelist to include video formats you accept
- Uploaders have callbacks, similar to ActiveRecord models. This is where we’ll tell Zencoder the file has been uploaded.
Creating a Zencoder job
- Input should be the location of the video that was uploaded
- Outputs should be an array. You can tell Zencoder to encode multiple formats, just label each hash of options appropriately (web, mobile, etc.)
- After a job is submitted Zencoder will respond with an array of jobs that it has created. We need to loop over the array of jobs from Zencoder and grab the output id and update the model.
- The notifications option is where we’ll tell Zencoder to we want to receive the callback. This is the controller we created earlier. In order to receive the callback Zencoder must be able to connect to your server, so it needs to be on the open internet, so edit (/config/application.rb)
- Include the Rails.application.routs.url_helpers module, since routes aren’t available in uploaders.
- Set the default url to be the host of your callback, if you want to just use the same host as your email just add: Rails.application.routes.default_url_options = ActionMailer::Base.default_url_options
- Use the ssl protocol in the callback url if your site is running ssl.
- Edit (/app/uploaders/video_uploader.rb)
- Mount the uploader on the video model to the attachment column. Put this line somewhere in your model (/app/models/video.rb).
Create Videos controller
We can now create a controller to tie it all together
- Generate controller
- Update your routes (/config/routes.rb):
- Your controller (/app/controllers/videos_controller.rb) is pretty basic, the create action just needs to return success or failure on uploading the video. The Uploadify script has success and failure functions that we’ll use.
- Edit your template (/app/views/videos/index.html.erb)
- The show view (/app/views/videos/show.html.erb) will either show a processing message or the video.
For a more full featured video player with Flash based fallback see http://videojs.com/
- Your new (/app/videos/new.html.erb) view doesn’t need a form, just an element in the DOM to attach the flash uploader to. Use the asset_path helper for the uploadify.swf and cancel.png paths, this is used to correctly reference the files in the asset pipeline.
Try it out!
Start your server and try uploading a video!
The full source code for this is located at:
(The video format in this post only works in Chrome with the HTML5 Video tag used) Good luck!