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
|
-
|
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
|
-
#
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
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
|
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.
|
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-
|
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:
|
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:
|
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
|