video_converter 0.9.1 → 0.10.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.
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