video_converter 0.8.2 → 0.9.0

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.
@@ -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