video_converter 0.7.6 → 0.7.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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