video_converter 0.8.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,7 +5,6 @@ require "video_converter/array"
5
5
  require "video_converter/base"
6
6
  require "video_converter/command"
7
7
  require "video_converter/f4fpackager"
8
- require "video_converter/faststart"
9
8
  require "video_converter/ffmpeg"
10
9
  require "video_converter/hash"
11
10
  require "video_converter/input"
@@ -27,6 +26,6 @@ module VideoConverter
27
26
  self.paral = true
28
27
 
29
28
  def self.new params
30
- VideoConverter::Base.new params.deep_symbolize_keys.deep_shellescape_values
29
+ VideoConverter::Base.new params.deep_symbolize_keys
31
30
  end
32
31
  end
@@ -10,7 +10,7 @@ module VideoConverter
10
10
  end
11
11
 
12
12
  def run
13
- convert && faststart && make_screenshots && segment && encrypt && clear
13
+ convert && make_screenshots && segment && encrypt && clear
14
14
  end
15
15
 
16
16
  # XXX inject instead of each would be better
@@ -20,13 +20,6 @@ module VideoConverter
20
20
  success
21
21
  end
22
22
 
23
- # TODO use for faststart ffmpeg moveflags
24
- def faststart
25
- success = true
26
- outputs.each { |output| success &&= Faststart.new(output).run if output.faststart }
27
- success
28
- end
29
-
30
23
  def make_screenshots
31
24
  success = true
32
25
  outputs.each do |output|
@@ -45,18 +38,6 @@ module VideoConverter
45
38
  success
46
39
  end
47
40
 
48
- def split
49
- Ffmpeg.split(inputs.first, outputs.first)
50
- end
51
-
52
- def concat method = nil
53
- Ffmpeg.concat(inputs, outputs.first, method)
54
- end
55
-
56
- def mux
57
- Ffmpeg.mux(inputs, outputs.first)
58
- end
59
-
60
41
  def encrypt(options = {})
61
42
  outputs.each do |output|
62
43
  case output.drm
@@ -77,5 +58,17 @@ module VideoConverter
77
58
  outputs.select { |output| output.type == 'segmented' }.each { |output| Command.new("rm #{output.ffmpeg_output}").execute }
78
59
  true
79
60
  end
61
+
62
+ def split
63
+ Ffmpeg.split(inputs.first, outputs.first)
64
+ end
65
+
66
+ def concat method = nil
67
+ Ffmpeg.concat(inputs, outputs.first, method)
68
+ end
69
+
70
+ def mux
71
+ Ffmpeg.mux(inputs, outputs.first)
72
+ end
80
73
  end
81
74
  end
@@ -14,9 +14,19 @@ module VideoConverter
14
14
 
15
15
  attr_accessor :command
16
16
 
17
- def initialize command, *params
17
+ def initialize command, params = {}, safe_keys = []
18
18
  self.command = command.dup
19
- self.command.gsub!(/%\{(\w+?)\}/) { |m| params[0][$1.to_sym] } if params.any?
19
+ if params.any?
20
+ params = params.deep_shellescape_values(safe_keys)
21
+ self.command.gsub!(/%\{(\w+?)\}/) do
22
+ value = params[$1.to_sym]
23
+ if value.is_a?(Hash)
24
+ value.deep_join(' ')
25
+ else
26
+ value.to_s
27
+ end
28
+ end
29
+ end
20
30
  raise ArgumentError.new("Command is not parsed '#{self.command}'") if self.command.match(/%{[\w\-.]+}/)
21
31
  end
22
32
 
@@ -41,11 +41,11 @@ module VideoConverter
41
41
  {
42
42
  :bin => bin,
43
43
  :input_file => options[:input_file],
44
- :options => allowed_options.map do |option|
44
+ :options => Hash[*allowed_options.map do |option|
45
45
  if value = options[option.gsub('-', '_').to_sym]
46
- '--' + option + (value == true ? '' : ' ' + value.to_s)
46
+ ['--' + option, value]
47
47
  end
48
- end.compact.join(' '),
48
+ end.compact.flatten],
49
49
  :log => options[:log]
50
50
  }
51
51
  end
@@ -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, :volume_detect_command
7
+ attr_accessor :one_pass_command, :first_pass_command, :second_pass_command, :keyframes_command, :split_command, :concat_command, :mux_command, :volume_detect_command, :crop_detect_command
8
8
  end
9
9
 
10
10
  self.aliases = {
@@ -24,14 +24,15 @@ module VideoConverter
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} %{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
- 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
- self.split_command = '%{bin} -fflags +genpts -i %{input} %{options} %{output} 1>>%{log} 2>&1 || exit 1'
32
- self.concat_command = "%{bin} -f concat -i %{input} %{options} %{output} 1>>%{log} 2>&1 || exit 1"
27
+ self.one_pass_command = '%{bin} %{inputs} -y %{options} %{output} 1>>%{log} 2>&1 || exit 1'
28
+ self.first_pass_command = '%{bin} %{inputs} -y -pass 1 -an %{options} /dev/null 1>>%{log} 2>&1 || exit 1'
29
+ self.second_pass_command = '%{bin} %{inputs} -y -pass 2 %{options} %{output} 1>>%{log} 2>&1 || exit 1'
30
+ self.keyframes_command = '%{ffprobe_bin} -show_frames -select_streams v:0 -print_format csv %{inputs} | grep frame,video,1 | cut -d\',\' -f5 | tr "\n" "," | sed \'s/,$//\''
31
+ self.split_command = '%{bin} -fflags +genpts %{inputs} %{options} %{output} 1>>%{log} 2>&1 || exit 1'
32
+ self.concat_command = "%{bin} -f concat %{inputs} %{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
34
  self.volume_detect_command = "%{bin} -i %{input} -af volumedetect -c:v copy -f null - 2>&1"
35
+ self.crop_detect_command = "%{bin} -ss %{ss} -i %{input} -vframes %{vframes} -vf cropdetect=round=2 -c:a copy -f null - 2>&1"
35
36
 
36
37
  def self.split(input, output)
37
38
  output.options = { :format => 'segment', :map => 0, :codec => 'copy' }.merge(output.options)
@@ -47,8 +48,8 @@ module VideoConverter
47
48
  def self.mux(inputs, output)
48
49
  output.options = { :codec => 'copy' }.merge(output.options)
49
50
  Command.new(mux_command, prepare_params(nil, output).merge({
50
- :inputs => inputs.map { |i| "-i #{i}" }.join(' '),
51
- :maps => inputs.each_with_index.map { |_,i| "-map #{i}:0" }.join(' ')
51
+ :inputs => { '-i' => inputs },
52
+ :maps => { '-map' => inputs.each_with_index.map { |_,i| "#{i}:0" }.join(' ') }
52
53
  })).execute
53
54
  end
54
55
 
@@ -59,43 +60,51 @@ module VideoConverter
59
60
  self.outputs = input.select_outputs(outputs)
60
61
 
61
62
  self.outputs.each do |output|
62
- # autorotate
63
- if output.type != 'playlist' && [nil, true].include?(output.rotate) && input.metadata[:rotate]
64
- output.rotate = 360 - input.metadata[:rotate]
65
- end
66
- # autodeinterlace
67
- output.options[:deinterlace] = input.metadata[:interlaced] if output.options[:deinterlace].nil?
68
63
  # volume
69
64
  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
- output.width = (output.height * aspect(input, output)).ceil / 2 * 2 if output.height && !output.width
75
- output.height = (output.width / aspect(input, output)).ceil / 2 * 2 if output.width && !output.height
76
- filter_complex << "scale=#{scale(output.width, :w)}:#{scale(output.height, :h)}"
77
- if output.options[:aspect]
78
- filter_complex << "setdar=#{output.options.delete(:aspect)}"
79
- elsif input.video_stream[:dar_width] && input.video_stream[:dar_height]
80
- filter_complex << "setdar=#{input.video_stream[:dar_width]}:#{input.video_stream[:dar_height]}"
65
+ unless output.options[:vn]
66
+ # autorotate
67
+ if output.type != 'playlist' && output.rotate == true
68
+ output.rotate = input.metadata[:rotate] ? 360 - input.metadata[:rotate] : nil
81
69
  end
82
- end
83
- if output.watermarks && (output.watermarks[:width] || output.watermarks[:height])
84
- filter_complex = ["[0:v] #{filter_complex.join(',')} [main]"]
85
- filter_complex << "[1:v] scale=#{scale(output.watermarks[:width], :w, output.width)}:#{scale(output.watermarks[:height], :h, output.height)} [overlay]"
86
- filter_complex << "[main] [overlay] overlay=#{overlay(output.watermarks[:x], :w)}:#{overlay(output.watermarks[:y], :h)}"
87
- if output.rotate
88
- filter_complex[filter_complex.count-1] += ' [overlayed]'
89
- filter_complex << '[overlayed] ' + rotate(output.rotate)
70
+ # autocrop
71
+ output.crop = input.crop_detect if output.type != 'playlist' && output.crop == true
72
+ # autodeinterlace
73
+ output.options[:deinterlace] = input.metadata[:interlaced] if output.options[:deinterlace].nil?
74
+ # filter_complex
75
+ filter_complex = []
76
+ filter_complex << "crop=#{output.crop.shellescape}" if output.crop
77
+ if output.width || output.height
78
+ output.width = (output.height * aspect(input, output)).ceil / 2 * 2 if output.height && !output.width
79
+ output.height = (output.width / aspect(input, output)).ceil / 2 * 2 if output.width && !output.height
80
+ filter_complex << "scale=#{scale(output.width, :w)}:#{scale(output.height, :h)}"
81
+ if output.options[:aspect]
82
+ filter_complex << "setdar=#{output.options.delete(:aspect).to_s.shellescape}"
83
+ elsif input.video_stream[:dar_width] && input.video_stream[:dar_height]
84
+ filter_complex << "setdar=#{aspect(input, output)}"
85
+ end
86
+ end
87
+ if output.watermarks && (output.watermarks[:width] || output.watermarks[:height])
88
+ filter_complex = ["[0:v] #{filter_complex.join(',')} [main]"]
89
+ filter_complex << "[1:v] scale=#{scale(output.watermarks[:width], :w, output.width)}:#{scale(output.watermarks[:height], :h, output.height)} [overlay]"
90
+ filter_complex << "[main] [overlay] overlay=#{overlay(output.watermarks[:x], :w)}:#{overlay(output.watermarks[:y], :h)}"
91
+ if output.rotate
92
+ filter_complex[filter_complex.count-1] += ' [overlayed]'
93
+ filter_complex << '[overlayed] ' + rotate(output.rotate)
94
+ end
95
+ output.options[:filter_complex] = "'#{filter_complex.join(';')}'"
96
+ else
97
+ filter_complex << "overlay=#{overlay(output.watermarks[:x], :w)}:#{overlay(output.watermarks[:y], :h)}" if output.watermarks
98
+ filter_complex << rotate(output.rotate) if output.rotate
99
+ output.options[:filter_complex] = filter_complex.join(',') if filter_complex.any?
90
100
  end
91
- output.options[:filter_complex] = "'#{filter_complex.join(';')}'"
92
101
  else
93
- filter_complex << "overlay=#{overlay(output.watermarks[:x], :w)}:#{overlay(output.watermarks[:y], :h)}" if output.watermarks
94
- filter_complex << rotate(output.rotate) if output.rotate
95
- output.options[:filter_complex] = filter_complex.join(',') if filter_complex.any?
102
+ output.options.delete(:deinterlace)
103
+ output.options.delete(:filter_complex)
96
104
  end
97
-
98
105
  output.options[:format] ||= File.extname(output.filename).delete('.')
106
+ output.options[:format] = 'mpegts' if output.options[:format] == 'ts'
107
+ output.options[:movflags] = '+faststart' if output.faststart || (output.faststart.nil? && %w(mov mp4).include?(output.options[:format].downcase))
99
108
  output.options = {
100
109
  :threads => 1,
101
110
  :video_codec => 'libx264',
@@ -126,7 +135,7 @@ module VideoConverter
126
135
  # TODO compare by size
127
136
  res
128
137
  end.last
129
- success &&= Command.new(self.class.first_pass_command, self.class.prepare_params(input, best_quality)).execute
138
+ success &&= Command.new(self.class.first_pass_command, self.class.prepare_params(input, best_quality), ['-filter_complex']).execute
130
139
  end
131
140
 
132
141
  qualities.each_with_index do |output, output_index|
@@ -136,12 +145,12 @@ module VideoConverter
136
145
  self.class.second_pass_command
137
146
  else
138
147
  output.options[:passlogfile] = File.join(output.work_dir, "group#{group_index}_#{output_index}.log")
139
- output.options[:force_key_frames] = (input.metadata[:duration_in_ms] / 1000.0 / Output.keyframe_interval_in_seconds).ceil.times.to_a.map { |t| t * Output.keyframe_interval_in_seconds }.join(',')
148
+ output.options[:force_key_frames] = input.metadata[:video_start_time].step(input.metadata[:duration_in_ms] / 1000.0, Output.keyframe_interval_in_seconds).map(&:floor).join(',')
140
149
  Command.chain(self.class.first_pass_command, self.class.second_pass_command)
141
150
  end
142
151
 
143
152
  # run ffmpeg
144
- command = Command.new(command, self.class.prepare_params(input, output))
153
+ command = Command.new(command, self.class.prepare_params(input, output), ['-filter_complex'])
145
154
  if VideoConverter.paral
146
155
  threads << Thread.new { success &&= command.execute }
147
156
  else
@@ -158,14 +167,14 @@ module VideoConverter
158
167
  def self.concat_muxer(inputs, output)
159
168
  list = File.join(output.work_dir, 'list.txt')
160
169
  # NOTE ffmpeg concat list requires unescaped files
161
- File.write(list, inputs.map { |input| "file '#{File.absolute_path(input.unescape)}'" }.join("\n"))
170
+ File.write(list, inputs.map { |input| "file '#{File.absolute_path(input.to_s)}'" }.join("\n"))
162
171
  success = Command.new(concat_command, prepare_params(list, output)).execute
163
172
  FileUtils.rm list if success
164
173
  success
165
174
  end
166
175
 
167
176
  def self.concat_protocol(inputs, output)
168
- Command.new(one_pass_command, prepare_params('"concat:' + inputs.join('|') + '"', output)).execute
177
+ Command.new(one_pass_command, prepare_params('"concat:' + inputs.map { |input| input.to_s.shellescape }.join('|') + '"', output), [:inputs]).execute
169
178
  end
170
179
 
171
180
  def common_first_pass?(qualities)
@@ -181,16 +190,10 @@ module VideoConverter
181
190
 
182
191
  {
183
192
  :bin => bin,
184
- :input => input.to_s,
185
- :watermark => (output.watermarks ? '-i ' + output.watermarks[:url] : ''),
186
- :options => output.options.map do |option, values|
187
- unless output.respond_to?(option)
188
- option = '-' + (aliases[option] || option).to_s
189
- Array.wrap(values).map do |value|
190
- value == true ? option : "#{option} #{value}"
191
- end.join(' ')
192
- end
193
- end.compact.join(' '),
193
+ :inputs => { '-i' => output.watermarks ? [input.to_s, output.watermarks[:url]] : input.to_s },
194
+ :options => Hash[*output.options.select { |option, values| !output.respond_to?(option) }.map do |option, values|
195
+ ['-' + (aliases[option] || option).to_s, values]
196
+ end.flatten(1)],
194
197
  :output => output.ffmpeg_output,
195
198
  :log => output.log
196
199
  }
@@ -200,7 +203,7 @@ module VideoConverter
200
203
  if size.to_s.end_with?('%')
201
204
  percent_of ? (percent_of * size.to_f / 100).to_i : "i#{wh}*#{size.to_f/100}"
202
205
  else
203
- size || "trunc\\(o#{{:h => :w, :w => :h}[wh]}/a/2\\)*2"
206
+ size || "trunc\\(o#{{:h => 'w/', :w => 'h*'}[wh]}a/2\\)*2"
204
207
  end
205
208
  end
206
209
 
@@ -233,8 +236,15 @@ module VideoConverter
233
236
  Float(aspect)
234
237
  end
235
238
  else
236
- (input.video_stream[:dar_width] || input.video_stream[:width]).to_f / (input.video_stream[:dar_height] || input.video_stream[:height]).to_f
239
+ width_smaller_in = output.crop ? input.video_stream[:width].to_f / crop_parse(output.crop)[:width].to_f : 1
240
+ height_smaller_in = output.crop ? input.video_stream[:height].to_f / crop_parse(output.crop)[:height].to_f : 1
241
+ ((input.video_stream[:dar_width] || input.video_stream[:width]).to_f / width_smaller_in) /
242
+ ((input.video_stream[:dar_height] || input.video_stream[:height]).to_f / height_smaller_in)
237
243
  end
238
244
  end
245
+
246
+ def crop_parse(crop)
247
+ crop.match(/^(?:w=)?(?<width>\d+):(?:h=)?(?<height>\d+)(?::(?:x=)?(?<h>\d+):(?:y=)?(?<y>\d+))?$/) or raise 'Unsupported crop format'
248
+ end
239
249
  end
240
250
  end
@@ -12,13 +12,15 @@ class Hash
12
12
  self.replace(self.deep_symbolize_keys)
13
13
  end
14
14
 
15
- def deep_shellescape_values
15
+ def deep_shellescape_values(safe_keys = [])
16
16
  inject({}) do |options, (key, value)|
17
- if value.is_a? Array
18
- value = value.map { |v| v.is_a?(Hash) ? v.deep_shellescape_values : v.shellescape }
17
+ if safe_keys.include?(key)
18
+ value
19
+ elsif value.is_a? Array
20
+ value = value.map { |v| v.is_a?(Hash) ? v.deep_shellescape_values(safe_keys) : v.shellescape }
19
21
  elsif value.is_a? Hash
20
- value = value.deep_shellescape_values
21
- elsif value.is_a? String
22
+ value = value.deep_shellescape_values(safe_keys)
23
+ elsif value.is_a?(String) && !value.empty?
22
24
  value = value.shellescape
23
25
  end
24
26
  options[key] = value
@@ -26,7 +28,28 @@ class Hash
26
28
  end
27
29
  end
28
30
 
29
- def deep_shellescape_values!
30
- self.replace(self.deep_shellescape_values)
31
- end
31
+ def deep_shellescape_values!(safe_keys = [])
32
+ self.replace(self.deep_shellescape_values(safe_keys))
33
+ end
34
+
35
+ def deep_join(separator)
36
+ map do |key, value|
37
+ case value.class.to_s
38
+ when 'TrueClass'
39
+ key
40
+ when 'FalseClass', 'NilClass'
41
+ nil
42
+ when 'Array'
43
+ value.map { |v| "#{key} #{v}"}
44
+ when 'Hash'
45
+ value.deep_join(separator)
46
+ else
47
+ "#{key} #{value}"
48
+ end
49
+ end.join(separator)
50
+ end
51
+
52
+ def deep_join!(separator)
53
+ self.replace(self.deep_join(separator))
54
+ end
32
55
  end
@@ -21,10 +21,6 @@ module VideoConverter
21
21
  input
22
22
  end
23
23
 
24
- def unescape
25
- input.gsub(/\\+([^n])/, '\1')
26
- end
27
-
28
24
  def metadata
29
25
  unless @metadata
30
26
  @metadata = {}
@@ -74,6 +70,19 @@ module VideoConverter
74
70
  @mean_volume ||= Command.new(Ffmpeg.volume_detect_command, :bin => Ffmpeg.bin, :input => input).capture.match(/mean_volume:\s([-\d.]+)\sdB/).to_a[1]
75
71
  end
76
72
 
73
+ def crop_detect(samples = 5)
74
+ (@crop_detect ||= samples.times.map do |sample|
75
+ (metadata[:duration_in_ms] / (samples + 1) / 1000.0 * (sample + 1)).round
76
+ end.uniq.map do |ss|
77
+ Command.new(Ffmpeg.crop_detect_command, :bin => Ffmpeg.bin, :ss => ss, :input => input, :vframes => 2).capture
78
+ .match(/Parsed_cropdetect.+crop=(?<crop>(?<w>[-\d]+):(?<h>[-\d]+):(?<x>\d+):(?<y>\d+))/)
79
+ end.compact.max do |m1, m2|
80
+ res = m1[:h].to_i <=> m2[:h].to_i
81
+ res = m1[:w].to_i <=> m2[:w].to_i if res == 0
82
+ res
83
+ end || {})[:crop]
84
+ end
85
+
77
86
  def select_outputs(outputs)
78
87
  outputs.select { |output| !output.path || output.path == input }
79
88
  end
@@ -111,8 +120,7 @@ module VideoConverter
111
120
  end
112
121
 
113
122
  def is_local?
114
- # NOTE method file escapes himself
115
- File.file?(input.gsub('\\', ''))
123
+ File.file?(input)
116
124
  end
117
125
 
118
126
  end
@@ -35,7 +35,7 @@ module VideoConverter
35
35
  res += "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=#{stream[:bandwidth].to_i * 1000}\n"
36
36
  res += stream[:path] + "\n"
37
37
  end
38
- res += "#EXT-X-ENDLIST"
38
+ res += "#EXT-X-ENDLIST\n"
39
39
  File.open(File.join(playlist.work_dir, playlist.filename), 'w') { |f| f.write res }
40
40
  true
41
41
  end
@@ -27,7 +27,7 @@ module VideoConverter
27
27
  {
28
28
  :work_dir => outputs.select { |output| output.type != 'playlist' }.first.work_dir,
29
29
  :bin => bin,
30
- :inputs => outputs.select { |output| output.type != 'playlist' }.map { |input| "--src #{File.basename(input.ffmpeg_output)}" }.join(' '),
30
+ :inputs => { '--src' => outputs.select { |output| output.type != 'playlist' }.map { |input| "#{File.basename(input.ffmpeg_output)}" } },
31
31
  :manifest => outputs.detect { |output| output.type == 'playlist' }.ffmpeg_output,
32
32
  :log => outputs.first.log
33
33
  }
@@ -28,7 +28,7 @@ module VideoConverter
28
28
 
29
29
  def self.get_encryption_key_path(output)
30
30
  if output.encryption_key
31
- File.open(File.join(output.work_dir,'video.key'), 'wb') { |f| f.puts output.encryption_key }
31
+ File.open(File.join(output.work_dir,'video.key'), 'wb') { |f| f.write output.encryption_key }
32
32
  'video.key'
33
33
  elsif output.encryption_key_url
34
34
  uri = URI(output.encryption_key_url)
@@ -1,3 +1,3 @@
1
1
  module VideoConverter
2
- VERSION = "0.8.2"
2
+ VERSION = "0.9.0"
3
3
  end
Binary file
Binary file
@@ -61,6 +61,40 @@ class VideoConverterTest < Test::Unit::TestCase
61
61
  assert_equal 240, m[:height].to_i
62
62
  end
63
63
  end
64
+
65
+ context 'with watermarks' do
66
+ setup do
67
+ (@c = VideoConverter.new(
68
+ :input => 'test/fixtures/test (1).mp4',
69
+ :outputs => [
70
+ { :video_bitrate => 300, :filename => 'res.mp4', :watermarks => {
71
+ :url => 'test/fixtures/logo.png', :x=>'-3%', :y=>'-3%', :height=>'3%'
72
+ }, :height => 240 },
73
+ ]
74
+ )).run
75
+ end
76
+
77
+ should 'be ok' do
78
+ assert File.exists?(@c.outputs.first.ffmpeg_output)
79
+ end
80
+ end
81
+
82
+ context 'with autocrop' do
83
+ setup do
84
+ (@c = VideoConverter.new(
85
+ :input => 'test/fixtures/test_crop.mp4',
86
+ :outputs => [
87
+ { :video_bitrate => 300, :filename => 'res.mp4', :crop => true },
88
+ ]
89
+ )).run
90
+ end
91
+
92
+ should 'crop' do
93
+ m = VideoConverter.new(:input => @c.outputs.first.ffmpeg_output).inputs.first.video_stream
94
+ assert_equal 406, m[:height].to_i
95
+ assert_equal 720, m[:width].to_i
96
+ end
97
+ end
64
98
  end
65
99
 
66
100
  context 'segmentation' do
@@ -154,8 +188,8 @@ class VideoConverterTest < Test::Unit::TestCase
154
188
  end
155
189
 
156
190
  assert_equal(
157
- (k1 = VideoConverter::Command.new(VideoConverter::Ffmpeg.keyframes_command, :ffprobe_bin => VideoConverter::Ffmpeg.ffprobe_bin, :input => File.join(@c.outputs.first.work_dir, 'sd1.mp4')).capture),
158
- (k2 = VideoConverter::Command.new(VideoConverter::Ffmpeg.keyframes_command, :ffprobe_bin => VideoConverter::Ffmpeg.ffprobe_bin, :input => File.join(@c.outputs.first.work_dir, 'sd2.mp4')).capture)
191
+ (k1 = VideoConverter::Command.new(VideoConverter::Ffmpeg.keyframes_command, :ffprobe_bin => VideoConverter::Ffmpeg.ffprobe_bin, :inputs => File.join(@c.outputs.first.work_dir, 'sd1.mp4')).capture),
192
+ (k2 = VideoConverter::Command.new(VideoConverter::Ffmpeg.keyframes_command, :ffprobe_bin => VideoConverter::Ffmpeg.ffprobe_bin, :inputs => File.join(@c.outputs.first.work_dir, 'sd2.mp4')).capture)
159
193
  )
160
194
  end
161
195
  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.8.2
4
+ version: 0.9.0
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-11-13 00:00:00.000000000 Z
12
+ date: 2015-03-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: video_screenshoter
@@ -110,7 +110,6 @@ files:
110
110
  - lib/video_converter/base.rb
111
111
  - lib/video_converter/command.rb
112
112
  - lib/video_converter/f4fpackager.rb
113
- - lib/video_converter/faststart.rb
114
113
  - lib/video_converter/ffmpeg.rb
115
114
  - lib/video_converter/hash.rb
116
115
  - lib/video_converter/input.rb
@@ -122,7 +121,9 @@ files:
122
121
  - lib/video_converter/version.rb
123
122
  - test/fixtures/chunk0.ts
124
123
  - test/fixtures/chunk1.ts
124
+ - test/fixtures/logo.png
125
125
  - test/fixtures/test (1).mp4
126
+ - test/fixtures/test_crop.mp4
126
127
  - test/fixtures/test_playlist.m3u8
127
128
  - test/test_helper.rb
128
129
  - test/video_converter_test.rb
@@ -142,7 +143,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
142
143
  version: '0'
143
144
  segments:
144
145
  - 0
145
- hash: 25263521592852440
146
+ hash: -163534295853469513
146
147
  required_rubygems_version: !ruby/object:Gem::Requirement
147
148
  none: false
148
149
  requirements:
@@ -151,7 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
151
152
  version: '0'
152
153
  segments:
153
154
  - 0
154
- hash: 25263521592852440
155
+ hash: -163534295853469513
155
156
  requirements:
156
157
  - ffmpeg, version 1.2 or greated configured with libx264 and libfaac
157
158
  - live_segmenter to convert to hls
@@ -163,7 +164,9 @@ summary: Ffmpeg, mencoder based converter to mp4, m3u8
163
164
  test_files:
164
165
  - test/fixtures/chunk0.ts
165
166
  - test/fixtures/chunk1.ts
167
+ - test/fixtures/logo.png
166
168
  - test/fixtures/test (1).mp4
169
+ - test/fixtures/test_crop.mp4
167
170
  - test/fixtures/test_playlist.m3u8
168
171
  - test/test_helper.rb
169
172
  - test/video_converter_test.rb
@@ -1,35 +0,0 @@
1
- # encoding: utf-8
2
-
3
- module VideoConverter
4
- class Faststart
5
- class << self
6
- attr_accessor :bin, :command
7
- end
8
- self.bin = '/usr/local/bin/qt-faststart'
9
- self.command = '%{bin} %{input} %{moov_atom} && mv %{moov_atom} %{input} 1>>%{log} 2>&1 || exit 1'
10
-
11
- attr_accessor :output
12
-
13
- def initialize output
14
- self.output = output
15
- end
16
-
17
- def run
18
- success = true
19
- success &&= Command.new(self.class.command, prepare_params(output)).execute
20
- success
21
- end
22
-
23
- private
24
-
25
- def prepare_params(output)
26
- {
27
- :bin => self.class.bin,
28
- :log => output.log,
29
- :input => output.ffmpeg_output,
30
- :moov_atom => "#{output.ffmpeg_output}.mov"
31
- }
32
- end
33
-
34
- end
35
- end