Class: Tootsie::FfmpegAdapter

Inherits:
Object
  • Object
show all
Defined in:
lib/tootsie/ffmpeg_adapter.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ FfmpegAdapter

Returns a new instance of FfmpegAdapter.



5
6
7
8
9
10
11
12
13
14
15
# File 'lib/tootsie/ffmpeg_adapter.rb', line 5

def initialize(options = {})
  @logger = Application.get.logger
  @ffmpeg_binary = 'ffmpeg'
  @ffmpeg_arguments = {}
  @ffmpeg_arguments['threads'] = (options[:thread_count] || 1)
  @ffmpeg_arguments['v'] = 1
  @ffmpeg_arguments['xerror'] = true
  # TODO: Only in newer FFmpeg versions
  #@ffmpeg_arguments['loglevel'] = 'verbose'
  @ffmpeg_arguments['y'] = true
end

Instance Attribute Details

#ffmpeg_argumentsObject

Returns the value of attribute ffmpeg_arguments.



99
100
101
# File 'lib/tootsie/ffmpeg_adapter.rb', line 99

def ffmpeg_arguments
  @ffmpeg_arguments
end

#ffmpeg_binaryObject

Returns the value of attribute ffmpeg_binary.



98
99
100
# File 'lib/tootsie/ffmpeg_adapter.rb', line 98

def ffmpeg_binary
  @ffmpeg_binary
end

#outputObject (readonly)

Output captured from FFmpeg command line tool so far.



102
103
104
# File 'lib/tootsie/ffmpeg_adapter.rb', line 102

def output
  @output
end

#progressObject

Progress reporter that implements call(seconds, total_seconds) to record transcoding progress.



106
107
108
# File 'lib/tootsie/ffmpeg_adapter.rb', line 106

def progress
  @progress
end

Instance Method Details

#transcode(input_filename, output_filename, options = {}) ⇒ Object

Transcode a file by taking an input file and writing an output file.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/tootsie/ffmpeg_adapter.rb', line 18

def transcode(input_filename, output_filename, options = {})
  arguments = @ffmpeg_arguments.dup
  if options[:audio_codec].to_s == 'none'
    arguments['an'] = true
  else
    case options[:audio_codec].try(:to_s)
      when 'aac'
        arguments['acodec'] = 'libfaac'
      when String
        arguments['acodec'] = options[:audio_codec]
    end
    arguments['ar'] = options[:audio_sample_rate] if options[:audio_sample_rate]
    arguments['ab'] = options[:audio_bitrate] if options[:audio_bitrate]
  end
  if options[:video_codec].to_s == 'none'
    arguments['vn'] = true
  else
    case options[:video_codec].try(:to_s)
      when 'h264'
        arguments['vcodec'] = 'libx264'
        arguments['vpre'] = ['medium', 'main']  # TODO: Allow override
        arguments['crf'] = 15                   # TODO: Allow override
        arguments['threads'] = 0
      when String
        arguments['vcodec'] = options[:video_codec]
    end
    arguments['b'] = options[:video_bitrate] if options[:video_bitrate]
    arguments['r'] = options[:video_frame_rate] if options[:video_frame_rate]
    arguments['s'] = "#{options[:width]}x#{options[:height]}" if options[:width] or options[:height]
    arguments['sameq'] = true
  end
  arguments['f'] = options[:format] if options[:format]

  progress, expected_duration = @progress, nil
  result_width, result_height = nil
  run_ffmpeg(input_filename, output_filename, arguments) do |line|
    if progress
      case line
        when /^\s*Duration: (\d+):(\d+):(\d+)\./
          unless expected_duration
            hours, minutes, seconds = $1.to_i, $2.to_i, $3.to_i
            expected_duration = seconds + minutes * 60 + hours * 60 * 60
          end
        when /^frame=.* time=(\d+)\./
          if expected_duration
            elapsed_time = $1.to_i
          end
        when /Stream.*Video: .*, (\d+)x(\d+)\s/
          unless result_width and result_height
            result_width, result_height = $1.to_i, $2.to_i
          end
      end
      progress.call(elapsed_time, expected_duration) if elapsed_time and expected_duration
    end
  end

  thumbnail_options = options[:thumbnail]
  if thumbnail_options
    thumb_width = thumbnail_options[:width].try(:to_i) || options[:width].try(:to_i)
    thumb_height = thumbnail_options[:height].try(:to_i) || options[:height].try(:to_i)
    if not thumbnail_options[:force_aspect_ratio] and result_width and result_height
      thumb_height = (thumb_width / (result_width / result_height.to_f)).to_i
    end
    at_seconds = thumbnail_options[:at_seconds].try(:to_f)
    at_seconds ||= (expected_duration || 0) * (thumbnail_options[:at_fraction].try(:to_f) || 0.5)
    @logger.info("Getting thumbnail frame (#{thumb_width}x#{thumb_height}) with FFmpeg at #{at_seconds} seconds")
    begin
      run_ffmpeg(input_filename, thumbnail_options[:filename], @ffmpeg_arguments.merge(
        :ss => at_seconds,
        :vcodec => :mjpeg,
        :vframes => 1,
        :an => true,
        :f => :rawvideo,
        :s => "#{thumb_width}x#{thumb_height}"))
    rescue CommandExecutionFailed => e
      @logger.error("Thumbnail rendering failed, ignoring: #{e}")
    end
  end
end