Class: VideoConverter::Ffmpeg

Inherits:
Object
  • Object
show all
Defined in:
lib/video_converter/ffmpeg.rb

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(input, outputs) ⇒ Ffmpeg

Returns a new instance of Ffmpeg.



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
97
98
99
100
101
102
103
104
105
106
# File 'lib/video_converter/ffmpeg.rb', line 57

def initialize input, outputs
  self.input = input      
  self.outputs = input.select_outputs(outputs)

  self.outputs.each do |output|
    # autorotate
    if output.type != 'playlist' && [nil, true].include?(output.rotate) && input.[:rotate]
      output.rotate = 360 - input.[:rotate]
    end
    # autodeinterlace
    output.options[:deinterlace] = input.[:interlaced] if output.options[:deinterlace].nil?
    # volume
    output.options[:audio_filter] = "volume=#{volume(output.volume)}" if output.volume
    # filter_complex
    filter_complex = [output.options[:filter_complex]].compact
    filter_complex << "crop=#{output.crop}" if output.crop
    if output.width || output.height
      output.width = (output.height * aspect(input, output)).ceil / 2 * 2 if output.height && !output.width
      output.height = (output.width / aspect(input, output)).ceil / 2 * 2 if output.width && !output.height
      filter_complex << "scale=#{scale(output.width, :w)}:#{scale(output.height, :h)}"
      if output.options[:aspect]
        filter_complex << "setdar=#{output.options.delete(:aspect)}"
      elsif input.video_stream[:dar_width] && input.video_stream[:dar_height]
        filter_complex << "setdar=#{input.video_stream[:dar_width]}:#{input.video_stream[:dar_height]}"
      end
    end
    if output.watermarks && (output.watermarks[:width] || output.watermarks[:height])
      filter_complex = ["[0:v] #{filter_complex.join(',')} [main]"]
      filter_complex << "[1:v] scale=#{scale(output.watermarks[:width], :w, output.width)}:#{scale(output.watermarks[:height], :h, output.height)} [overlay]"
      filter_complex << "[main] [overlay] overlay=#{overlay(output.watermarks[:x], :w)}:#{overlay(output.watermarks[:y], :h)}"
      if output.rotate
        filter_complex[filter_complex.count-1] += ' [overlayed]'
        filter_complex << '[overlayed] ' + rotate(output.rotate)
      end
      output.options[:filter_complex] = "'#{filter_complex.join(';')}'"
    else
      filter_complex << "overlay=#{overlay(output.watermarks[:x], :w)}:#{overlay(output.watermarks[:y], :h)}" if output.watermarks
      filter_complex << rotate(output.rotate) if output.rotate
      output.options[:filter_complex] = filter_complex.join(',') if filter_complex.any?
    end

    output.options[:format] ||= File.extname(output.filename).delete('.')
    output.options = { 
      :threads => 1, 
      :video_codec => 'libx264', 
      :audio_codec => 'libfaac', 
      :pixel_format => 'yuv420p' 
    }.merge(output.options) unless output.type == 'playlist'
  end
end

Class Attribute Details

.aliasesObject

Returns the value of attribute aliases.



6
7
8
# File 'lib/video_converter/ffmpeg.rb', line 6

def aliases
  @aliases
end

.binObject

Returns the value of attribute bin.



6
7
8
# File 'lib/video_converter/ffmpeg.rb', line 6

def bin
  @bin
end

.concat_commandObject

Returns the value of attribute concat_command.



7
8
9
# File 'lib/video_converter/ffmpeg.rb', line 7

def concat_command
  @concat_command
end

.ffprobe_binObject

Returns the value of attribute ffprobe_bin.



6
7
8
# File 'lib/video_converter/ffmpeg.rb', line 6

def ffprobe_bin
  @ffprobe_bin
end

.first_pass_commandObject

Returns the value of attribute first_pass_command.



7
8
9
# File 'lib/video_converter/ffmpeg.rb', line 7

def first_pass_command
  @first_pass_command
end

.keyframes_commandObject

Returns the value of attribute keyframes_command.



7
8
9
# File 'lib/video_converter/ffmpeg.rb', line 7

def keyframes_command
  @keyframes_command
end

.mux_commandObject

Returns the value of attribute mux_command.



7
8
9
# File 'lib/video_converter/ffmpeg.rb', line 7

def mux_command
  @mux_command
end

.one_pass_commandObject

Returns the value of attribute one_pass_command.



7
8
9
# File 'lib/video_converter/ffmpeg.rb', line 7

def one_pass_command
  @one_pass_command
end

.second_pass_commandObject

Returns the value of attribute second_pass_command.



7
8
9
# File 'lib/video_converter/ffmpeg.rb', line 7

def second_pass_command
  @second_pass_command
end

.split_commandObject

Returns the value of attribute split_command.



7
8
9
# File 'lib/video_converter/ffmpeg.rb', line 7

def split_command
  @split_command
end

.volume_detect_commandObject

Returns the value of attribute volume_detect_command.



7
8
9
# File 'lib/video_converter/ffmpeg.rb', line 7

def volume_detect_command
  @volume_detect_command
end

Instance Attribute Details

#inputObject

Returns the value of attribute input.



55
56
57
# File 'lib/video_converter/ffmpeg.rb', line 55

def input
  @input
end

#outputsObject

Returns the value of attribute outputs.



55
56
57
# File 'lib/video_converter/ffmpeg.rb', line 55

def outputs
  @outputs
end

Class Method Details

.concat(inputs, output, method = nil) ⇒ Object



41
42
43
44
45
# File 'lib/video_converter/ffmpeg.rb', line 41

def self.concat(inputs, output, method = nil)
  method = %w(ts mpg mpeg).include?(File.extname(inputs.first.to_s).delete('.')) ? :protocol : :muxer unless method
  output.options = { :codec => 'copy' }.merge(output.options)
  send("concat_#{method}", inputs, output)
end

.mux(inputs, output) ⇒ Object



47
48
49
50
51
52
53
# File 'lib/video_converter/ffmpeg.rb', line 47

def self.mux(inputs, output)
  output.options = { :codec => 'copy' }.merge(output.options)
  Command.new(mux_command, prepare_params(nil, output).merge({
    :inputs => inputs.map { |i| "-i #{i}" }.join(' '),
    :maps => inputs.each_with_index.map { |_,i| "-map #{i}:0" }.join(' ')
  })).execute
end

.split(input, output) ⇒ Object



36
37
38
39
# File 'lib/video_converter/ffmpeg.rb', line 36

def self.split(input, output)
  output.options = { :format => 'segment', :map => 0, :codec => 'copy' }.merge(output.options)
  Command.new(split_command, prepare_params(input, output)).execute
end

Instance Method Details

#runObject



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/video_converter/ffmpeg.rb', line 108

def run
  success = true
  threads = []

  input.output_groups(outputs).each_with_index do |group, group_index|
    qualities = group.select { |output| output.type != 'playlist' }

    # common first pass
    if !one_pass?(qualities) && common_first_pass?(qualities)
      qualities.each do |output| 
        output.options[:passlogfile] = File.join(output.work_dir, "group#{group_index}.log")
        output.options[:keyint_min] = 25
        output.options[:keyframe_interval] = 100
      end
      best_quality = qualities.sort do |q1, q2|
        res = q1.options[:video_bitrate].to_i <=> q2.options[:video_bitrate].to_i
        res = q1.height.to_i <=> q2.height.to_i if res == 0
        res = q1.width.to_i <=> q2.width.to_i if res == 0
        # TODO compare by size
        res
      end.last
      success &&= Command.new(self.class.first_pass_command, self.class.prepare_params(input, best_quality)).execute
    end

    qualities.each_with_index do |output, output_index|
      command = if one_pass?(qualities)
        self.class.one_pass_command
      elsif common_first_pass?(qualities)
        self.class.second_pass_command
      else
        output.options[:passlogfile] = File.join(output.work_dir, "group#{group_index}_#{output_index}.log")
        output.options[:force_key_frames] = (input.[:duration_in_ms] / 1000.0 / Output.keyframe_interval_in_seconds).ceil.times.to_a.map { |t| t * Output.keyframe_interval_in_seconds }.join(',')
        Command.chain(self.class.first_pass_command, self.class.second_pass_command)
      end

      # run ffmpeg
      command = Command.new(command, self.class.prepare_params(input, output))
      if VideoConverter.paral
        threads << Thread.new { success &&= command.execute }
      else
        success &&= command.execute
      end
    end
  end
  threads.each { |t| t.join } if VideoConverter.paral
  success
end