Recently, I had the need to take videos and upload them to a rails application. Naturally, the videos needed to be any format, so I needed to proccess them for the web (mp4 and webm in this case) and I needed some preview screenshots of the videos as well.

I used paperclip with paperclip-av-transcoder gem on a model, so I could just do has_attached_file as normal. The av-transcoder gem allows you to transcode different format videos via the paperclip :styles hash. Here is the styles hash from this project;

I had a hard time finding documentation on the paperclip-av-transcoder gem, but to my knowledge, it accepts all flags and just passes them to ffmpeg.
These are options specific to ffmpeg, not avconv. If you have to use avconv, BE WARY OF THESE OPTIONS They may not work for you.
PAPERCLIP_VIDEO_OPTIONS = {
  :styles => {
    :mp4 => {
      :format => 'mp4',
      :geometry => "1200x675#",
      :convert_options => {
        :input => {},
        :output => {
          :vcodec => 'libx264',
          :movflags => '+faststart',
          :strict => :experimental
        }
      }
    },
    :webm => {
      :format => 'webm',
      :geometry => "1200x675#",
      :convert_options => {
        :input => {},
        :output => {
          :vcodec => 'libvpx',
          :acodec => 'libvorbis',
          'cpu-used' => -10,
          :deadline => :realtime,
          :strict => :experimental
        }
      }
    },

    # I couldn't get the preview to work with
    # the method outlined in the docs,
    # so I just passed the options
    # to ffmpeg specifically.

    :preview => {
      :format => :jpg,
      :geometry => "1200x675#",
      :convert_options => {
        :output => {
          :vframes => 1,
          :s => "1200x675",
          :ss => '00:00:02'
        }
      }
    },
    :thumb => {
      :format => :jpg,
      :geometry => "300x169#",
      :convert_options => {
        :output => {
          :vframes => 1,
          :s => '300x169',
          :ss => '00:00:02'
        }
      }
    },
  },
  :processors => [:transcoder]
}

Then I nabbed the delayed_paperclip gem to process the transcoding jobs with delayed_job and added this to the model;

process_in_background :video

At first, I just ran a new delayed_job worker on the :after_create hook on the model, like so.

before_validation(on: [:create, :save]) do
  if self.video_processing
    Delayed::Worker.new.run(Delayed::Job.last)
  end
end

But I quickly figured out that this was problematic as I started using delayed_job with ActionMailer and the last delayed_job had the possibility of not being the video. Thus, I just ran three delayed_job workers on the server that start up with the rails server (currently using puma).

I also wanted to alert the user to a processing video, so I added a before_filter :check_for_video_crunch that adds a flash message to the application controller.

def check_for_video_crunch
  if Model.where(:video_processing => true).each do |m|
    flash[:video_messages] = Array.new if not flash[:video_messages]
    flash[:video_messages] << "Video file is currently proccessing for #{m.name}."
  end.empty?
  end
end

I then used the mediaelement js JavaScript plugin to fallback to flash for browsers that are a bit behind. I followed the instructions to get all the assets loaded in the project. You can just use the video.url(:style) paperclip method to get the url for each video.

In conclusion

I began the project thinking it would be a chore, luckily I was able to get videos transcoding in the background fairly quickly. For reference, here’s the complete code for the video paperclip attribute.

###
# model.rb
###
has_attached_file :video, :styles => {
    :mp4 => {
      :format => 'mp4',
      :geometry => "1200x675#",
      :convert_options => {
        :input => {},
        :output => {
          :vcodec => 'libx264',
          :movflags => '+faststart',
          :strict => :experimental
        }
      }
    },
    :webm => {
      :format => 'webm',
      :geometry => "1200x675#",
      :convert_options => {
        :input => {},
        :output => {
          :vcodec => 'libvpx',
          :acodec => 'libvorbis',
          'cpu-used' => -10,
          :deadline => :realtime,
          :strict => :experimental
        }
      }
    },

    # I couldn't get the preview to work with
    # the method outlined in the docs,
    # so I just passed the options
    # to avconv specifically.

    :preview => {
      :format => :jpg,
      :geometry => "1200x675#",
      :convert_options => {
        :output => {
          :vframes => 1,
          :s => "1200x675",
          :ss => '00:00:02'
        }
      }
    },
    :thumb => {
      :format => :jpg,
      :geometry => "300x169#",
      :convert_options => {
        :output => {
          :vframes => 1,
          :s => '300x169',
          :ss => '00:00:02'
        }
      }
    },
  },
  :processors => [:transcoder]

validates_attachment_size :video, less_than: 1.gigabytes
validates_attachment_content_type :video, :content_type => ["video/mp4", "video/quicktime", "video/x-flv", "video/x-msvideo", "video/x-ms-wmv", "video/webm"]
process_in_background :video

###
# application_controller.rb
###
before_filter :check_for_video_crunch

def check_for_video_crunch
  if Model.where(:video_processing => true).each do |s|
    flash[:video_messages] = Array.new
    flash[:video_messages] << "Video file is currently proccessing for #{s.title}."
  end.empty?
  end
end