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 :bin, :chunk_prefix, :encoding_profile, :select_streams, :command
6
+ attr_accessor :chunks_pattern, :command
7
7
  end
8
8
 
9
- self.bin = '/usr/local/bin/live_segmenter'
10
- self.chunk_prefix = 's'
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
- make_chunks(output) && gen_quality_playlist(output)
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
- :bin => bin,
83
- :keyframe_interval_in_seconds => Output.keyframe_interval_in_seconds,
84
- :chunks_dir => output.chunks_dir,
85
- :chunk_prefix => chunk_prefix,
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, "#{LiveSegmenter.chunk_prefix}-*[0-9].ts")).sort do |c1, c2|
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|
@@ -1,3 +1,3 @@
1
1
  module VideoConverter
2
- VERSION = "0.8.1"
2
+ VERSION = "0.8.2"
3
3
  end
@@ -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-00001.ts', 's-00002.ts', 's-00003.ts'], Dir.entries(File.join(@c.outputs.first.work_dir, quality)).delete_if { |e| ['.', '..'].include?(e) }.sort
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-00001.ts', 's-00002.ts', 's-00003.ts'], Dir.entries(File.join(@c.outputs.first.work_dir, quality)).delete_if { |e| ['.', '..'].include?(e) }.sort
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.1
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-07 00:00:00.000000000 Z
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: -2686683982963894083
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: -2686683982963894083
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