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: "/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 !@video.public
            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
4

2 回答 2

3

您的表应该有这些列:

  • 视频文件名
  • 视频内容类型
  • 视频文件大小
  • 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: "/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     = !@geometry.nil? && @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 !@video.public
            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

你可以运行一个 delay_jobs 工作线程,不管怎样最适合你。我可能在这里犯了一些错误,但我试图使我的方法适应您当前的模型。

于 2013-03-09T02:41:12.790 回答
1

有点晚了,但我们使用回形针-ffmeg gem - 你可能想看看

您需要做的就是将它们放入您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
于 2013-10-23T10:07:33.873 回答