class FFmpegWeb::Transcoder

Attributes

input[RW]
output[RW]
video_info[RW]

Public Class Methods

new(input, output) click to toggle source
# File lib/ffmpeg_web.rb, line 7
def initialize(input, output)
  @input = input
  @output = output
  
  raise "Input file does not exist" if !File.exists?(input)
  raise "Output directory does not exist" if !File.exists?(File.dirname(output))

  @video_info = ffmpeg_info

  raise "File is not a valid movie file" if !is_valid?
end

Public Instance Methods

duration() click to toggle source

returns the duration of the video in seconds

# File lib/ffmpeg_web.rb, line 20
def duration
  duration = /Duration: (\d{2}):(\d{2}):(\d{2}).(\d{2})/.match(@video_info)
  duration_in_milliseconds = ($~[1].to_i * 60 * 60) + ($~[2].to_i * 60) + $~[3].to_i + ($~[4].to_i / 1000)
end
transcode(output_size=nil) click to toggle source

transcodes the file and returns an io pipe object

# File lib/ffmpeg_web.rb, line 26
def transcode(output_size=nil)
  progress_read, progress_write = IO::pipe
  pid = Process.fork do
    progress_read.close

    if output_size.nil?
     crf_encode(progress_write)
    else
      two_pass_encode(output_size, progress_write)
    end
  end

  progress_write.close

  progress_read
end

Private Instance Methods

crf_encode(progress_write) click to toggle source
# File lib/ffmpeg_web.rb, line 91
def crf_encode(progress_write)
  pid, read = do_pass("ffmpeg -y -i '#{@input}' -movflags faststart -c:v libx264 -preset slow -crf 20 -c:a libfaac '#{output}.mp4'")
  log_progress(read, progress_write, 1, 1)

  pid, status = Process.waitpid2(pid)
  status.exitstatus
end
do_pass(cmd) click to toggle source
# File lib/ffmpeg_web.rb, line 99
def do_pass(cmd)
  read, write = IO::pipe
  pid = Process.fork do
    read.close

    $stderr.reopen(write)
    $stdout.reopen(write)
    exec(cmd)
  end
  write.close

  [pid, read]
end
ffmpeg_info() click to toggle source
# File lib/ffmpeg_web.rb, line 44
def ffmpeg_info
  read, write = IO::pipe
  pid = Process.fork do
    read.close

    $stderr.reopen(write)
    exec("ffmpeg -y -i '#{@input}'")
  end
  write.close

  ffmpeg_output = read.read
  ffmpeg_output
end
is_valid?() click to toggle source
# File lib/ffmpeg_web.rb, line 58
def is_valid?
  video_stream, audio_stream = nil

  if match = @video_info.match(/Video: (.*)$/i)
    video_stream = match.captures.first
  end

  if match = @video_info.match(/Audio: (.*)$/i)
    audio_stream = match.captures.first
  end

  video_stream && audio_stream
end
log_progress(input_stream, output_stream, current_pass, number_of_passes) click to toggle source
# File lib/ffmpeg_web.rb, line 113
def log_progress(input_stream, output_stream, current_pass, number_of_passes)
  duration = 0.00 #milliseconds
  elapsed = 0.00 #milliseconds

  while line = input_stream.gets
    if line =~ /Duration: (\d{2}):(\d{2}):(\d{2}).(\d{2})/ && duration == 0.00
      duration = ($~[1].to_i * 60 * 60 * 1000) + ($~[2].to_i * 60 * 1000) + ($~[3].to_i * 1000) + $~[4].to_i
      break
    end     
  end

  while line = input_stream.gets("\r")
    line = line.encode('UTF-8', 'binary', :invalid => :replace, :undef => :replace, :replace => '') if line
    if line =~ /time=(\d{2}):(\d{2}):(\d{2}).(\d{2})/
      elapsed = ($~[1].to_i * 60 * 60 * 1000) + ($~[2].to_i * 60 * 1000) + ($~[3].to_i * 1000) + $~[4].to_i

      pass_progress = (elapsed.to_f / duration.to_f) * 100
      if number_of_passes > 1 && current_pass > 1
        progress = (pass_progress / number_of_passes) + (100 / number_of_passes)
      elsif number_of_passes > 1 && current_pass == 1
        progress = pass_progress / number_of_passes
      else
        progress = pass_progress
      end
      
      output_stream.puts progress.round(2).to_s + "%"
    end
  end
end
two_pass_encode(output_size, progress_write) click to toggle source
# File lib/ffmpeg_web.rb, line 72
def two_pass_encode(output_size, progress_write)
  output_size_in_kilobits = output_size * 8192      
  audio_bitrate = 128
  total_bitrate = output_size_in_kilobits / self.duration
  video_bitrate = total_bitrate - audio_bitrate

  first_pass = "ffmpeg -y -i '#{@input}' -movflags faststart -c:v libx264 -preset slow -b:v #{video_bitrate}k -an -pass 1 -f mp4 /dev/null"
  second_pass = "ffmpeg -y -i '#{@input}' -movflags faststart -c:v libx264 -preset slow -b:v #{video_bitrate}k -c:a libfaac -b:a #{audio_bitrate}k -pass 2 '#{@output}.mp4'"

  pid, read = do_pass(first_pass)
  log_progress(read, progress_write, 1, 2)
  pid, status = Process.waitpid2(pid)

  pid, read = do_pass(second_pass)
  log_progress(read, progress_write, 2, 2)
  pid, status = Process.waitpid2(pid)
  status.exitstatus
end