Inline media uploading and processing in ruby
Today, we released a new gem to make integrating off-site processing easy. With this gem and utilizing Amazon’s EC2 cloud and S3 storage back-end, you can easily process media files in a non-blocking, threaded way and do it all inline with the rest of your application.
First download the gem and all it’s dependencies by typing:
sudo gem install processor_pool -y
This gem is built on top of Sinatra. So, if you are familiar with the Sinatra syntax, then you’ll be right at home. If not, I highly suggest you check it out.
First,
# In a file called media_processor.rb
require "rubygems"
require "processor_pool"
# Set the access_key_id and the secret_access_key provided by amazon
access_key_id, secret_access_key = 'accesskey', 'somethinglongandfuzzyhere'
# And start the processors!
ProcessorPool.start(access_key_id, secret_access_key)
Add to the end of the file
get '/' do
"Show me the money"
end
That’s it. Start your server up with
ruby media_processor.rb
And now you are ready to go to allow a user to hit the EC2 cloud (more on this later) and be responded with the “Show me the money” text.
Replace the above content with the following
class PFile
attr_accessor :filename, :temp_file
@@temp_directory_base = "tmp"
def initialize(f)
self.filename = f[:filename]
self.temp_file = f[:tempfile]
@media_file = nil
@temp_dir
end
def delete!
#File.unlink(self.temp_file)
self.temp_file.delete
end
def store_in_temp_directory
@temp_dir = File.join(@@temp_directory_base, @upload.member_id, @upload.media_file_id)
FileUtils.mkdir_p(@temp_dir)
filename = File.basename(self.filename)
FileUtils.mv(self.temp_file.path, File.join(@temp_dir, filename))
@media_file = MediaFile.new(File.join(@temp_dir, filename))
end
def process!
ImageConverter.new(@media_file).convert
@media_file.delete!
"Processed images for #{@media_file.original_file_name}"
end
def save_to_storage
# DO THE S3 saving here
MediaDirectory.new(@temp_dir).copy_to_s3(STORAGE_BUCKET, upload_prefix, true)
end
def upload_prefix
"#{@upload.member_id}/#{@upload.media_file_id}"
end
end
class ImageConverter
def resize_to(name, image = nil)
img = image || Magick::Image.read(self.file.file_name).first
named_size = self.class.named_sizes[name]
if named_size =~ /c$/
dimensions = named_size.gsub(/c$/, '').split('x')
img.crop_resized!(dimensions[0].to_i, dimensions[1].to_i)
else
img.change_geometry!(named_size) do |cols, rows, img|
img = img.resize(cols, rows)
end
end
img.write(path_to(name))
end
end
post '/new' do
@file = PFile.new(params[:file])
@file.store_in_temp_directory
@file.save_to_storage
redirect params[:success_url] if params[:success_url]
end
If you look closely, we are just using some simple classes to handle our conversion. But ignoring that, we are sending a post to the server with file parameters. We can send the process a file and the rest is history. Notice that this all happens on a remote server. None of this is handled locally.
That’s it.
Of course, you can imagine how you could extend this to include movie types and other types of processing, but for the time being, let’s focus the images. (Although I will be showing how to do this in rails, you can use this with any back-end, PHP, Java, etc.)
Let’s add a convenience method in the application.rb
def url_for_upload_server
if RAILS_ENV == 'production'
# determine the upload server to try to use
p = Processors.get_random_processor(::SERVER_POOL_BUCKET)
"http://#{p.hostname}:4567/new"
else
p = Processors.get_random_processor(::SERVER_POOL_BUCKET)
"http://localhost:4567/new"
end
end
In your upload controller add
@upload_url = url_for_upload_server
Now, in your upload view, add
<% form_for @media, :id => "fileform", :url => upload_url, :multipart => true do %>
...
<% end %>
Lets handle this inline and non-blocking
Add a button field to the end of your form like so:
and this to your javascript file (note, this is jQuery)
function beginFileUpload()
{
// todo: some kind of feedback that upload has started...
$("#uploading").show();
$("#buttonUpload").hide();
$("#validation_code").load('<%= url_for(:controller=>"media_file", :action=>"upload") %>',
{ title: $("#title").val(),
description: $("#description").val()
},
function(responseText, textStatus, XMLHttpRequest) {
$("#validation_code").val(responseText)
if (textStatus == 'success')
completeFileUpload();
else
alert('Error uploading file.');
});
return false;
}
function completeFileUpload()
{
$("#fileform").submit();
return false;
}
With that, you have inline file uploading with inline image processing off-site.
For more information, check the docs at http://rubyforge.org/projects/processorpool/
{ 5 comments }
