Created
April 8, 2010 08:26
-
-
Save serek/359901 to your computer and use it in GitHub Desktop.
Polymorphic file upload with paperclip, delayed_job, has_many_polymorphs, swfupload, formtastic and haml. Make same for any other types like Image, Audio, Document and so on.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module ApplicationHelper | |
# Access Levels | |
ACCESS_LEVELS = { :private => 0, :friends => 1, :public => 2 } | |
# Access levels i18n translation | |
def access_levels | |
ACCESS_LEVELS.collect{|k,v| [I18n.t("access." + k.to_s), v]} | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- content_for :head do | |
= javascript_include_tag 'jquery-1.3.2', 'swfupload/swfupload', 'swfupload/swfupload.swfobject', 'swfupload/handlers', 'swfupload/fileprogress', 'swfupload/swfupload.queue' | |
- session_key_name = ActionController::Base.session_options[:key] | |
#form_degrade_gracefully | |
%h1 New #{model_name} | |
- semantic_form_for [parent, model], :html => {:multipart => true} do |f| | |
- f.inputs do | |
= f.input :title | |
= f.input :description, :as => :text | |
= f.input :access, :as => :select, :collection => access_levels, :prompt => false, :selected => 2 | |
= f.input :data, :as => :file | |
- f.buttons do | |
= f.commit_button | |
%div#swfupload_container{:style => 'display: none'} | |
%h1 New #{model_name_plural} | |
= select("flash", "access", access_levels, {:prompt => false, :selected => 2}) | |
:javascript | |
jQuery(function() { | |
jQuery("#flash_access").change(function() { | |
swf_upload.addPostParam("access", jQuery('#flash_access').val()); | |
}); | |
}); | |
#fsUploadProgress.fieldset.flash | |
%span.legend Queue | |
#divStatus 0 uploaded #{model_name} (status kolejki) | |
%div | |
%span#spanButtonPlaceHolder | |
%input#btnCancel{ :type => "button", :value => "Cancel all", :disabled => "disabled", :onclick => "swf_upload.cancelQueue();" } | |
:javascript | |
var swf_upload; | |
SWFUpload.onload = function() { | |
var swf_settings = { | |
// SWFObject settings | |
minimum_flash_version: "9.0.28", | |
// Authencity config (Rails) | |
post_params: { | |
"#{session_key_name}": "#{cookies[session_key_name]}", | |
"authenticity_token": "#{form_authenticity_token}", | |
"access": "2", | |
}, | |
// Backend settings | |
upload_url: "#{polymorphic_path([parent, model])}", | |
// SWFUpload flash file | |
flash_url: '/flash/swfupload/swfupload.swf', | |
// Flash file settings | |
file_size_limit : "#{file_size_limit}", | |
file_types : "#{file_types_extensions}", | |
file_types_description : "#{file_types_description}", | |
file_upload_limit : "0", | |
file_queue_limit : "0", | |
// Event handler settings | |
file_queued_handler : fileQueued, | |
file_queue_error_handler : fileQueueError, | |
file_dialog_complete_handler : fileDialogComplete, | |
upload_start_handler : uploadStart, | |
upload_progress_handler : uploadProgress, | |
upload_error_handler : uploadError, | |
upload_success_handler : uploadSuccess, | |
upload_complete_handler : uploadComplete, | |
queue_complete_handler : queueComplete, // Queue plugin event | |
// Button Settings | |
button_image_url : "/images/swfupload/SmallSpyGlassWithTransperancy_17x18.png", | |
button_placeholder_id : "spanButtonPlaceHolder", | |
button_width: 17, | |
button_height: 18, | |
custom_settings : { | |
progressTarget : "fsUploadProgress", | |
cancelButtonId : "btnCancel" | |
}, | |
// Degrade gracefully normal form | |
swfupload_pre_load_handler: function() { | |
$('#form_degrade_gracefully').hide(); | |
$('#swfupload_container').show(); | |
}, | |
// Debug settings | |
debug: false | |
} | |
swf_upload = new SWFUpload(swf_settings); | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class CreateVideos < ActiveRecord::Migration | |
def self.up | |
create_table :videos do |t| | |
# Security | |
t.integer :access, :default => 2 | |
t.string :secure_token | |
# Default data | |
t.string :title | |
t.text :description | |
# For Delayed Job status | |
t.boolean :processing, :default => true | |
# Paperclip | |
t.string :data_file_name | |
t.string :data_content_type | |
t.integer :data_file_size | |
t.datetime :data_updated_at | |
t.timestamps | |
end | |
add_index :videos, :secure_token | |
add_index :videos, :title | |
add_index :videos, :description | |
add_index :videos, :data_file_name | |
add_index :videos, [:title, :description, :data_file_name] | |
end | |
def self.down | |
drop_table :videos | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ActionController::Routing::Routes.draw do |map| | |
map.resources :users do |u| | |
u.resources :videos, :controller => 'videos' | |
u.resources :images, :controller => 'images' | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class User < ActiveRecord::Base | |
# Used plugin has_many_polymorphs for easy polymorphic associations | |
# Example: | |
# User.first.assets # => [#Video, #Image, #Image] | |
# User.first.images # => [#Image, #Image] | |
has_many_polymorphs :assets, :from => [:videos, :images] | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Video < ActiveRecord::Base | |
# Allowable content types for file upload | |
VIDEO_TYPES = MIME::Types[/(video)/].map(&:content_type) + [ 'application/x-flv', 'application/x-wmv', 'application/x-avi', 'application/x-mpg' ] | |
# Video formats for ffmpeg (http://ffmpeg.org/ffmpeg-doc.html) | |
# qvga => 320x240 | |
# vga => 640x480 | |
# hd480 => 852x480 | |
# Paperclip attachment | |
has_attached_file :data, | |
:url => "/uploads/videos/:id/:style/:basename.:extension", | |
:path => ":rails_root/public/uploads/videos/:id/:style/:basename.:extension", | |
:styles => { | |
:i_icon => { :size => "32x32", :format => "jpg" }, | |
:i_small => { :size => "120x90", :format => "jpg" }, | |
:i_normal => { :size => "320x240", :format => "jpg" }, | |
:i_large => { :size => "640x480", :format => "jpg" }, | |
:i_hd => { :size => "852x480", :format => "jpg" }, | |
:v_normal => { :vid_size => "qvga", :format => "flv" }, | |
:v_large => { :vid_size => "vga", :format => "flv" }, | |
:v_hd => { :vid_size => "hd480", :format => "flv" } | |
}, | |
:processors => "video_transcoder" | |
# Secure mass assignment | |
attr_protected :data_file_name, :data_content_type, :data_size, :secure_token | |
# Validations | |
validates_presence_of :title | |
validates_attachment_presence :data | |
validates_attachment_size :data, :less_than => 200.megabytes | |
validates_attachment_content_type :data, :content_type => VIDEO_TYPES | |
# Stop processing after upload | |
before_post_process do |video| | |
false if video.processing? | |
end | |
# Push processing to Delayed Job | |
after_create { |video| video.send_later(:perform) } | |
# Named scopes | |
named_scope :private, :conditions => ['access = ?', 0] | |
named_scope :friends, :conditions => ['access = ?', 1] | |
named_scope :public, :conditions => ['access = ?', 2] | |
# Processings pushed to Delayed Job | |
def perform | |
update_attribute(:processing, false) | |
set_token | |
data.reprocess! | |
save | |
end | |
# Default URL with processing checks | |
def url(style = :original) | |
if (self.data && processing? && style != :original) or !File.exist?(self.data.path(style)) | |
return data.url(:original) | |
end | |
data.url(style) | |
end | |
# Default secure URL with processing checks | |
def secure_url(style = :original) | |
if (self.data && processing? && style != :original) or !File.exist?(self.data.path(style)) | |
return "/not_generated_yet" | |
end | |
"/#{secure_token}##{style.to_s}" | |
end | |
# Set secure token | |
def set_token | |
update_attribute(:secure_token, Digest::MD5.hexdigest(data_file_name + created_at.to_s + id.to_s + self.class.name)) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
= render :partial => "shared/asset_form", :locals => { :model => @video, :parent => @parent, :model_name => "Video", :model_name_plural => "Videos", :file_size_limit => "200 MB", :file_types_extensions => "*.avi; *.flv; *.mov; *.mpg; *.mpeg; *.mp4; *.wmv; *.3g2; *.3gp; *.m4v; *.asf;", :file_types_description => "Any file video" } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Paperclip | |
class VideoTranscoder < Processor | |
attr_accessor :options | |
def initialize file, options = {}, attachment = nil | |
super | |
@current_format = File.extname(@file.path) | |
@basename = File.basename(@file.path, @current_format) | |
@options = options | |
@format = options[:format] | |
end | |
def make | |
dst = Tempfile.new([@basename, @format].compact.join(".")) | |
dst.binmode | |
RVideo.logger = RAILS_DEFAULT_LOGGER | |
# Screenshot grabbing | |
if @format == 'jpg' | |
# Initalize transcoder | |
transcoder = RVideo::Transcoder.new | |
# Calculate time offset (50%) | |
offset = "50" | |
inspector = RVideo::Inspector.new :file => file.path | |
time = (inspector.duration.to_i / 1000.0) * (offset.to_f / 100.0) | |
# Recipe for ffmpeg screenshot grabbing | |
recipe = "ffmpeg -y -i $input_file$ -ss $time_offset$ -vframes 1 -vcodec mjpeg -f image2 -s $vid_size$ $output_file$" | |
begin | |
transcoder.execute(recipe, {:input_file => file.path, :output_file => dst.path, :vid_size => @options[:size], :time_offset => time}) | |
rescue TranscoderError => e | |
puts "Unable to generate screenshot: #{e.class} - #{e.message}" | |
end | |
end | |
# Video transcoder | |
if @format == 'flv' | |
# Initalize transcoder | |
transcoder = RVideo::Transcoder.new | |
# Recipe for ffmpeg video transcoding | |
recipe = "ffmpeg -y -i $input_file$ -vcodec libx264 -acodec libmp3lame -ac 2 -ar 44100 -ab 64000 -b 650kb -f flv -r 25 -s $vid_size$ -vpre default $output_file$" | |
begin | |
transcoder.execute(recipe, {:input_file => file.path, :output_file => dst.path, :vid_size => @options[:vid_size]}) | |
rescue TranscoderError => e | |
puts "Unable to transcode file: #{e.class} - #{e.message}" | |
end | |
end | |
dst | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class VideosController < ApplicationController | |
before_filter :find_parent | |
def index | |
@videos = @parent.videos.all | |
respond_to do |format| | |
format.html | |
end | |
end | |
def new | |
@video = @parent.videos.new | |
respond_to do |format| | |
format.html # new.html.erb | |
end | |
end | |
def create | |
if params[:Filedata] | |
# SWFUpload | |
@data = params[:Filedata] | |
@data.content_type = MIME::Types.type_for(@data.original_filename).to_s | |
@video = @parent.videos.create(:title => @data.original_filename, :data => @data, :access => params[:access]) | |
if @video.save | |
render :nothing => true # Sends 200 OK | |
else | |
render :text => "Error" | |
end | |
else | |
# Standard form | |
@video = @parent.videos.create(params[:video]) | |
if @video.save | |
redirect_to([@parent, :videos]) | |
else | |
render :action => "new" | |
end | |
end | |
end | |
private | |
def find_parent | |
params.each do |name, value| | |
if name =~ /(.+)_id$/ | |
@parent = $1.classify.constantize.find(value) and return | |
end | |
end | |
nil | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment