Skip to content

Instantly share code, notes, and snippets.

@adriand
Created August 19, 2010 03:03
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adriand/536890 to your computer and use it in GitHub Desktop.
Save adriand/536890 to your computer and use it in GitHub Desktop.
class Video < ActiveRecord::Base
validates_presence_of :title
named_scope :finished, :conditions => { :encoded_state => "finished" }
has_attached_file :video,
:url => ":class/:id/:style/:basename.:extension",
:path => ":class/:id/:style/:basename.:extension",
:storage => :s3
validates_attachment_presence :video
has_attached_file :thumbnail, :styles => {:thumb => "162x161#"}
after_destroy :remove_encoded_video
# this runs on the after_destroy callback. It is reponsible for removing the encoded file
# and the thumbnail that is associated with this video. Paperclip will automatically remove the other files, but
# since we created our own bucket for encoded video, we need to handle this part ourselves.
def remove_encoded_video
unless output_url.blank?
AWS::S3::Base.establish_connection!(
:access_key_id => zencoder_setting["s3_output"]["access_key_id"],
:secret_access_key => zencoder_setting["s3_output"]["secret_access_key"]
)
AWS::S3::S3Object.delete(File.basename(output_url), zencoder_setting["s3_output"]["bucket"])
# there is no real concept of folders in S3, they are just labels, essentially
AWS::S3::S3Object.delete("/thumbnails_#{self.id}/frame_0000.png", zencoder_setting["s3_output"]["bucket"])
end
end
# commence encoding of the video. Width and height are hard-coded into this, but there may be situations where
# you want that to be more dynamic - that modification will be trivial.
def encode!(options = {})
begin
zen = Zencoder.new("http://s3.amazonaws.com/" + zencoder_setting["s3_output"]["bucket"], zencoder_setting["settings"]["notification_url"])
# 'video.url(:original, false)' prevents paperclip from adding timestamp, which causes errors
if zen.encode(self.video.url(:original, false), 800, 450, "/thumbnails_#{self.id}", options)
self.encoded_state = "queued"
self.output_url = zen.output_url
self.job_id = zen.job_id
self.save
else
errors.add_to_base(zen.errors)
nil
end
rescue RuntimeError => exception
errors.add_to_base("Video encoding request failed with result: " + exception.to_s)
nil
end
end
# must be called from a controller action, in this case, videos/encode_notify, that will capture the post params
# and send them in. This captures a successful encoding and sets the encode_state to "finished", so that our application
# knows we're good to go. It also retrieves the thumbnail image that Zencoder creates and attaches it to the video
# using Paperclip. And finally, it retrieves the duration of the video, again from Zencoder.
def capture_notification(output)
self.encoded_state = output[:state]
if self.encoded_state == "finished"
self.output_url = output[:url]
self.thumbnail = open(URI.parse("http://s3.amazonaws.com/" + zencoder_setting["s3_output"]["bucket"] + "/thumbnails_#{self.id}/frame_0000.png"))
self.thumbnail_content_type = "image/png"
# get the job details so we can retrieve the length of the video in milliseconds
zen = Zencoder.new
self.duration_in_ms = zen.details(self.job_id)["job"]["output_media_files"].first["duration_in_ms"]
end
self.save
end
# a handy way to turn duration_in_ms into a formatted string like 5:34
def human_length
if duration_in_ms
minutes = duration_in_ms / 1000 / 60
seconds = (duration_in_ms / 1000) - (minutes * 60)
sprintf("%d:%02d", minutes, seconds)
else
"Unknown"
end
end
private
def zencoder_setting
@zencoder_config ||= YAML.load_file("#{RAILS_ROOT}/config/zencoder.yml")
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment