video_converter 0.9.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4fdb79738807817fb560768a6c96967624eac3c3
4
- data.tar.gz: 61c4941087d486940d2c3420797c71d1f5aa8e83
3
+ metadata.gz: 16c106529bdbd5b91396f8864f23d01ebb6ab2f1
4
+ data.tar.gz: 8bfcbdd50a8641a38b52096223a33a4015b752d8
5
5
  SHA512:
6
- metadata.gz: 19e2a3ea041ca0d766d15748af7f7cd9f580e9b505766d25f7aa67380c483988afdac23a1d2a23979b0cc6a3265921d7d0962eca4fd9472e9171fddab15ed249
7
- data.tar.gz: 7b01bdaefe59e061815d1ea818c5563fe43a420a9073ce49b6b021625cf13752f2638dad808fc098867206b964a8a45d3594253c68a4b5da258711eabf30f5d9
6
+ metadata.gz: b039f0550490cf96c4db35927cd138a4a839224b23b04aa1aa69dea55be3ee13b3fd335b8ec9b40b301fabfbaf919dcff86718d2b5c2b02cbcc9bde820e96245
7
+ data.tar.gz: 4d03de51303d79eded64eeffa7421a1916213ee20eb11f72e7070e5f421db492713e5c50af11ca7b99d0bb9a3d15845938bbff9a7d39a0bd200310cf8cd3f8a6
data/.gitignore CHANGED
@@ -3,6 +3,7 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
+ .rvmrc
6
7
  Gemfile.lock
7
8
  InstalledFiles
8
9
  _yardoc
@@ -18,4 +19,4 @@ test/fixtures/*.log*
18
19
  tmp
19
20
  *.swp
20
21
  log
21
- .tags
22
+ .tags
@@ -1,6 +1,8 @@
1
1
  require "fileutils"
2
2
  require "net/http"
3
3
  require "shellwords"
4
+ require 'securerandom'
5
+
4
6
  require "video_converter/array"
5
7
  require "video_converter/base"
6
8
  require "video_converter/command"
@@ -3,15 +3,11 @@
3
3
  module VideoConverter
4
4
  class Command
5
5
  class << self
6
- attr_accessor :dry_run, :verbose
6
+ attr_accessor :dry_run, :verbose, :nice, :ionice
7
7
  end
8
8
  self.dry_run = false
9
9
  self.verbose = true
10
10
 
11
- def self.chain(*commands)
12
- commands.map { |c| "(#{c})" }.join(' && ')
13
- end
14
-
15
11
  attr_accessor :command
16
12
 
17
13
  def initialize command, params = {}, safe_keys = []
@@ -28,6 +24,8 @@ module VideoConverter
28
24
  end
29
25
  end
30
26
  raise ArgumentError.new("Command is not parsed '#{self.command}'") if self.command.match(/%{[\w\-.]+}/)
27
+ self.command = "nice -n #{self.class.nice} #{self.command}" if self.class.nice
28
+ self.command = "ionice -c 2 -n #{self.class.ionice} #{self.command}" if self.class.ionice
31
29
  end
32
30
 
33
31
  def execute params = {}
@@ -47,5 +45,10 @@ module VideoConverter
47
45
  def to_s
48
46
  command
49
47
  end
48
+
49
+ def append(*commands)
50
+ self.command = commands.unshift(command).map { |c| "(#{c})" }.join(' && ')
51
+ self
52
+ end
50
53
  end
51
54
  end
@@ -4,7 +4,7 @@ module VideoConverter
4
4
  class Ffmpeg
5
5
  class << self
6
6
  attr_accessor :aliases, :defaults, :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, :crop_detect_command
7
+ attr_accessor :one_pass_command, :first_pass_command, :second_pass_command, :split_command, :concat_command, :mux_command, :volume_detect_command, :crop_detect_command, :key_frames_command
8
8
  end
9
9
 
10
10
  self.aliases = {
@@ -22,12 +22,12 @@ module VideoConverter
22
22
  :audio_filter => 'af',
23
23
  :profile => 'vprofile'
24
24
  }
25
- self.defaults = {
25
+ self.defaults = {
26
26
  :threads => 1,
27
27
  :video_codec => 'libx264',
28
28
  :audio_codec => 'libfaac',
29
29
  :pixel_format => 'yuv420p',
30
- :frame_rate => 24,
30
+ :frame_rate => 25,
31
31
  :preset => 'medium',
32
32
  :profile => 'main',
33
33
  :level => 31,
@@ -36,22 +36,22 @@ module VideoConverter
36
36
  }
37
37
  self.bin = '/usr/local/bin/ffmpeg'
38
38
  self.ffprobe_bin = '/usr/local/bin/ffprobe'
39
-
39
+
40
40
  self.one_pass_command = '%{bin} %{inputs} -y %{options} %{output} 1>>%{log} 2>&1 || exit 1'
41
41
  self.first_pass_command = '%{bin} %{inputs} -y -pass 1 -an %{options} /dev/null 1>>%{log} 2>&1 || exit 1'
42
42
  self.second_pass_command = '%{bin} %{inputs} -y -pass 2 %{options} %{output} 1>>%{log} 2>&1 || exit 1'
43
- 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/,$//\''
44
43
  self.split_command = '%{bin} -fflags +genpts %{inputs} %{options} %{output} 1>>%{log} 2>&1 || exit 1'
45
44
  self.concat_command = "%{bin} -f concat %{inputs} %{options} %{output} 1>>%{log} 2>&1 || exit 1"
46
45
  self.mux_command = "%{bin} %{inputs} %{maps} %{options} %{output} 1>>%{log} 2>&1 || exit 1"
47
46
  self.volume_detect_command = "%{bin} -i %{input} -af volumedetect -c:v copy -f null - 2>&1"
48
47
  self.crop_detect_command = "%{bin} -ss %{ss} -i %{input} -vframes %{vframes} -vf cropdetect=round=2 -c:a copy -f null - 2>&1"
48
+ self.key_frames_command = "%{bin} -i %{input} -an -vf \"select=eq(pict_type\\,PICT_TYPE_I),showinfo\" -f null - 2>&1"
49
49
 
50
50
  def self.split(input, output)
51
51
  output.options = { :format => 'segment', :map => 0, :codec => 'copy' }.merge(output.options)
52
52
  Command.new(split_command, prepare_params(input, output)).execute
53
53
  end
54
-
54
+
55
55
  def self.concat(inputs, output, method = nil)
56
56
  method = %w(ts mpg mpeg).include?(File.extname(inputs.first.to_s).delete('.')) ? :protocol : :muxer unless method
57
57
  output.options = { :codec => 'copy' }.merge(output.options)
@@ -65,11 +65,11 @@ module VideoConverter
65
65
  :maps => { '-map' => inputs.each_with_index.map { |_,i| "#{i}:0" }.join(' ') }
66
66
  })).execute
67
67
  end
68
-
68
+
69
69
  attr_accessor :input, :outputs
70
70
 
71
71
  def initialize input, outputs
72
- self.input = input
72
+ self.input = input
73
73
  self.outputs = input.select_outputs(outputs)
74
74
 
75
75
  self.outputs.each do |output|
@@ -135,7 +135,7 @@ module VideoConverter
135
135
 
136
136
  # common first pass
137
137
  if !one_pass?(qualities) && common_first_pass?(qualities)
138
- qualities.each do |output|
138
+ qualities.each do |output|
139
139
  output.options[:passlogfile] = File.join(output.work_dir, "group#{group_index}.log")
140
140
  end
141
141
  best_quality = qualities.sort do |q1, q2|
@@ -150,18 +150,20 @@ module VideoConverter
150
150
 
151
151
  qualities.each_with_index do |output, output_index|
152
152
  command = if one_pass?(qualities)
153
- self.class.one_pass_command
153
+ Command.new(self.class.one_pass_command, self.class.prepare_params(input, output), ['-filter_complex'])
154
154
  elsif common_first_pass?(qualities)
155
- self.class.second_pass_command
155
+ Command.new(self.class.second_pass_command, self.class.prepare_params(input, output), ['-filter_complex'])
156
156
  else
157
157
  output.options[:passlogfile] = File.join(output.work_dir, "group#{group_index}_#{output_index}.log")
158
158
  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(',')
159
- output.options[:keyint_min], output.options[:keyframe_interval] = output.options[:keyframe_interval], nil
160
- Command.chain(self.class.first_pass_command, self.class.second_pass_command)
159
+ output.options[:sc_threshold] = 0
160
+ output.options[:keyint_min] = output.options[:keyframe_interval] = nil
161
+ Command.new(self.class.first_pass_command, self.class.prepare_params(input, output), ['-filter_complex']).append(
162
+ Command.new(self.class.second_pass_command, self.class.prepare_params(input, output), ['-filter_complex'])
163
+ )
161
164
  end
162
165
 
163
166
  # run ffmpeg
164
- command = Command.new(command, self.class.prepare_params(input, output), ['-filter_complex'])
165
167
  if VideoConverter.paral
166
168
  threads << Thread.new { success &&= command.execute }
167
169
  else
@@ -173,7 +175,7 @@ module VideoConverter
173
175
  success
174
176
  end
175
177
 
176
- private
178
+ private
177
179
 
178
180
  def self.concat_muxer(inputs, output)
179
181
  list = File.join(output.work_dir, 'list.txt')
@@ -198,7 +200,7 @@ module VideoConverter
198
200
  end
199
201
 
200
202
  def self.prepare_params input, output
201
-
203
+
202
204
  {
203
205
  :bin => bin,
204
206
  :inputs => { '-i' => output.watermarks ? [input.to_s, output.watermarks[:url]] : input.to_s },
@@ -66,6 +66,10 @@ module VideoConverter
66
66
  metadata[:video_streams].first
67
67
  end
68
68
 
69
+ def audio_stream
70
+ metadata[:audio_streams].first
71
+ end
72
+
69
73
  def mean_volume
70
74
  @mean_volume ||= Command.new(Ffmpeg.volume_detect_command, :bin => Ffmpeg.bin, :input => input).capture.match(/mean_volume:\s([-\d.]+)\sdB/).to_a[1]
71
75
  end
@@ -83,6 +87,14 @@ module VideoConverter
83
87
  end || {})[:crop]
84
88
  end
85
89
 
90
+ def key_frames
91
+ @key_frames ||= Command.new(Ffmpeg.key_frames_command, :bin => Ffmpeg.bin, :input => input).capture.split("\n").map do |l|
92
+ if m = l.match(/pts:\s*(\d+)\s*pts_time:\s*(\d+(?:\.\d+)?)/)
93
+ { :pts => m[1].to_i, :pts_time => m[2].to_f }
94
+ end
95
+ end.compact
96
+ end
97
+
86
98
  def select_outputs(outputs)
87
99
  outputs.select { |output| !output.path || output.path == input }
88
100
  end
@@ -91,11 +103,11 @@ module VideoConverter
91
103
  # qualities with the same group param are one group
92
104
  groups = Hash.new([])
93
105
  outputs.each { |output| groups[output.group] += [output] if output.group.present? }
94
- groups = groups.values
106
+ groups = groups.values
95
107
 
96
108
  # qualities of one playlist are one group
97
109
  groups += outputs.select { |output| output.type == 'playlist' }.map { |playlist| playlist.output_group(outputs) }
98
-
110
+
99
111
  # other outputs are separate groups
100
112
  (outputs - groups.flatten).each { |output| groups << [output] }
101
113
  groups
@@ -118,7 +130,7 @@ module VideoConverter
118
130
  def is_http?
119
131
  !!input.match(/^http:\/\//)
120
132
  end
121
-
133
+
122
134
  def is_local?
123
135
  File.file?(input)
124
136
  end
@@ -35,7 +35,8 @@ module VideoConverter
35
35
  File.open(File.join(output.work_dir,'url.video.key'), 'wb') { |f| f.puts Net::HTTP.get(uri.host, uri.path) }
36
36
  output.encryption_key_url
37
37
  else
38
- nil
38
+ File.open(File.join(output.work_dir, "#{output.filename}.key"), 'wb') { |f| f.write SecureRandom.hex(8) }
39
+ "#{output.filename}.key"
39
40
  end
40
41
  end
41
42
 
@@ -45,7 +46,7 @@ module VideoConverter
45
46
  elsif output.encryption_key_url
46
47
  File.join(output.work_dir, "url.video.key")
47
48
  else
48
- nil
49
+ File.join(output.work_dir, "#{output.filename}.key")
49
50
  end
50
51
  end
51
52
 
@@ -1,3 +1,3 @@
1
1
  module VideoConverter
2
- VERSION = "0.9.1"
2
+ VERSION = "0.10.0"
3
3
  end
Binary file
@@ -5,10 +5,10 @@ class VideoConverterTest < Test::Unit::TestCase
5
5
  context 'with thumbnails' do
6
6
  setup do
7
7
  (@c = VideoConverter.new(
8
- :input => 'test/fixtures/test (1).mp4',
8
+ :input => 'test/fixtures/test (1).mp4',
9
9
  :outputs => [
10
- { :video_bitrate => 300, :filename => 'q1.mp4' },
11
- { :video_bitrate => 400, :filename => 'q2.mp4', :thumbnails => {
10
+ { :video_bitrate => 300, :filename => 'q1.mp4' },
11
+ { :video_bitrate => 400, :filename => 'q2.mp4', :thumbnails => {
12
12
  :number => 2, :offset_start => '50%', :offset_end => '20%', :presets => { :norm => '-normalize' }, :exact => true
13
13
  } },
14
14
  { :video_bitrate => 500, :filename => 'q3.mp4' }
@@ -29,9 +29,9 @@ class VideoConverterTest < Test::Unit::TestCase
29
29
  context 'with aspect' do
30
30
  setup do
31
31
  (@c = VideoConverter.new(
32
- :input => 'test/fixtures/test (1).mp4',
32
+ :input => 'test/fixtures/test (1).mp4',
33
33
  :outputs => [
34
- { :video_bitrate => 300, :filename => 'res.mp4', :aspect => '4:3' },
34
+ { :video_bitrate => 300, :filename => 'res.mp4', :aspect => '4:3' },
35
35
  ]
36
36
  )).run
37
37
  end
@@ -46,9 +46,9 @@ class VideoConverterTest < Test::Unit::TestCase
46
46
  context 'with aspect and resize' do
47
47
  setup do
48
48
  (@c = VideoConverter.new(
49
- :input => 'test/fixtures/test (1).mp4',
49
+ :input => 'test/fixtures/test (1).mp4',
50
50
  :outputs => [
51
- { :video_bitrate => 300, :filename => 'res.mp4', :aspect => '4:3', :height => 240 },
51
+ { :video_bitrate => 300, :filename => 'res.mp4', :aspect => '4:3', :height => 240 },
52
52
  ]
53
53
  )).run
54
54
  end
@@ -65,11 +65,11 @@ class VideoConverterTest < Test::Unit::TestCase
65
65
  context 'with watermarks' do
66
66
  setup do
67
67
  (@c = VideoConverter.new(
68
- :input => 'test/fixtures/test (1).mp4',
68
+ :input => 'test/fixtures/test (1).mp4',
69
69
  :outputs => [
70
70
  { :video_bitrate => 300, :filename => 'res.mp4', :watermarks => {
71
71
  :url => 'test/fixtures/logo.png', :x=>'-3%', :y=>'-3%', :height=>'3%'
72
- }, :height => 240 },
72
+ }, :height => 240 },
73
73
  ]
74
74
  )).run
75
75
  end
@@ -82,18 +82,60 @@ class VideoConverterTest < Test::Unit::TestCase
82
82
  context 'with autocrop' do
83
83
  setup do
84
84
  (@c = VideoConverter.new(
85
- :input => 'test/fixtures/test_crop.mp4',
85
+ :input => 'test/fixtures/test_crop.mp4',
86
86
  :outputs => [
87
- { :video_bitrate => 300, :filename => 'res.mp4', :crop => true },
87
+ { :filename => 'audio.mp4', :ac => 1, :ar => 44100, :vn => true, :one_pass => true, :volume => '-21dB' },
88
+ { :video_bitrate => 300, :filename => 'res.mp4', :crop => true }
88
89
  ]
89
90
  )).run
90
91
  end
91
-
92
+
92
93
  should 'crop' do
93
- m = VideoConverter.new(:input => @c.outputs.first.ffmpeg_output).inputs.first.video_stream
94
+ m = VideoConverter.new(:input => @c.outputs.last.ffmpeg_output).inputs.first.video_stream
94
95
  assert_equal 406, m[:height].to_i
95
96
  assert_equal 720, m[:width].to_i
96
97
  end
98
+
99
+ should 'consist no video in output' do
100
+ m = VideoConverter.new(:input => @c.outputs.first.ffmpeg_output).inputs.first.video_stream
101
+ assert !m
102
+ end
103
+ end
104
+
105
+ context 'with sound only' do
106
+ setup do
107
+ (@c = VideoConverter.new(
108
+ :input => 'test/fixtures/test (1).mp4',
109
+ :outputs => [
110
+ { :filename => 'audio.mp4', :ac => 1, :ar => 44100, :vn => true, :one_pass => true, :volume => '-21dB' }
111
+ ]
112
+ )).run
113
+ end
114
+
115
+ should 'consist no video' do
116
+ m = VideoConverter.new(:input => @c.outputs.first.ffmpeg_output).inputs.first.video_stream
117
+ assert !m
118
+ m = VideoConverter.new(:input => @c.outputs.first.ffmpeg_output).inputs.first.audio_stream
119
+ assert m
120
+ end
121
+ end
122
+
123
+ context 'with video only' do
124
+ setup do
125
+ (@c = VideoConverter.new(
126
+ :input => 'test/fixtures/test_no_sound (1).mp4',
127
+ :outputs => [
128
+ { :video_bitrate => 300, :filename => 'res.mp4', :crop => true }
129
+ ]
130
+ )).run
131
+ end
132
+
133
+ should 'consist no video' do
134
+ m = VideoConverter.new(:input => @c.outputs.first.ffmpeg_output).inputs.first.video_stream
135
+ assert m
136
+ m = VideoConverter.new(:input => @c.outputs.first.ffmpeg_output).inputs.first.audio_stream
137
+ assert !m
138
+ end
97
139
  end
98
140
  end
99
141
 
@@ -103,15 +145,15 @@ class VideoConverterTest < Test::Unit::TestCase
103
145
  setup do
104
146
  VideoConverter.paral = true
105
147
  (@c = VideoConverter.new(
106
- "input"=>["test/fixtures/test (1).mp4"],
148
+ "input"=>["test/fixtures/test (1).mp4"],
107
149
  "output"=>[
108
- {"video_bitrate"=>676, "filename"=>"sd1.m3u8", "type"=>"segmented", "audio_bitrate"=>128, "height"=>528},
109
- {"video_bitrate"=>1172, "filename"=>"sd2.m3u8", "type"=>"segmented", "audio_bitrate"=>128, "height"=>528},
150
+ {"video_bitrate"=>676, "filename"=>"sd1.m3u8", "type"=>"segmented", "audio_bitrate"=>128, "height"=>528},
151
+ {"video_bitrate"=>1172, "filename"=>"sd2.m3u8", "type"=>"segmented", "audio_bitrate"=>128, "height"=>528},
110
152
  {"filename"=>"playlist.m3u8", "type"=>"playlist", "streams"=>[
111
153
  {"path"=>"sd1.m3u8", "bandwidth"=>804}, {"path"=>"sd2.m3u8", "bandwidth"=>1300}
112
- ]},
113
- {"video_bitrate"=>1550, "filename"=>"hd1.m3u8", "type"=>"segmented", "audio_bitrate"=>48, "height"=>720},
114
- {"video_bitrate"=>3200, "filename"=>"hd2.m3u8", "type"=>"segmented", "audio_bitrate"=>128, "height"=>720},
154
+ ]},
155
+ {"video_bitrate"=>1550, "filename"=>"hd1.m3u8", "type"=>"segmented", "audio_bitrate"=>48, "height"=>720},
156
+ {"video_bitrate"=>3200, "filename"=>"hd2.m3u8", "type"=>"segmented", "audio_bitrate"=>128, "height"=>720},
115
157
  {"filename"=>"hd_playlist.m3u8", "type"=>"playlist", "streams"=>[
116
158
  {"path"=>"hd1.m3u8", "bandwidth"=>1598}, {"path"=>"hd2.m3u8", "bandwidth"=>3328}
117
159
  ]}
@@ -122,10 +164,10 @@ class VideoConverterTest < Test::Unit::TestCase
122
164
  should 'generate hls' do
123
165
  %w(sd1 sd2 hd1 hd2).each do |quality|
124
166
  # should create chunks
125
- assert_equal ['s-00000.ts', 's-00001.ts'], Dir.entries(File.join(@c.outputs.first.work_dir, quality)).delete_if { |e| ['.', '..'].include?(e) }.sort
167
+ assert_equal ['s-00000.ts', 's-00001.ts', 's-00002.ts'], Dir.entries(File.join(@c.outputs.first.work_dir, quality)).delete_if { |e| ['.', '..'].include?(e) }.sort
126
168
  # TODO verify that chunks have different quality (weight)
127
169
  # should create playlists
128
- assert File.exists?(playlist = File.join(@c.outputs.first.work_dir, "#{quality}.m3u8"))
170
+ assert File.exists?(File.join(@c.outputs.first.work_dir, "#{quality}.m3u8"))
129
171
  # TODO verify that playlist is valid (contain all chunks and modifiers)
130
172
  end
131
173
  end
@@ -134,17 +176,22 @@ class VideoConverterTest < Test::Unit::TestCase
134
176
  context 'to HLS AES' do
135
177
  setup do
136
178
  (@c = VideoConverter.new(
137
- "input"=>["test/fixtures/test (1).mp4"],
179
+ "input"=>["test/fixtures/test (1).mp4"],
138
180
  "output"=>[
139
- {"video_bitrate"=>676, "filename"=>"sd1.m3u8", "type"=>"segmented", "audio_bitrate"=>128, "height"=>528, "drm"=>"hls", "encryption_key"=>'a'*16},
140
- {"video_bitrate"=>1172, "filename"=>"sd2.m3u8", "type"=>"segmented", "audio_bitrate"=>128, "height"=>528, "drm"=>"hls", "encryption_key"=>'a'*16},
181
+ {"video_bitrate"=>676, "filename"=>"sd1.m3u8", "type"=>"segmented", "audio_bitrate"=>128, "height"=>528, "drm"=>"hls", "encryption_key"=>'a'*16},
182
+ {"video_bitrate"=>1172, "filename"=>"sd2.m3u8", "type"=>"segmented", "audio_bitrate"=>128, "height"=>528, "drm"=>"hls", "encryption_key"=>'a'*16},
141
183
  {"filename"=>"playlist.m3u8", "type"=>"playlist", "streams"=>[
142
184
  {"path"=>"sd1.m3u8", "bandwidth"=>804}, {"path"=>"sd2.m3u8", "bandwidth"=>1300}
143
- ]},
144
- {"video_bitrate"=>1550, "filename"=>"hd1.m3u8", "type"=>"segmented", "audio_bitrate"=>48, "height"=>720, "drm"=>"hls", "encryption_key"=>'a'*16},
145
- {"video_bitrate"=>3200, "filename"=>"hd2.m3u8", "type"=>"segmented", "audio_bitrate"=>128, "height"=>720, "drm"=>"hls", "encryption_key"=>'a'*16},
185
+ ]},
186
+ {"video_bitrate"=>1550, "filename"=>"hd1.m3u8", "type"=>"segmented", "audio_bitrate"=>48, "height"=>720, "drm"=>"hls", "encryption_key"=>'a'*16},
187
+ {"video_bitrate"=>3200, "filename"=>"hd2.m3u8", "type"=>"segmented", "audio_bitrate"=>128, "height"=>720, "drm"=>"hls", "encryption_key"=>'a'*16},
146
188
  {"filename"=>"hd_playlist.m3u8", "type"=>"playlist", "streams"=>[
147
189
  {"path"=>"hd1.m3u8", "bandwidth"=>1598}, {"path"=>"hd2.m3u8", "bandwidth"=>3328}
190
+ ]},
191
+ {"video_bitrate"=>676, "filename"=>"sd3.m3u8", "type"=>"segmented", "audio_bitrate"=>128, "height"=>720, "drm"=>"hls"},
192
+ {"video_bitrate"=>800, "filename"=>"sd4.m3u8", "type"=>"segmented", "audio_bitrate"=>128, "height"=>720, "drm"=>"hls"},
193
+ {"filename"=>"sd3_playlist.m3u8", "type"=>"playlist", "streams"=>[
194
+ {"path"=>"sd3.m3u8", "bandwidth"=>804}, {"path"=>"sd4.m3u8", "bandwidth"=>3328}
148
195
  ]}
149
196
  ]
150
197
  )).run
@@ -153,28 +200,46 @@ class VideoConverterTest < Test::Unit::TestCase
153
200
  should 'generate hls' do
154
201
  %w(sd1 sd2 hd1 hd2).each do |quality|
155
202
  # should create chunks
156
- assert_equal ['s-00000.ts', 's-00001.ts'], Dir.entries(File.join(@c.outputs.first.work_dir, quality)).delete_if { |e| ['.', '..'].include?(e) }.sort
203
+ assert_equal ['s-00000.ts', 's-00001.ts', 's-00002.ts'], Dir.entries(File.join(@c.outputs.first.work_dir, quality)).delete_if { |e| ['.', '..'].include?(e) }.sort
157
204
  # should create playlists
158
205
  assert File.exists?(playlist = File.join(@c.outputs.first.work_dir, "#{quality}.m3u8"))
159
206
  assert File.read(playlist).include?('EXT-X-KEY:METHOD=AES-128,URI="video.key"')
160
207
  assert File.exists?(File.join(@c.outputs.first.work_dir, 'video.key'))
161
208
  end
162
209
  end
210
+
211
+ should 'be deencrypted successfully' do
212
+ # with encryption key
213
+ key = `hexdump -e '16/1 "%02x"' #{File.join(@c.outputs.first.work_dir, 'video.key')}`
214
+ `openssl aes-128-cbc -d -in #{File.join(@c.outputs.first.work_dir, 'sd1/s-00000.ts')} -out #{File.join(@c.outputs.first.work_dir, '1.ts')} -K #{key} -iv 00000000000000000000000000000000`
215
+ meta = VideoConverter.new(:inputs => File.join(@c.outputs.first.work_dir, '1.ts')).inputs.first.metadata
216
+ assert_equal meta[:channels], 2
217
+ assert_equal meta[:video_streams].first[:video_codec], "h264"
218
+
219
+ #w/out encryption key
220
+ key = `hexdump -e '16/1 "%02x"' #{File.join(@c.outputs.first.work_dir, 'sd3.m3u8.key')}`
221
+ `openssl aes-128-cbc -d -in #{File.join(@c.outputs.first.work_dir, 'sd3/s-00000.ts')} -out #{File.join(@c.outputs.first.work_dir, '2.ts')} -K #{key} -iv 00000000000000000000000000000000`
222
+ meta = VideoConverter.new(:inputs => File.join(@c.outputs.first.work_dir, '2.ts')).inputs.first.metadata
223
+ assert_equal meta[:channels], 2
224
+ assert_equal meta[:video_streams].first[:video_codec], "h264"
225
+ end
163
226
  end
164
227
 
165
228
  context 'to HDS' do
166
229
  setup do
167
230
  VideoConverter.paral = true
231
+ VideoConverter::Command.nice = 10
232
+ VideoConverter::Command.ionice = 6
168
233
  (@c = VideoConverter.new(
169
- "input"=>["test/fixtures/test (1).mp4"],
234
+ "input"=>["test/fixtures/test (1).mp4"],
170
235
  "output"=>[
171
- {"video_bitrate"=>676, "filename"=>"sd1.mp4", "audio_bitrate"=>128, "height"=>360},
172
- {"video_bitrate"=>1172, "filename"=>"sd2.mp4", "audio_bitrate"=>128, "height"=>528},
236
+ {"video_bitrate"=>676, "filename"=>"sd1.mp4", "audio_bitrate"=>128, "height"=>360},
237
+ {"video_bitrate"=>1172, "filename"=>"sd2.mp4", "audio_bitrate"=>128, "height"=>528},
173
238
  {"filename"=>"playlist.f4m", "type"=>"playlist", "streams"=>[
174
239
  {"path"=>"sd1.mp4", "bandwidth"=>804}, {"path"=>"sd2.mp4", "bandwidth"=>1300}
175
- ]},
176
- {"video_bitrate"=>1550, "filename"=>"hd1.mp4", "audio_bitrate"=>48, "height"=>720},
177
- {"video_bitrate"=>3200, "filename"=>"hd2.mp4", "audio_bitrate"=>128, "height"=>720},
240
+ ]},
241
+ {"video_bitrate"=>1550, "filename"=>"hd1.mp4", "audio_bitrate"=>48, "height"=>720},
242
+ {"video_bitrate"=>3200, "filename"=>"hd2.mp4", "audio_bitrate"=>128, "height"=>720},
178
243
  {"filename"=>"hd_playlist.f4m", "type"=>"playlist", "streams"=>[
179
244
  {"path"=>"hd1.mp4", "bandwidth"=>1598}, {"path"=>"hd2.mp4", "bandwidth"=>3328}
180
245
  ]}
@@ -186,12 +251,16 @@ class VideoConverterTest < Test::Unit::TestCase
186
251
  %w(sd1.mp4 sd2.mp4 playlist.f4m hd1.mp4 hd2.mp4 hd_playlist.f4m).each do |filename|
187
252
  assert File.exists?(File.join(@c.outputs.first.work_dir, "#{filename}"))
188
253
  end
189
-
190
254
  assert_equal(
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)
255
+ VideoConverter.new(:input => File.join(@c.outputs.first.work_dir, 'sd1.mp4')).inputs.first.key_frames,
256
+ VideoConverter.new(:input => File.join(@c.outputs.first.work_dir, 'sd2.mp4')).inputs.first.key_frames
193
257
  )
194
258
  end
259
+
260
+ teardown do
261
+ VideoConverter::Command.nice = nil
262
+ VideoConverter::Command.ionice = nil
263
+ end
195
264
  end
196
265
  end
197
266
 
@@ -200,10 +269,10 @@ class VideoConverterTest < Test::Unit::TestCase
200
269
  VideoConverter.paral = false
201
270
  FileUtils.cp("test/fixtures/test (1).mp4", "test/fixtures/test (2).mp4")
202
271
  (@c = VideoConverter.new(
203
- :input => ["test/fixtures/test (1).mp4", "test/fixtures/test (2).mp4"],
272
+ :input => ["test/fixtures/test (1).mp4", "test/fixtures/test (2).mp4"],
204
273
  :output => [
205
- {:filename=>"q1.m3u8", :path=>"test/fixtures/test (1).mp4", :type=>"segmented", :one_pass=>true, :video_codec=>"copy", :audio_codec=>"copy", 'bsf:v'=>"h264_mp4toannexb"},
206
- {:filename=>"q2.m3u8", :path=>"test/fixtures/test (2).mp4", :type=>"segmented", :one_pass=>true, :video_codec=>"copy", :audio_codec=>"copy", 'bsf:v'=>"h264_mp4toannexb"},
274
+ {:filename=>"q1.m3u8", :path=>"test/fixtures/test (1).mp4", :type=>"segmented", :one_pass=>true, :video_codec=>"copy", :audio_codec=>"copy", 'bsf:v'=>"h264_mp4toannexb"},
275
+ {:filename=>"q2.m3u8", :path=>"test/fixtures/test (2).mp4", :type=>"segmented", :one_pass=>true, :video_codec=>"copy", :audio_codec=>"copy", 'bsf:v'=>"h264_mp4toannexb"},
207
276
  {:filename=>"playlist.m3u8", :type=>"playlist", :streams=>[
208
277
  {:path=>"q1.m3u8", :bandwidth=>464}, {:path=>"q2.m3u8", :bandwidth=>928}
209
278
  ]}
@@ -218,7 +287,7 @@ class VideoConverterTest < Test::Unit::TestCase
218
287
  assert_equal ['s-00000.ts', 's-00001.ts'], Dir.entries(File.join(@c.outputs.first.work_dir, quality)).delete_if { |e| ['.', '..'].include?(e) }.sort
219
288
  # TODO verify that chunks have the same quality (weight)
220
289
  # should create playlists
221
- assert File.exists?(playlist = File.join(@c.outputs.first.work_dir, "#{quality}.m3u8"))
290
+ assert File.exists?(File.join(@c.outputs.first.work_dir, "#{quality}.m3u8"))
222
291
  # TODO verify that playlist is valid (contain all chunks and modifiers)
223
292
  end
224
293
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: video_converter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - novikov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-04 00:00:00.000000000 Z
11
+ date: 2015-07-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: video_screenshoter
@@ -113,6 +113,7 @@ files:
113
113
  - test/fixtures/logo.png
114
114
  - test/fixtures/test (1).mp4
115
115
  - test/fixtures/test_crop.mp4
116
+ - test/fixtures/test_no_sound (1).mp4
116
117
  - test/fixtures/test_playlist.m3u8
117
118
  - test/test_helper.rb
118
119
  - test/video_converter_test.rb
@@ -149,6 +150,7 @@ test_files:
149
150
  - test/fixtures/logo.png
150
151
  - test/fixtures/test (1).mp4
151
152
  - test/fixtures/test_crop.mp4
153
+ - test/fixtures/test_no_sound (1).mp4
152
154
  - test/fixtures/test_playlist.m3u8
153
155
  - test/test_helper.rb
154
156
  - test/video_converter_test.rb