video_converter 0.8.1 → 0.8.2

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