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.



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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/video_converter/ffmpeg.rb', line 71

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

  self.outputs.each do |output|
    # volume
    output.options[:audio_filter] = "volume=#{volume(output.volume)}" if output.volume
    unless output.options[:vn]
      # autorotate
      if output.type != 'playlist' && output.rotate == true
        output.rotate = input.[:rotate] ? 360 - input.[:rotate] : nil
      end
      # autocrop
      output.crop = input.crop_detect if output.type != 'playlist' && output.crop == true
      # autodeinterlace
      output.options[:deinterlace] = input.[:interlaced] if output.options[:deinterlace].nil?
      # filter_complex
      filter_complex = []
      filter_complex << "crop=#{output.crop.shellescape}" 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).to_s.shellescape}"
        elsif input.video_stream[:dar_width] && input.video_stream[:dar_height]
          filter_complex << "setdar=#{aspect(input, output)}"
        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
    else
      output.options.delete(:deinterlace)
      output.options.delete(:filter_complex)
    end
    output.options[:format] ||= File.extname(output.filename).delete('.')
    output.options[:format] = 'mpegts' if output.options[:format] == 'ts'
    output.options[:movflags] = '+faststart' if output.faststart || (output.faststart.nil? && %w(mov mp4).include?(output.options[:format].downcase))
    unless output.type == 'playlist'
      output.options = self.class.defaults.merge(output.options)
      output.options[:keyint_min] ||= output.options[:frame_rate]
      output.options[:keyframe_interval] = output.options[:keyint_min] * Output.keyframe_interval_in_seconds
    end
  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

.crop_detect_commandObject

Returns the value of attribute crop_detect_command.



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

def crop_detect_command
  @crop_detect_command
end

.defaultsObject

Returns the value of attribute defaults.



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

def defaults
  @defaults
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

.key_frames_commandObject

Returns the value of attribute key_frames_command.



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

def key_frames_command
  @key_frames_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.



69
70
71
# File 'lib/video_converter/ffmpeg.rb', line 69

def input
  @input
end

#outputsObject

Returns the value of attribute outputs.



69
70
71
# File 'lib/video_converter/ffmpeg.rb', line 69

def outputs
  @outputs
end

Class Method Details

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



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

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



61
62
63
64
65
66
67
# File 'lib/video_converter/ffmpeg.rb', line 61

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

.split(input, output) ⇒ Object



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

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



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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/video_converter/ffmpeg.rb', line 129

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")
      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), ['-filter_complex']).execute
    end

    qualities.each_with_index do |output, output_index|
      command = if one_pass?(qualities)
        Command.new(self.class.one_pass_command, self.class.prepare_params(input, output), ['-filter_complex'])
      elsif common_first_pass?(qualities)
        Command.new(self.class.second_pass_command, self.class.prepare_params(input, output), ['-filter_complex'])
      else
        output.options[:passlogfile] = File.join(output.work_dir, "group#{group_index}_#{output_index}.log")
        output.options[:force_key_frames] = input.[:video_start_time].step(input.[:duration_in_ms] / 1000.0, Output.keyframe_interval_in_seconds).map(&:floor).join(',')
        output.options[:sc_threshold] = 0
        output.options[:keyint_min] = output.options[:keyframe_interval] = nil
        Command.new(self.class.first_pass_command, self.class.prepare_params(input, output), ['-filter_complex']).append(
          Command.new(self.class.second_pass_command, self.class.prepare_params(input, output), ['-filter_complex'])
        )
      end

      # run ffmpeg
      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