2012-11-20 66 views
2

我想上传的视频转换后台,运行窗口。一些我在用的:背景视频处理与导轨

gem 'paperclip' 
gem 'delayed_job_active_record' 
gem 'ffmpeg' 

我已经编辑注册表,以允许FFmpeg的命令,从任何地方跑了,我得到我认为是ffmpeg的,因为它消失得太快的弹出,猜命令所以如果有人知道它有什么问题,请告诉我。但真正的问题是,它只是挂在那儿,它说:

[2012-12-09 22:47:03] ERROR invalid body size. 
[2012-12-09 22:47:03] ERROR Errno::ECONNABORTED: An established connection was a 
borted by the software in your host machine. 
     C:/RailsInstaller/Ruby1.9.3/lib/ruby/1.9.1/webrick/httpresponse.rb:396:i 
n `write' 
     C:/RailsInstaller/Ruby1.9.3/lib/ruby/1.9.1/webrick/httpresponse.rb:396:i 
n `<<' 
     C:/RailsInstaller/Ruby1.9.3/lib/ruby/1.9.1/webrick/httpresponse.rb:396:i 
n `_write_data' 
     C:/RailsInstaller/Ruby1.9.3/lib/ruby/1.9.1/webrick/httpresponse.rb:368:i 
n `send_body_string' 
     C:/RailsInstaller/Ruby1.9.3/lib/ruby/1.9.1/webrick/httpresponse.rb:249:i 
n `send_body' 
     C:/RailsInstaller/Ruby1.9.3/lib/ruby/1.9.1/webrick/httpresponse.rb:152:i 
n `send_response' 
     C:/RailsInstaller/Ruby1.9.3/lib/ruby/1.9.1/webrick/httpserver.rb:110:in 
`run' 
     C:/RailsInstaller/Ruby1.9.3/lib/ruby/1.9.1/webrick/server.rb:191:in `blo 
ck in start_thread' 

有谁知道如何正确地得到这个工作?我已经阅读了一些教程,其中包含我需要的一些小部分,但我无法让它们一起工作。这里是我到目前为止,还是让我知道,如果你需要更多:

型号:

class Video < ActiveRecord::Base 

    belongs_to :user 
    has_many :comments, dependent: :destroy 
    attr_accessible :video, :user_id, :video_file_name, :title, :public, :description, :views 

    has_attached_file :video, url: "https://stackoverflow.com/users/:user_id/videos/:id/:basename_:style.:extension" 

    #process_in_background :video #causes death 

    validates :video, presence: true 
    validates :description, presence: true, length: { minimum: 5, maximum: 100} 
    validates :title, presence: true, length: { minimum: 1, maximum: 15 } 

    validates_attachment_size :video, less_than: 1.gigabytes 
    validates_attachment :video, presence: true 

    default_scope order: 'created_at DESC' 

    Paperclip.interpolates :user_id do |attachment, style|attachment.instance.user_id 
    end 

    #before_post_process do |video| 
    # false if video.status == "converting" 
    #end 

    def perform 
    command = <<-end_command 
     start ffmpeg -i #{ '/public/users/:user_id/videos/:id/:basename_:style.:extension' } -ar 22050 -ab 32 -s 1280x720 -vcodec webm -r 25 -qscale 8 -f webm -y #{ '/public/users/:user_id/videos/:id/:basename_.webm' } 

    end_command 
    success = system(command) 
    logger.debug 'Converting File: ' + success.to_s 
    if success && $?.exitstatus.to_i == 0 
     #self.converted! 
     self.status = "converted" 
    else 
     #self.failure! 
     self.status = "failed" 
    end 
    end 

    handle_asynchronously :perform 

    def self.search(search) 
    if search 
     find(:all, conditions: ["public = 't' AND title LIKE ?", "%#{search}%"], order: "created_at DESC") 
    else 
     find(:all, conditions: ["public = 't'"], order: "created_at DESC") 
    end 
    end 

    def self.admin_search(search) 
    if search 
     find(:all, conditions: ['title LIKE ?', "%#{search}%"], order: "created_at DESC") 
    else 
     find(:all, order: "created_at DESC") 
    end 
    end 

    private 

    # This updates the stored filename with the new flash video file 
    def set_new_filename 
     #update_attribute(:filename, "#{filename}.#{id}.webm") 
     update_attribute(:content_type, "video/x-webm") 
    end 

end 

控制器:

class VideosController < ApplicationController 
    before_filter :signed_in_user, only: [:upload, :update, :destroy] 
    before_filter :admin_user, only: :admin_index 

    def upload 
     @video = Video.new 
     # generate a unique id for the upload 
     @uuid = (0..29).to_a.map {|x| rand(10)} 
    end 

    def create 
     @video = Video.new(params[:video]) 
     @video.user_id = current_user.id 

     if @video.save 
      @video.delay.perform 
      flash[:success] = "Uploaded Succefully!" 
      redirect_to @video.user 
      Delayed::Worker.new.start 
     else 
      render 'upload' 
     end 
    end 

    def show 
     @video = Video.find(params[:id]) 
     @comments = @video.comments.paginate(page: params[:page], per_page: 6) 
     if [email protected] 
      if !signed_in? || current_user.id != @video.user_id && !current_user.admin && !current_user.approved?(@video.user) 
      flash[:notice] = "Video is private" 
      redirect_to root_path 
     end 
    end 
    end 

    def update 
     @video = Video.find(params[:id]) 
     if @video.update_attributes(params[:video]) 
     flash[:success] = "Video preferences saved" 
    else 
     flash[:fail] = "Failed to update video preferences" 
    end 
    redirect_to :back 
    end 

    def destroy 
     @video = Video.find(params[:id]) 
     @video.destroy 
     flash[:deleted] = "Deleted Succefully!" 
     redirect_to :back 
    end 

    def index 
     @videos = Video.paginate(page: params[:page], per_page: 6).search(params[:search]) 
    end 

    def admin_index 
     @videos = Video.paginate(page: params[:page], per_page: 6).admin_search(params[:search]) 
    end 

    def ajax_video_comments 
     @video = Video.find(params[:id]) 
     @comments = @video.comments.paginate(page: params[:page], per_page: 6) 

     respond_to do |format| 
     format.js { render partial: 'shared/comments', content_type: 'text/html' } 
    end 
    end 

    def ajax_video_watched 
     @video = Video.find(params[:id]) 
     @video.views += 1 
     @video.save 
    end 

    private 

    def signed_in_user 
     redirect_to root_path, notice: "Please Login." unless signed_in? 
    end 

    def admin_user 
     redirect_to(root_path) unless current_user.admin? 
    end 

end 
+0

你可以尝试不延迟? (成功=系统(convert_command))。我想延迟作业可以避免创建预期的Process :: Status对象(由$引用)。 – alto

+0

@alto好吧,亚,它允许它至少加载到用户视频页面,但它仍然不转换,它将错误放入aasm_state字段而不是转换,这从来没有改变 –

+0

什么是在aasm_state字段?您的示例代码中没有提及AASM。 – alto

回答

2

你的表应该有这些列:

  • video_file_name
  • video_content_type
  • video_file_size
  • video_updated_at
  • video_meta

我增加了一些额外的回形针魔术给你的模型,很明显,你可以调整为ffmpeg的设置。这并不是所有的原始代码,但我不记得我在哪里找到零碎的东西,所以如果有人认识到它可以随意获得信贷。

class Video < ActiveRecord::Base 

    belongs_to :user 
    has_many :comments, dependent: :destroy 
    attr_accessible :video, :user_id, :video_file_name, 
        :title, :public, :description, :views 

    has_attached_file :video, 
    url: "https://stackoverflow.com/users/:user_id/videos/:id/:basename_:style.:extension" 
    styles: { 
      :original => { :geometry => "1280x720", :format => 'mp4', :streaming => true, :convert_options => { :input => {}, :output => {'c:v' => 'libx264', vprofile: 'high', preset: 'medium', 'b:v' => '1250k', maxrate: '1250k', bufsize: '2500k', pix_fmt: 'yuv420p', flags: '+mv4+aic', threads: 'auto', 'b:a' => '128k', strict: '-2'}} }, 
      :medium => { :geometry => "854x480", :format => 'mp4', :streaming => true,     :convert_options => { :input => {}, :output => {'c:v' => 'libx264', vprofile: 'high', preset: 'medium', 'b:v' => '750k', maxrate: '750k', bufsize: '1500k', pix_fmt: 'yuv420p', flags: '+mv4+aic', threads: 'auto', 'b:a' => '128k', strict: '-2'}} }, 
      :small => { :geometry => '640x360', :format => 'mp4', :streaming => true, :convert_options => { :input => {}, :output => {'c:v' => 'libx264', vprofile: 'high', preset: 'medium', 'b:v' => '250k', maxrate: '250k', bufsize: '500k', pix_fmt: 'yuv420p', flags: '+mv4+aic', threads: 'auto', 'b:a' => '128k', strict: '-2'}} }, 
      :thumb => { :geometry => "160x90", :format => 'jpg', :time => 10 } 
      }, 
    processors: [:ffmpeg, :qtfaststart] 

    validates :video, presence: true 
    validates :description, presence: true, length: { minimum: 5, maximum: 100} 
    validates :title, presence: true, length: { minimum: 1, maximum: 15 } 

    validates_attachment_size :video, less_than: 1.gigabytes 
    validates_attachment :video, presence: true 

    default_scope order: 'created_at DESC' 

    # cancel post-processing now, and set flag... 


    before_post_process do |video| 
    if video.status == nil 
     video.status = "queuing" 
     false # halts processing 
    end 
    end 

    # ...and perform after save in background 
    after_commit do |video| 
    if video.status == "queuing" 
     Delayed::Job.enqueue VideoJob.new(video.id), :queue => 'video' 
     video.status == "queued" 
     video.save(validations: false) 
    end 
    end 

    # generate styles (downloads original first) 
    def regenerate_styles! 
    self.video.reprocess! 
    end 

    # detect if our source file has changed 
    def video_changed? 
    self.video_file_size_changed? || 
    self.video_file_name_changed? || 
    self.video_content_type_changed? || 
    self.video_updated_at_changed? 
    end 

    # Class to perform with delayed jobs 
    class VideoJob < Struct.new(:video_id) 

    def perform 
     video = Video.find(self.video_id) 
     video.status = "processing" 
     video.save(validations: false) 
     video.regenerate_styles! 
    end 

    def success(job) 
     video = Video.find(self.video_id) 
     video.status = "complete" 
     video.save(:validate => false) 
    end 

    def error(job, exception) 
     video = Video.find(self.video_id) 
     video.status = "error" 
     video.save(:validate => false) 
    end 
    end 
end 

回形针处理器(/lib/paperclip_processors/ffmpeg.rb):

module Paperclip 
    class Ffmpeg < Processor 
    attr_accessor :geometry, :format, :whiny, :convert_options 

    # Creates a Video object set to work on the +file+ given. It 
    # will attempt to transcode the video into one defined by +target_geometry+ 
    # which is a "WxH"-style string. +format+ should be specified. 
    # Video transcoding will raise no errors unless 
    # +whiny+ is true (which it is, by default. If +convert_options+ is 
    # set, the options will be appended to the convert command upon video transcoding. 
    def initialize file, options = {}, attachment = nil 
     @convert_options = { 
     :input => {}, 
     :output => { :y => nil } 
     } 
     unless options[:convert_options].nil? || options[:convert_options].class != Hash 
     unless options[:convert_options][:input].nil? || options[:convert_options][:input].class != Hash 
      @convert_options[:input].reverse_merge! options[:convert_options][:input] 
     end 
     unless options[:convert_options][:output].nil? || options[:convert_options][:output].class != Hash 
      @convert_options[:output].reverse_merge! options[:convert_options][:output] 
     end 
     end 

     @geometry  = options[:geometry] 
     @file   = file 
     @keep_aspect  = [email protected]? && @geometry[-1,1] != '!' 
     @pad_only  = @keep_aspect && @geometry[-1,1] == '#' 
     @enlarge_only = @keep_aspect && @geometry[-1,1] == '<' 
     @shrink_only  = @keep_aspect && @geometry[-1,1] == '>' 
     @whiny   = options[:whiny].nil? ? true : options[:whiny] 
     @format   = options[:format] 
     @time   = options[:time].nil? ? 3 : options[:time] 
     @current_format = File.extname(@file.path) 
     @basename  = File.basename(@file.path, @current_format) 
     @meta   = identify 
     @pad_color  = options[:pad_color].nil? ? "black" : options[:pad_color] 
     attachment.instance_write(:meta, @meta) 
    end 
    # Performs the transcoding of the +file+ into a thumbnail/video. Returns the Tempfile 
    # that contains the new image/video. 
    def make 
     src = @file 
     dst = Tempfile.new([@basename, @format ? ".#{@format}" : '']) 
     dst.binmode 

     parameters = [] 
     # Add geometry 
     if @geometry 
     # Extract target dimensions 
     if @geometry =~ /(\d*)x(\d*)/ 
      target_width = $1 
      target_height = $2 
     end 
     # Only calculate target dimensions if we have current dimensions 
     unless @meta[:size].nil? 
      current_geometry = @meta[:size].split('x') 
      # Current width and height 
      current_width = current_geometry[0] 
      current_height = current_geometry[1] 
      if @keep_aspect 
      if @enlarge_only 
       if current_width.to_i < target_width.to_i 
       # Keep aspect ratio 
       width = target_width.to_i 
       height = (width.to_f/(@meta[:aspect].to_f)).to_i 
       @convert_options[:output][:s] = "#{width.to_i/2*2}x#{height.to_i/2*2}" 
       else 
       return nil 
       end 
      elsif @shrink_only 
       if current_width.to_i > target_width.to_i 
       # Keep aspect ratio 
       width = target_width.to_i 
       height = (width.to_f/(@meta[:aspect].to_f)).to_i 
       @convert_options[:output][:s] = "#{width.to_i/2*2}x#{height.to_i/2*2}" 
       else 
       return nil 
       end 
      elsif @pad_only 
       # Keep aspect ratio 
       width = target_width.to_i 
       height = (width.to_f/(@meta[:aspect].to_f)).to_i 
       # We should add half the delta as a padding offset Y 
       pad_y = (target_height.to_f - height.to_f)/2 
       if pad_y > 0 
       @convert_options[:output][:vf] = "scale=#{width}:-1,pad=#{width.to_i}:#{target_height.to_i}:0:#{pad_y}:#@pad_color" 
       else 
       @convert_options[:output][:vf] = "scale=#{width}:-1,crop=#{width.to_i}:#{height.to_i}" 
       end 
      else 
       # Keep aspect ratio 
       width = target_width.to_i 
       height = (width.to_f/(@meta[:aspect].to_f)).to_i 
       @convert_options[:output][:s] = "#{width.to_i/2*2}x#{height.to_i/2*2}" 
      end 
      else 
      # Do not keep aspect ratio 
      @convert_options[:output][:s] = "#{target_width.to_i/2*2}x#{target_height.to_i/2*2}" 
      end 
     end 
     end 
     # Add format 
     case @format 
     when 'jpg', 'jpeg', 'png', 'gif' # Images 
     @convert_options[:input][:ss] = @time 
     @convert_options[:output][:vframes] = 1 
     @convert_options[:output][:f] = 'image2' 
     end 

     # Add source 
     parameters << @convert_options[:input].map { |k,v| "-#{k.to_s} #{v} "} 
     parameters << "-i ':source'" 
     parameters << @convert_options[:output].map { |k,v| "-#{k.to_s} #{v} "} 
     parameters << "':dest'" 

     parameters = parameters.flatten.compact.join(" ").strip.squeeze(" ") 

     begin 
     success = Paperclip.run("ffmpeg", parameters, :source => "#{File.expand_path(src.path)}", :dest => File.expand_path(dst.path)) 
     rescue Cocaine::ExitStatusError => e 
     raise Paperclip::Error, "error while processing video for #{@basename}: #{e}" if @whiny 
     end 

     dst 
    end 

    def identify 
     meta = {} 
     command = "ffmpeg -i \"#{File.expand_path(@file.path)}\" 2>&1" 
     ffmpeg = IO.popen(command) 
     ffmpeg.each("\r") do |line| 
     # Matching lines like: 
     # Video: h264, yuvj420p, 640x480 [PAR 72:72 DAR 4:3], 10301 kb/s, 30 fps, 30 tbr, 600 tbn, 600 tbc 
     if line.include?(' Video: ') 
      start = line.index('Video:') 
      items = line[start, 150].split(',') 
      size = items[2].strip!.split(' ').first 
      meta[:size] = size.to_s 
      meta[:aspect] = size.split('x').first.to_f/size.split('x').last.to_f 
     end 
     # Matching Duration: 00:01:31.66, start: 0.000000, bitrate: 10404 kb/s 
     if line =~ /Duration:(\s.?(\d*):(\d*):(\d*\.\d*))/ 
      meta[:length] = $2.to_s + ":" + $3.to_s + ":" + $4.to_s 
     end 
     end 
     meta 
    end 
    end 

    class Attachment 
    def meta 
     instance_read(:meta) 
    end 
    end 
end 

回形针处理器(/lib/paperclip_processors/qtfaststart.rb):

module Paperclip 
    class Qtfaststart < Processor 
    attr_accessor :streaming, :format, :whiny 

    # Creates a Video object set to work on the +file+ given. It 
    # will attempt to reposition the moov atom in the video given 
    # if +streaming+ is set. 
    def initialize file, options = {}, attachment = nil 
     @streaming  = options[:streaming] 
     @file   = file 
     @whiny   = options[:whiny].nil? ? true : options[:whiny] 
     @format   = options[:format] 
     @current_format = File.extname(@file.path) 
     @basename  = File.basename(@file.path, @current_format) 
     @meta   = attachment.meta 
     attachment.instance_write(:meta, @meta) 
    end 

    # Performs the atom repositioning on +file+. 
    # Returns the Tempfile that contains the new video or the original 
    # file if +streaming+ wasn't set. 
    def make 
     return @file unless @streaming 

     src = @file 
     dst = Tempfile.new([@basename, @format ? ".#{@format}" : '']) 
     dst.binmode 

     parameters = [] 
     # Add source 
     parameters << ":source" 
     # Add destination 
     parameters << ":dest" 

     parameters = parameters.flatten.compact.join(" ").strip.squeeze(" ") 

     Paperclip.log("[qtfaststart] #{parameters}") 
     begin 
     success = Paperclip.run("qt-faststart", parameters, :source => "#{File.expand_path(src.path)}", :dest => File.expand_path(dst.path)) 
     rescue Cocaine::ExitStatusError => e 
     raise PaperclipError, "error while processing video for #{@basename}: #{e}" if @whiny 
     end 
     dst 
    end 
    end 

    class Attachment 
    def meta 
     instance_read(:meta) 
    end 
    end 
end 

您控制器可使用一些更多的清洁,但我只是作出调整,使其与我的其他变化工作。

class VideosController < ApplicationController 
    before_filter :signed_in_user, only: [:upload, :update, :destroy] 
    before_filter :admin_user, only: :admin_index 

    def upload 
     @video = Video.new 
     # generate a unique id for the upload 
     @uuid = (0..29).to_a.map {|x| rand(10)} 
    end 

    def create 
     @video = Video.new(params[:video]) 
     @video.user_id = current_user.id 

     if @video.save 
      flash[:success] = "Uploaded Succefully!" 
      redirect_to @video.user 
     else 
      render 'upload' 
     end 
    end 

    def show 
     @video = Video.find(params[:id]) 
     @comments = @video.comments.paginate(page: params[:page], per_page: 6) 
     if [email protected] 
      if !signed_in? || current_user.id != @video.user_id && !current_user.admin && !current_user.approved?(@video.user) 
      flash[:notice] = "Video is private" 
      redirect_to root_path 
     end 
    end 
    end 

    def update 
     @video = Video.find(params[:id]) 
     if @video.update_attributes(params[:video]) 
     flash[:success] = "Video preferences saved" 
    else 
     flash[:fail] = "Failed to update video preferences" 
    end 
    redirect_to :back 
    end 

    def destroy 
     @video = Video.find(params[:id]) 
     @video.destroy 
     flash[:deleted] = "Deleted Succefully!" 
     redirect_to :back 
    end 

    def index 
     @videos = Video.paginate(page: params[:page], per_page: 6).search(params[:search]) 
    end 

    def admin_index 
     @videos = Video.paginate(page: params[:page], per_page: 6).admin_search(params[:search]) 
    end 

    def ajax_video_comments 
     @video = Video.find(params[:id]) 
     @comments = @video.comments.paginate(page: params[:page], per_page: 6) 

     respond_to do |format| 
     format.js { render partial: 'shared/comments', content_type: 'text/html' } 
    end 
    end 

    def ajax_video_watched 
     @video = Video.find(params[:id]) 
     @video.views += 1 
     @video.save 
    end 

    private 

    def signed_in_user 
     redirect_to root_path, notice: "Please Login." unless signed_in? 
    end 

    def admin_user 
     redirect_to(root_path) unless current_user.admin? 
    end 

end 

你可以运行一个delayed_jobs工作线程,它对你来说最合适。我可能在这里犯了一些错误,但我试图使我的方法适应您当前的模型。

1

有点晚了,但我们使用paperclip-ffmeg宝石 - 一些你可能想看看

所有你需要做的就是把这些宝石到您的gemfile,然后你只需要定义处理器:ffmpeg

这是我们从代码中的活生生的例子:

class Attachment < ActiveRecord::Base 

     has_attached_file :attachment, 
       styles:   lambda { |a| a.instance.is_image? ? {:small => "x200>", :medium => "x300>", :large => "x400>"} : {:thumb => { :geometry => "100x100#", :format => 'jpg', :time => 10}, :medium => { :geometry => "300x300#", :format => 'jpg', :time => 10}}}, 
       :processors => lambda { |a| a.is_video? ? [ :ffmpeg ] : [ :thumbnail ] } 

     def is_video? 
       attachment.instance.attachment_content_type =~ %r(video) 
     end 

     def is_image? 
       attachment.instance.attachment_content_type =~ %r(image) 
     end 

end 
+0

我相信我确实尝试了这一点,但它并没有解决成功上传后未在后台处理的问题,导致用户在上传完成之前等待完成 –