video_converter 0.7.6 → 0.7.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -16,14 +16,8 @@ module VideoConverter
16
16
 
17
17
  def initialize command, *params
18
18
  self.command = command.dup
19
- if params.any?
20
- if params[0].is_a?(Hash)
21
- self.command.gsub!(/%\{(\w+?)\}/) { |m| params[0][$1.to_sym] }
22
- else
23
- self.command.gsub!('?') { params.shift }
24
- end
25
- end
26
- raise ArgumentError.new("Command is not parsed '#{self.command}'") if self.command.include?('?') || self.command.match(/%{[\w\-.]+}/)
19
+ self.command.gsub!(/%\{(\w+?)\}/) { |m| params[0][$1.to_sym] } if params.any?
20
+ raise ArgumentError.new("Command is not parsed '#{self.command}'") if self.command.match(/%{[\w\-.]+}/)
27
21
  end
28
22
 
29
23
  def execute params = {}
@@ -4,7 +4,7 @@ module VideoConverter
4
4
  class Ffmpeg
5
5
  class << self
6
6
  attr_accessor :aliases, :bin, :ffprobe_bin
7
- attr_accessor :one_pass_command, :first_pass_command, :second_pass_command, :keyframes_command, :split_command, :concat_command, :mux_command
7
+ attr_accessor :one_pass_command, :first_pass_command, :second_pass_command, :keyframes_command, :split_command, :concat_command, :mux_command, :volume_detect_command
8
8
  end
9
9
 
10
10
  self.aliases = {
@@ -16,21 +16,22 @@ module VideoConverter
16
16
  :video_bitrate => 'b:v',
17
17
  :audio_bitrate => 'b:a',
18
18
  :size => 's',
19
- :video_filter => 'vf',
20
19
  :format => 'f',
21
20
  :bitstream_format => 'bsf',
22
- :pixel_format => 'pix_fmt'
21
+ :pixel_format => 'pix_fmt',
22
+ :audio_filter => 'af'
23
23
  }
24
24
  self.bin = '/usr/local/bin/ffmpeg'
25
25
  self.ffprobe_bin = '/usr/local/bin/ffprobe'
26
26
 
27
- self.one_pass_command = '%{bin} -i %{input} -y %{options} %{output} 1>>%{log} 2>&1 || exit 1'
28
- self.first_pass_command = '%{bin} -i %{input} -y -pass 1 -an %{options} /dev/null 1>>%{log} 2>&1 || exit 1'
29
- self.second_pass_command = '%{bin} -i %{input} -y -pass 2 %{options} %{output} 1>>%{log} 2>&1 || exit 1'
27
+ self.one_pass_command = '%{bin} -i %{input} %{watermark} -y %{options} %{output} 1>>%{log} 2>&1 || exit 1'
28
+ self.first_pass_command = '%{bin} -i %{input} %{watermark} -y -pass 1 -an %{options} /dev/null 1>>%{log} 2>&1 || exit 1'
29
+ self.second_pass_command = '%{bin} -i %{input} %{watermark} -y -pass 2 %{options} %{output} 1>>%{log} 2>&1 || exit 1'
30
30
  self.keyframes_command = '%{ffprobe_bin} -show_frames -select_streams v:0 -print_format csv %{input} | grep frame,video,1 | cut -d\',\' -f5 | tr "\n" "," | sed \'s/,$//\''
31
31
  self.split_command = '%{bin} -fflags +genpts -i %{input} %{options} %{output} 1>>%{log} 2>&1 || exit 1'
32
32
  self.concat_command = "%{bin} -f concat -i %{input} %{options} %{output} 1>>%{log} 2>&1 || exit 1"
33
33
  self.mux_command = "%{bin} %{inputs} %{maps} %{options} %{output} 1>>%{log} 2>&1 || exit 1"
34
+ self.volume_detect_command = "%{bin} -i %{input} -af volumedetect -f null - 2>&1"
34
35
 
35
36
  def self.split(input, output)
36
37
  output.options = { :format => 'segment', :map => 0, :codec => 'copy' }.merge(output.options)
@@ -64,13 +65,31 @@ module VideoConverter
64
65
  end
65
66
  # autodeinterlace
66
67
  output.options[:deinterlace] = input.metadata[:interlaced] if output.options[:deinterlace].nil?
67
- # video filter
68
- video_filter = [output.options[:video_filter]].compact
69
- video_filter << "scale=#{output.width}:trunc\\(ow/a/2\\)*2" if output.width && !output.height
70
- video_filter << "scale=trunc\\(oh*a/2\\)*2:#{output.height}" if output.height && !output.width
71
- video_filter << { 90 => 'transpose=2', 180 => 'transpose=2,transpose=2', 270 => 'transpose=1' }[output.rotate] if output.rotate
72
- video_filter << "crop=#{output.crop}" if output.crop
73
- output.options[:video_filter] = video_filter.join(',') if video_filter.any?
68
+ # volume
69
+ output.options[:audio_filter] = "volume=#{volume(output.volume)}" if output.volume
70
+ # filter_complex
71
+ filter_complex = [output.options[:filter_complex]].compact
72
+ filter_complex << "crop=#{output.crop}" if output.crop
73
+ if output.width || output.height
74
+ video_stream = input.metadata[:video_streams].first
75
+ output.width = (output.height * video_stream[:width].to_f / video_stream[:height].to_f / 2).to_i * 2 if output.height && !output.width
76
+ output.height = (output.width * video_stream[:height].to_f / video_stream[:width].to_f / 2).to_i * 2 if output.width && !output.height
77
+ filter_complex << "scale=#{scale(output.width, :w)}:#{scale(output.height, :h)}"
78
+ end
79
+ if output.watermarks && (output.watermarks[:width] || output.watermarks[:height])
80
+ filter_complex = ["[0:v] #{filter_complex.join(',')} [main]"]
81
+ filter_complex << "[1:v] scale=#{scale(output.watermarks[:width], :w, output.width)}:#{scale(output.watermarks[:height], :h, output.height)} [overlay]"
82
+ filter_complex << "[main] [overlay] overlay=#{overlay(output.watermarks[:x], :w)}:#{overlay(output.watermarks[:y], :h)}"
83
+ if output.rotate
84
+ filter_complex[filter_complex.count-1] += ' [overlayed]'
85
+ filter_complex << '[overlayed] ' + rotate(output.rotate)
86
+ end
87
+ output.options[:filter_complex] = "'#{filter_complex.join(';')}'"
88
+ else
89
+ filter_complex << "overlay=#{overlay(output.watermarks[:x], :w)}:#{overlay(output.watermarks[:y], :h)}" if output.watermarks
90
+ filter_complex << rotate(output.rotate) if output.rotate
91
+ output.options[:filter_complex] = filter_complex.join(',') if filter_complex.any?
92
+ end
74
93
 
75
94
  output.options[:format] ||= File.extname(output.filename).delete('.')
76
95
  output.options = {
@@ -159,6 +178,7 @@ module VideoConverter
159
178
  {
160
179
  :bin => bin,
161
180
  :input => input.to_s,
181
+ :watermark => (output.watermarks ? '-i ' + output.watermarks[:url] : ''),
162
182
  :options => output.options.map do |option, values|
163
183
  unless output.respond_to?(option)
164
184
  option = '-' + (aliases[option] || option).to_s
@@ -171,5 +191,34 @@ module VideoConverter
171
191
  :log => output.log
172
192
  }
173
193
  end
194
+
195
+ def scale(size, wh, percent_of = nil)
196
+ if size.to_s.end_with?('%')
197
+ percent_of ? (percent_of * size.to_f / 100).to_i : "i#{wh}*#{size.to_f/100}"
198
+ else
199
+ size || "trunc\\(o#{{:h => :w, :w => :h}[wh]}/a/2\\)*2"
200
+ end
201
+ end
202
+
203
+ def overlay(xy, wh)
204
+ if xy.to_s.end_with?('%')
205
+ xy = xy.to_f / 100
206
+ xy < 0 ? "main_#{wh}*#{1 + xy}-overlay_#{wh}" : "main_#{wh}*#{xy}"
207
+ else
208
+ xy.to_i < 0 ? "main_#{wh}-overlay_#{wh}#{xy}" : xy.to_i
209
+ end
210
+ end
211
+
212
+ def rotate(angle)
213
+ { 90 => 'transpose=2', 180 => 'transpose=2,transpose=2', 270 => 'transpose=1' }[angle]
214
+ end
215
+
216
+ def volume(level)
217
+ if level.to_s.end_with?('dB')
218
+ (level.to_f - input.mean_volume.to_f).round(4).to_s + 'dB'
219
+ else
220
+ level
221
+ end
222
+ end
174
223
  end
175
224
  end
@@ -66,6 +66,10 @@ module VideoConverter
66
66
  @metadata
67
67
  end
68
68
 
69
+ def mean_volume
70
+ @mean_volume ||= Command.new(Ffmpeg.volume_detect_command, :bin => Ffmpeg.bin, :input => input).capture.match(/mean_volume:\s([-\d.]+)\sdB/).to_a[1]
71
+ end
72
+
69
73
  def select_outputs(outputs)
70
74
  outputs.select { |output| !output.path || output.path == input }
71
75
  end
@@ -9,17 +9,19 @@ module VideoConverter
9
9
  self.log = 'converter.log'
10
10
  self.keyframe_interval_in_seconds = 4
11
11
 
12
- attr_accessor :chunks_dir, :crop, :drm, :faststart, :ffmpeg_output, :filename, :group, :height, :log, :no_fragments, :one_pass, :options, :path, :rotate, :streams, :thumbnails, :type, :uid, :width, :work_dir
12
+ attr_accessor :chunks_dir, :crop, :drm, :faststart, :ffmpeg_output, :filename, :group, :height, :log, :no_fragments, :one_pass, :options, :path, :rotate, :streams, :thumbnails, :type, :uid, :volume, :watermarks, :width, :work_dir
13
13
 
14
14
  def initialize params = {}
15
15
  self.work_dir = File.join(self.class.work_dir, params[:uid])
16
- FileUtils.mkdir_p(work_dir)
16
+ mkdir_mode = params.delete(:mkdir_mode)
17
+ mkdir_mode = mkdir_mode.to_i(8) if mkdir_mode.is_a?(String)
18
+ FileUtils.mkdir_p(work_dir, :mode => mkdir_mode)
17
19
  self.filename = params[:filename] or raise ArgumentError.new('Filename required')
18
20
  self.log = File.join(work_dir, self.class.log)
19
21
  self.type = params[:type] || 'default'
20
22
  if type == 'segmented'
21
23
  self.chunks_dir = File.join(work_dir, File.basename(filename, '.*'))
22
- FileUtils.mkdir_p(chunks_dir)
24
+ FileUtils.mkdir_p(chunks_dir, :mode => mkdir_mode)
23
25
  if File.extname(filename) == '.m3u8'
24
26
  self.ffmpeg_output = chunks_dir + '.ts'
25
27
  params[:format] = 'mpegts'
@@ -33,7 +35,7 @@ module VideoConverter
33
35
  self.ffmpeg_output = File.join(work_dir, filename)
34
36
  end
35
37
  raise ArgumentError.new('Invalid type') unless %w(default segmented playlist).include?(type)
36
- [:path, :streams, :width, :height, :one_pass, :rotate, :faststart, :thumbnails, :group, :drm, :no_fragments, :crop].each { |attr| self.send("#{attr}=", params[attr]) }
38
+ [:path, :streams, :width, :height, :one_pass, :rotate, :faststart, :thumbnails, :group, :drm, :no_fragments, :crop, :watermarks, :volume].each { |attr| self.send("#{attr}=", params[attr]) }
37
39
  [:video_bitrate, :audio_bitrate].each { |bitrate| params[bitrate] = "#{params[bitrate]}k" if params[bitrate].is_a?(Numeric) }
38
40
 
39
41
  # options will be substituted to convertation commands
@@ -1,3 +1,3 @@
1
1
  module VideoConverter
2
- VERSION = "0.7.6"
2
+ VERSION = "0.7.7"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: video_converter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.6
4
+ version: 0.7.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-09-04 00:00:00.000000000 Z
12
+ date: 2014-09-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: video_screenshoter
@@ -141,7 +141,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
141
141
  version: '0'
142
142
  segments:
143
143
  - 0
144
- hash: 3466758185986790975
144
+ hash: -3630338721637181246
145
145
  required_rubygems_version: !ruby/object:Gem::Requirement
146
146
  none: false
147
147
  requirements:
@@ -150,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
150
150
  version: '0'
151
151
  segments:
152
152
  - 0
153
- hash: 3466758185986790975
153
+ hash: -3630338721637181246
154
154
  requirements:
155
155
  - ffmpeg, version 1.2 or greated configured with libx264 and libfaac
156
156
  - live_segmenter to convert to hls