video_converter 0.8.1 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
@@ -3,21 +3,17 @@
|
|
3
3
|
module VideoConverter
|
4
4
|
class LiveSegmenter
|
5
5
|
class << self
|
6
|
-
attr_accessor :
|
6
|
+
attr_accessor :chunks_pattern, :command
|
7
7
|
end
|
8
8
|
|
9
|
-
self.
|
10
|
-
self.
|
11
|
-
self.encoding_profile = 's'
|
12
|
-
self.select_streams = 'v'
|
13
|
-
|
14
|
-
self.command = '%{ffmpeg_bin} -i %{ffmpeg_output} -c:v copy -c:a copy -f mpegts pipe:1 2>>/dev/null | %{bin} %{keyframe_interval_in_seconds} %{chunks_dir} %{chunk_prefix} %{encoding_profile} 1>>%{log} 2>&1'
|
9
|
+
self.chunks_pattern = 's-%05d.ts'
|
10
|
+
self.command = '%{ffmpeg_bin} -i %{ffmpeg_output} -c copy -map 0 -f ssegment -segment_time %{segment_time} -segment_list %{segment_list} -segment_list_entry_prefix %{segment_list_entry_prefix} %{chunks} 1>>%{log} 2>&1'
|
15
11
|
|
16
12
|
def self.run(outputs)
|
17
13
|
success = true
|
18
14
|
threads = []
|
19
15
|
p = Proc.new do |output|
|
20
|
-
|
16
|
+
Command.new(command, prepare_params(output)).execute
|
21
17
|
end
|
22
18
|
outputs.select { |output| output.type != 'playlist' }.each do |output|
|
23
19
|
if VideoConverter.paral
|
@@ -33,37 +29,6 @@ module VideoConverter
|
|
33
29
|
|
34
30
|
private
|
35
31
|
|
36
|
-
def self.make_chunks output
|
37
|
-
Command.new(command, prepare_params(output)).execute
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.gen_quality_playlist output
|
41
|
-
res = ''
|
42
|
-
durations = []
|
43
|
-
# order desc
|
44
|
-
chunks = Dir::glob(File.join(output.chunks_dir, "#{chunk_prefix}-*[0-9].ts")).sort do |c1, c2|
|
45
|
-
File.basename(c2).match(/\d+/).to_s.to_i <=> File.basename(c1).match(/\d+/).to_s.to_i
|
46
|
-
end
|
47
|
-
# chunk duration = (pts of first frame of the next chunk - pts of first frame of current chunk) / time_base
|
48
|
-
# for the last chunks the last two pts are used
|
49
|
-
prl_pts, l_pts = `#{Ffmpeg.ffprobe_bin} -show_frames -select_streams #{select_streams} -print_format csv -loglevel fatal #{chunks.first} | tail -n2 2>&1`.split("\n").map { |l| l.split(',')[3].to_i }
|
50
|
-
# NOTE for case when chunk has one frame
|
51
|
-
l_pts ||= prl_pts
|
52
|
-
next_chunk_pts = 2 * l_pts - prl_pts
|
53
|
-
time_base = `#{Ffmpeg.ffprobe_bin} -show_streams -select_streams #{select_streams} -loglevel fatal #{chunks.first} 2>&1`.match(/\ntime_base=1\/(\d+)/)[1].to_f
|
54
|
-
chunks.each do |chunk|
|
55
|
-
pts = `#{Ffmpeg.ffprobe_bin} -show_frames -select_streams #{select_streams} -print_format csv -loglevel fatal #{chunk} | head -n1 2>&1`.split(',')[3].to_i
|
56
|
-
durations << (duration = (next_chunk_pts - pts) / time_base)
|
57
|
-
next_chunk_pts = pts
|
58
|
-
res = File.join(File.basename(output.chunks_dir), File.basename(chunk)) + "\n" + res
|
59
|
-
res = "#EXTINF:%0.2f,\n" % duration + res
|
60
|
-
end
|
61
|
-
encryption_info = OpenSSL.get_encryption_key_path(output)
|
62
|
-
encryption_info = "#EXT-X-KEY:METHOD=AES-128,URI=\"#{encryption_info}\"\n" if encryption_info
|
63
|
-
res = "#EXTM3U\n#{encryption_info}#EXT-X-VERSION:3\n#EXT-X-TARGETDURATION:#{durations.max.to_f.ceil}\n#EXT-X-MEDIA-SEQUENCE:0\n" + res + "#EXT-X-ENDLIST"
|
64
|
-
!!File.open(File.join(output.work_dir, output.filename), 'w') { |f| f.write res }
|
65
|
-
end
|
66
|
-
|
67
32
|
def self.gen_group_playlist playlist
|
68
33
|
res = "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-PLAYLIST-TYPE:VOD\n"
|
69
34
|
playlist.streams.sort { |s1, s2| s1[:bandwidth].to_i <=> s2[:bandwidth].to_i }.each do |stream|
|
@@ -79,11 +44,10 @@ module VideoConverter
|
|
79
44
|
{
|
80
45
|
:ffmpeg_bin => Ffmpeg.bin,
|
81
46
|
:ffmpeg_output => output.ffmpeg_output,
|
82
|
-
:
|
83
|
-
:
|
84
|
-
:
|
85
|
-
:
|
86
|
-
:encoding_profile => encoding_profile,
|
47
|
+
:segment_time => Output.keyframe_interval_in_seconds,
|
48
|
+
:segment_list => File.join(output.work_dir, output.filename),
|
49
|
+
:segment_list_entry_prefix => File.basename(output.chunks_dir) + '/',
|
50
|
+
:chunks => File.join(output.chunks_dir, chunks_pattern),
|
87
51
|
:log => output.log
|
88
52
|
}
|
89
53
|
end
|
@@ -11,8 +11,10 @@ module VideoConverter
|
|
11
11
|
|
12
12
|
def self.run(output)
|
13
13
|
success = true
|
14
|
+
playlist = File.join(output.work_dir, output.filename)
|
15
|
+
File.write(playlist, File.read(playlist).sub("#EXTM3U\n", "#EXTM3U\n#EXT-X-KEY:METHOD=AES-128,URI=\"#{OpenSSL.get_encryption_key_path(output)}\"\n"))
|
14
16
|
encryption_dir = FileUtils.mkdir_p("#{output.chunks_dir}_encrypted").first
|
15
|
-
chunks = Dir::glob(File.join(output.chunks_dir, "
|
17
|
+
chunks = Dir::glob(File.join(output.chunks_dir, "*.ts")).sort do |c1, c2|
|
16
18
|
File.basename(c1).match(/\d+/).to_s.to_i <=> File.basename(c2).match(/\d+/).to_s.to_i
|
17
19
|
end
|
18
20
|
chunks.each_with_index do |chunk, index|
|
@@ -88,7 +88,7 @@ class VideoConverterTest < Test::Unit::TestCase
|
|
88
88
|
should 'generate hls' do
|
89
89
|
%w(sd1 sd2 hd1 hd2).each do |quality|
|
90
90
|
# should create chunks
|
91
|
-
assert_equal ['s-
|
91
|
+
assert_equal ['s-00000.ts', 's-00001.ts'], Dir.entries(File.join(@c.outputs.first.work_dir, quality)).delete_if { |e| ['.', '..'].include?(e) }.sort
|
92
92
|
# TODO verify that chunks have different quality (weight)
|
93
93
|
# should create playlists
|
94
94
|
assert File.exists?(playlist = File.join(@c.outputs.first.work_dir, "#{quality}.m3u8"))
|
@@ -97,6 +97,37 @@ class VideoConverterTest < Test::Unit::TestCase
|
|
97
97
|
end
|
98
98
|
end
|
99
99
|
|
100
|
+
context 'to HLS AES' do
|
101
|
+
setup do
|
102
|
+
(@c = VideoConverter.new(
|
103
|
+
"input"=>["test/fixtures/test (1).mp4"],
|
104
|
+
"output"=>[
|
105
|
+
{"video_bitrate"=>676, "filename"=>"sd1.m3u8", "type"=>"segmented", "audio_bitrate"=>128, "height"=>528, "drm"=>"hls", "encryption_key"=>'a'*16},
|
106
|
+
{"video_bitrate"=>1172, "filename"=>"sd2.m3u8", "type"=>"segmented", "audio_bitrate"=>128, "height"=>528, "drm"=>"hls", "encryption_key"=>'a'*16},
|
107
|
+
{"filename"=>"playlist.m3u8", "type"=>"playlist", "streams"=>[
|
108
|
+
{"path"=>"sd1.m3u8", "bandwidth"=>804}, {"path"=>"sd2.m3u8", "bandwidth"=>1300}
|
109
|
+
]},
|
110
|
+
{"video_bitrate"=>1550, "filename"=>"hd1.m3u8", "type"=>"segmented", "audio_bitrate"=>48, "height"=>720, "drm"=>"hls", "encryption_key"=>'a'*16},
|
111
|
+
{"video_bitrate"=>3200, "filename"=>"hd2.m3u8", "type"=>"segmented", "audio_bitrate"=>128, "height"=>720, "drm"=>"hls", "encryption_key"=>'a'*16},
|
112
|
+
{"filename"=>"hd_playlist.m3u8", "type"=>"playlist", "streams"=>[
|
113
|
+
{"path"=>"hd1.m3u8", "bandwidth"=>1598}, {"path"=>"hd2.m3u8", "bandwidth"=>3328}
|
114
|
+
]}
|
115
|
+
]
|
116
|
+
)).run
|
117
|
+
end
|
118
|
+
|
119
|
+
should 'generate hls' do
|
120
|
+
%w(sd1 sd2 hd1 hd2).each do |quality|
|
121
|
+
# should create chunks
|
122
|
+
assert_equal ['s-00000.ts', 's-00001.ts'], Dir.entries(File.join(@c.outputs.first.work_dir, quality)).delete_if { |e| ['.', '..'].include?(e) }.sort
|
123
|
+
# should create playlists
|
124
|
+
assert File.exists?(playlist = File.join(@c.outputs.first.work_dir, "#{quality}.m3u8"))
|
125
|
+
assert File.read(playlist).include?('EXT-X-KEY:METHOD=AES-128,URI="video.key"')
|
126
|
+
assert File.exists?(File.join(@c.outputs.first.work_dir, 'video.key'))
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
100
131
|
context 'to HDS' do
|
101
132
|
setup do
|
102
133
|
VideoConverter.paral = true
|
@@ -150,7 +181,7 @@ class VideoConverterTest < Test::Unit::TestCase
|
|
150
181
|
should 'generate hls' do
|
151
182
|
%w(q1 q2).each do |quality|
|
152
183
|
# should create chunks
|
153
|
-
assert_equal ['s-
|
184
|
+
assert_equal ['s-00000.ts', 's-00001.ts'], Dir.entries(File.join(@c.outputs.first.work_dir, quality)).delete_if { |e| ['.', '..'].include?(e) }.sort
|
154
185
|
# TODO verify that chunks have the same quality (weight)
|
155
186
|
# should create playlists
|
156
187
|
assert File.exists?(playlist = File.join(@c.outputs.first.work_dir, "#{quality}.m3u8"))
|
@@ -189,22 +220,4 @@ class VideoConverterTest < Test::Unit::TestCase
|
|
189
220
|
)
|
190
221
|
end
|
191
222
|
end
|
192
|
-
|
193
|
-
# context 'muxing' do
|
194
|
-
# setup do
|
195
|
-
# input = "test/fixtures/test (1).mp4"
|
196
|
-
# (c1 = VideoConverter.new(:uid => 'test', :input => input, :output => { :map => '0:0', :filename => 'video.mp4' })).convert
|
197
|
-
# (c2 = VideoConverter.new(:uid => 'test', :input => input, :output => { :map => '0:1', :filename => 'audio.wav' })).convert
|
198
|
-
# (@c = VideoConverter.new(:uid => 'test', :input => [c1.outputs.first.ffmpeg_output, c2.outputs.first.ffmpeg_output], :output => { :filename => 'mux.mp4' })).mux
|
199
|
-
# @metadata = VideoConverter.new(:input => @c.outputs.first.ffmpeg_output).inputs.first.metadata
|
200
|
-
# end
|
201
|
-
# should 'mux streams' do
|
202
|
-
# assert File.exists?(@c.outputs.first.ffmpeg_output)
|
203
|
-
# assert_equal '0:0', @metadata[:video_stream]
|
204
|
-
# assert_equal '0:1', @metadata[:audio_stream]
|
205
|
-
# end
|
206
|
-
# teardown do
|
207
|
-
# #FileUtils.rm_r @c.outputs.first.work_dir
|
208
|
-
# end
|
209
|
-
# end
|
210
223
|
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.
|
4
|
+
version: 0.8.2
|
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-
|
12
|
+
date: 2014-11-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: video_screenshoter
|
@@ -142,7 +142,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
142
142
|
version: '0'
|
143
143
|
segments:
|
144
144
|
- 0
|
145
|
-
hash:
|
145
|
+
hash: 25263521592852440
|
146
146
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
147
147
|
none: false
|
148
148
|
requirements:
|
@@ -151,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
151
151
|
version: '0'
|
152
152
|
segments:
|
153
153
|
- 0
|
154
|
-
hash:
|
154
|
+
hash: 25263521592852440
|
155
155
|
requirements:
|
156
156
|
- ffmpeg, version 1.2 or greated configured with libx264 and libfaac
|
157
157
|
- live_segmenter to convert to hls
|