video_converter 0.2.1 → 0.2.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.
@@ -5,6 +5,7 @@ require "video_converter/process"
5
5
  require "video_converter/ffmpeg"
6
6
  require "video_converter/live_segmenter"
7
7
  require "video_converter/input"
8
+ require "video_converter/input_array"
8
9
  require "video_converter/output"
9
10
  require "video_converter/output_array"
10
11
  require "fileutils"
@@ -18,7 +19,7 @@ module VideoConverter
18
19
  self.paral = true
19
20
 
20
21
  def self.new params
21
- VideoConverter::Base.new params
22
+ VideoConverter::Base.new params.deep_symbolize_keys
22
23
  end
23
24
 
24
25
  def self.find uid
@@ -2,16 +2,13 @@
2
2
 
3
3
  module VideoConverter
4
4
  class Base
5
- attr_accessor :input, :output_array, :log, :uid
5
+ attr_accessor :input_array, :output_array, :log, :uid
6
6
 
7
7
  def initialize params
8
- params.deep_symbolize_keys!
9
- raise ArgumentError.new('input is needed') if params[:input].nil? || params[:input].empty?
10
- self.input = Input.new(params[:input])
11
- raise ArgumentError.new('input does not exist') unless input.exists?
12
8
  self.uid = params[:uid] || (Socket.gethostname + object_id.to_s)
13
- raise ArgumentError.new('output is needed') if params[:output].nil? || params[:output].empty?
14
- self.output_array = OutputArray.new(params[:output], uid)
9
+ self.output_array = OutputArray.new(params[:output] || {}, uid)
10
+ self.input_array = InputArray.new(params[:input], output_array)
11
+ input_array.inputs.each { |input| raise ArgumentError.new("#{input} does not exist") unless input.exists? }
15
12
  if params[:log].nil?
16
13
  self.log = '/dev/null'
17
14
  else
@@ -45,7 +42,7 @@ module VideoConverter
45
42
 
46
43
  def convert
47
44
  params = {}
48
- [:input, :output_array, :log].each do |param|
45
+ [:input_array, :output_array, :log].each do |param|
49
46
  params[param] = self.send(param)
50
47
  end
51
48
  Ffmpeg.new(params).run
@@ -11,16 +11,16 @@ module VideoConverter
11
11
  self.paral = true
12
12
  self.log = '/dev/null'
13
13
 
14
- self.one_pass_command = "%{bin} -i %{input} -y -acodec copy -vcodec libx264 -g 100 -keyint_min 50 -b:v %{video_bitrate}k -bt %{video_bitrate}k -f mp4 %{local_path} 1>%{log} 2>&1 || exit 1"
14
+ self.one_pass_command = "%{bin} -i %{input} -y -acodec copy -vcodec %{video_codec} -g 100 -keyint_min 50 -b:v %{video_bitrate}k -bt %{video_bitrate}k -f mp4 %{local_path} 1>%{log} 2>&1 || exit 1"
15
15
 
16
- self.first_pass_command = "%{bin} -i %{input} -y -an -vcodec libx264 -g %{keyframe_interval} -keyint_min 25 -pass 1 -passlogfile %{input}.log -b:v 700k -threads %{threads} -f mp4 /dev/null 1>>%{log} 2>&1 || exit 1"
16
+ self.first_pass_command = "%{bin} -i %{input} -y -an -vcodec %{video_codec} -g %{keyframe_interval} -keyint_min 25 -pass 1 -passlogfile %{passlogfile} -b:v 700k -threads %{threads} -f mp4 /dev/null 1>>%{log} 2>&1 || exit 1"
17
17
 
18
- self.second_pass_command = "%{bin} -i %{input} -y -pass 2 -passlogfile %{input}.log -c:a libfaac -b:a %{audio_bitrate}k -c:v libx264 -g %{keyframe_interval} -keyint_min 25 %{frame_rate} -b:v %{video_bitrate}k %{size} -threads %{threads} -f mp4 %{local_path} 1>%{log} 2>&1 || exit 1"
18
+ self.second_pass_command = "%{bin} -i %{input} -y -pass 2 -passlogfile %{passlogfile} -c:a %{audio_codec} -b:a %{audio_bitrate}k -c:v %{video_codec} -g %{keyframe_interval} -keyint_min 25 %{frame_rate} -b:v %{video_bitrate}k %{size} -threads %{threads} -f mp4 %{local_path} 1>%{log} 2>&1 || exit 1"
19
19
 
20
- attr_accessor :input, :output_array, :one_pass, :paral, :log
20
+ attr_accessor :input_array, :output_array, :one_pass, :paral, :log
21
21
 
22
22
  def initialize params
23
- [:input, :output_array].each do |param|
23
+ [:input_array, :output_array].each do |param|
24
24
  self.send("#{param}=", params[param]) or raise ArgumentError.new("#{param} is needed")
25
25
  end
26
26
  [:one_pass, :paral, :log].each do |param|
@@ -30,33 +30,37 @@ module VideoConverter
30
30
 
31
31
  def run
32
32
  res = true
33
- threads = []
34
- output_array.groups.each do |group|
35
- unless one_pass
36
- first_pass_command = Command.new self.class.first_pass_command, prepare_params(common_params.merge(group.first.to_hash).merge((group.first.playlist.to_hash rescue {})))
37
- res &&= first_pass_command.execute
38
- end
39
- group.each do |quality|
40
- if one_pass
41
- quality_command = Command.new self.class.one_pass_command, prepare_params(common_params.merge(quality.to_hash))
42
- else
43
- quality_command = Command.new self.class.second_pass_command, prepare_params(common_params.merge(quality.to_hash))
33
+ input_array.inputs.each do |input|
34
+ threads = []
35
+ input.output_groups.each_with_index do |group, group_number|
36
+ passlogfile = File.join(File.dirname(group.first.local_path), "#{group_number}.log")
37
+ one_pass = self.one_pass || group.first.video_codec == 'copy'
38
+ unless one_pass
39
+ first_pass_command = Command.new self.class.first_pass_command, prepare_params(common_params.merge(group.first.to_hash).merge((group.first.playlist.to_hash rescue {})).merge(:passlogfile => passlogfile, :input => input))
40
+ res &&= first_pass_command.execute
44
41
  end
45
- if paral
46
- threads << Thread.new { res &&= quality_command.execute }
47
- else
48
- res &&= quality_command.execute
42
+ group.each do |quality|
43
+ if one_pass
44
+ quality_command = Command.new self.class.one_pass_command, prepare_params(common_params.merge(quality.to_hash).merge(:passlogfile => passlogfile, :input => input))
45
+ else
46
+ quality_command = Command.new self.class.second_pass_command, prepare_params(common_params.merge(quality.to_hash).merge(:passlogfile => passlogfile, :input => input))
47
+ end
48
+ if paral
49
+ threads << Thread.new { res &&= quality_command.execute }
50
+ else
51
+ res &&= quality_command.execute
52
+ end
49
53
  end
50
54
  end
55
+ threads.each { |t| t.join } if paral
51
56
  end
52
- threads.each { |t| t.join } if paral
53
57
  res
54
58
  end
55
59
 
56
60
  private
57
61
 
58
62
  def common_params
59
- { :bin => self.class.bin, :input => input, :log => log }
63
+ { :bin => self.class.bin, :log => log }
60
64
  end
61
65
 
62
66
  def prepare_params params
@@ -8,10 +8,13 @@ module VideoConverter
8
8
 
9
9
  self.metadata_command = "%{bin} -i %{input} 2>&1"
10
10
 
11
- attr_accessor :input
11
+ attr_accessor :input, :outputs, :output_groups
12
12
 
13
13
  def initialize input
14
+ raise ArgumentError.new('input is needed') if input.nil? || input.empty?
14
15
  self.input = input
16
+ self.outputs = []
17
+ self.output_groups = []
15
18
  end
16
19
 
17
20
  def to_s
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ module VideoConverter
4
+ class InputArray
5
+ attr_accessor :inputs
6
+
7
+ def initialize inputs, output_array
8
+ self.inputs = (inputs.is_a?(Array) ? inputs : [inputs]).map { |input| Input.new(input) }
9
+ output_array.outputs.each do |output|
10
+ if [:standard, :segmented].include? output.type
11
+ self.inputs[self.inputs.index { |input| input.to_s == output.path }.to_i].outputs << output
12
+ end
13
+ end
14
+ self.inputs.each do |input|
15
+ output_array.playlists.each do |playlist|
16
+ unless (groups = input.outputs.select { |output| output.playlist == playlist }).empty?
17
+ input.output_groups << groups
18
+ end
19
+ end
20
+ input.outputs.select { |output| output.playlist.nil? }.each { |output| input.output_groups << [output] }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -63,7 +63,7 @@ module VideoConverter
63
63
  end
64
64
 
65
65
  def gen_group_playlist playlist
66
- res = "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-PLAYLIST-TYPE:VOD"
66
+ res = "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-PLAYLIST-TYPE:VOD\n"
67
67
  playlist.streams.sort { |s1, s2| s1[:bandwidth].to_i <=> s2[:bandwidth].to_i }.each do |stream|
68
68
  res += "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=#{stream[:bandwidth].to_i * 1000}\n"
69
69
  res += File.join('.', stream[:path]) + "\n"
@@ -3,7 +3,7 @@
3
3
  module VideoConverter
4
4
  class Output
5
5
  class << self
6
- attr_accessor :base_url, :work_dir, :video_bitrate, :audio_bitrate, :segment_seconds, :keyframe_interval, :threads
6
+ attr_accessor :base_url, :work_dir, :video_bitrate, :audio_bitrate, :segment_seconds, :keyframe_interval, :threads, :video_codec, :audio_codec
7
7
  end
8
8
 
9
9
  self.base_url = '/tmp'
@@ -13,11 +13,13 @@ module VideoConverter
13
13
  self.segment_seconds = 10
14
14
  self.keyframe_interval = 250
15
15
  self.threads = 1
16
+ self.video_codec = 'libx264'
17
+ self.audio_codec = 'libfaac'
16
18
 
17
- attr_accessor :type, :url, :base_url, :filename, :format, :video_bitrate, :uid, :streams, :work_dir, :local_path, :playlist, :items, :segment_seconds, :chunks_dir, :audio_bitrate, :keyframe_interval, :threads
19
+ attr_accessor :type, :url, :base_url, :filename, :format, :video_bitrate, :uid, :streams, :work_dir, :local_path, :playlist, :items, :segment_seconds, :chunks_dir, :audio_bitrate, :keyframe_interval, :threads, :video_codec, :audio_codec, :path
18
20
 
19
21
  def initialize params = {}
20
- self.uid = params[:uid]
22
+ self.uid = params[:uid].to_s
21
23
 
22
24
  # General output options
23
25
  self.type = params[:type] ? params[:type].to_sym : :standard
@@ -36,7 +38,7 @@ module VideoConverter
36
38
  self.base_url = (params[:url] ? File.dirname(params[:url]) : params[:base_url]) || self.class.base_url
37
39
  self.filename = (params[:url] ? File.basename(params[:url]) : params[:filename]) || self.uid + '.' + self.format
38
40
  self.url = params[:url] ? params[:url] : File.join(base_url, filename)
39
- self.work_dir = File.join(params[:work_dir] || self.class.work_dir, uid.to_s)
41
+ self.work_dir = File.join(params[:work_dir] || self.class.work_dir, uid)
40
42
  format_regexp = Regexp.new("#{File.extname(filename)}$")
41
43
  self.local_path = File.join(work_dir, filename.sub(format_regexp, ".#{format}"))
42
44
  FileUtils.mkdir_p File.dirname(local_path)
@@ -45,6 +47,7 @@ module VideoConverter
45
47
  FileUtils.mkdir_p chunks_dir
46
48
  end
47
49
  self.threads = self.class.threads
50
+ self.path = params[:path]
48
51
 
49
52
  # Rate controle
50
53
  self.video_bitrate = params[:video_bitrate].to_i > 0 ? params[:video_bitrate].to_i : self.class.video_bitrate
@@ -56,10 +59,14 @@ module VideoConverter
56
59
 
57
60
  # Frame rate
58
61
  self.keyframe_interval = params[:keyframe_interval].to_i > 0 ? params[:keyframe_interval].to_i : self.class.keyframe_interval
62
+
63
+ # Format and codecs
64
+ self.video_codec = (params[:copy_video] ? 'copy' : params[:video_codec]) || self.class.video_codec
65
+ self.audio_codec = (params[:copy_audio] ? 'copy' : params[:audio_codec]) || self.class.audio_codec
59
66
  end
60
67
 
61
68
  def to_hash
62
- keys = [:video_bitrate, :local_path, :segment_seconds, :chunks_dir, :audio_bitrate, :keyframe_interval, :threads]
69
+ keys = [:video_bitrate, :local_path, :segment_seconds, :chunks_dir, :audio_bitrate, :keyframe_interval, :threads, :video_codec, :audio_codec]
63
70
  Hash[*keys.map{ |key| [key, self.send(key)] }.flatten]
64
71
  end
65
72
 
@@ -17,14 +17,5 @@ module VideoConverter
17
17
  def playlists
18
18
  outputs.select { |output| output.type == :playlist }
19
19
  end
20
-
21
- def groups
22
- groups = []
23
- playlists.each { |playlist| groups << playlist.items }
24
- outputs.select { |output| output.playlist.nil? && [:standard, :segmented].include?(output.type) }.each do |output|
25
- groups << [output]
26
- end
27
- groups
28
- end
29
20
  end
30
21
  end
@@ -1,3 +1,3 @@
1
1
  module VideoConverter
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.2"
3
3
  end
@@ -3,12 +3,12 @@ require 'test_helper'
3
3
  class VideoConverterTest < Test::Unit::TestCase
4
4
  context 'run' do
5
5
  setup do
6
- @input = 'test/fixtures/test.mp4'
6
+ @input_file = 'test/fixtures/test.mp4'
7
+ @input_url = 'http://techslides.com/demos/sample-videos/small.mp4'
7
8
  end
8
-
9
9
  context 'with default type' do
10
10
  setup do
11
- @c = VideoConverter.new('input' => @input, 'output' => [{'video_bitrate' => 300, 'filename' => 'tmp/test1.mp4'}, {'video_bitrate' => 700, :filename => 'tmp/test2.mp4'}], 'log' => 'tmp/test.log')
11
+ @c = VideoConverter.new('input' => @input_file, 'output' => [{'video_bitrate' => 300, 'filename' => 'tmp/test1.mp4'}, {'video_bitrate' => 700, :filename => 'tmp/test2.mp4'}], 'log' => 'tmp/test.log')
12
12
  @res = @c.run
13
13
  end
14
14
  should 'convert files' do
@@ -31,7 +31,7 @@ class VideoConverterTest < Test::Unit::TestCase
31
31
 
32
32
  context 'with type segmented' do
33
33
  setup do
34
- @c = VideoConverter.new(:input => @input, :output => [{:type => :segmented, :video_bitrate => 500, :audio_bitrate => 128, :filename => 'tmp/sd/r500.m3u8'}, {:type => :segmented, :video_bitrate => 700, :audio_bitrate => 128, :filename => 'tmp/sd/r700.m3u8'}, {:type => :segmented, :video_bitrate => 200, :audio_bitrate => 64, :filename => 'tmp/ld/r200.m3u8'}, {:type => :segmented, :video_bitrate => 300, :audio_bitrate => 60, :filename => 'tmp/ld/r300.m3u8'}, {:type => :playlist, :streams => [{'path' => 'tmp/sd/r500.m3u8', 'bandwidth' => 650}, {'path' => 'tmp/sd/r700.m3u8', 'bandwidth' => 850}], :filename => 'tmp/playlist_sd.m3u8'}, {:type => :playlist, :streams => [{'path' => 'tmp/ld/r200.m3u8', 'bandwidth' => 300}, {'path' => 'tmp/ld/r300.m3u8', 'bandwidth' => 400}], :filename => 'tmp/playlist_ld.m3u8'}])
34
+ @c = VideoConverter.new(:input => @input_file, :output => [{:type => :segmented, :video_bitrate => 500, :audio_bitrate => 128, :filename => 'tmp/sd/r500.m3u8'}, {:type => :segmented, :video_bitrate => 700, :audio_bitrate => 128, :filename => 'tmp/sd/r700.m3u8'}, {:type => :segmented, :video_bitrate => 200, :audio_bitrate => 64, :filename => 'tmp/ld/r200.m3u8'}, {:type => :segmented, :video_bitrate => 300, :audio_bitrate => 60, :filename => 'tmp/ld/r300.m3u8'}, {:type => :playlist, :streams => [{'path' => 'tmp/sd/r500.m3u8', 'bandwidth' => 650}, {'path' => 'tmp/sd/r700.m3u8', 'bandwidth' => 850}], :filename => 'tmp/playlist_sd.m3u8'}, {:type => :playlist, :streams => [{'path' => 'tmp/ld/r200.m3u8', 'bandwidth' => 300}, {'path' => 'tmp/ld/r300.m3u8', 'bandwidth' => 400}], :filename => 'tmp/playlist_ld.m3u8'}])
35
35
  @res = @c.run
36
36
  @work_dir = File.join(VideoConverter::Output.work_dir, @c.uid)
37
37
  end
@@ -63,5 +63,54 @@ class VideoConverterTest < Test::Unit::TestCase
63
63
  assert File.read(playlist).include?('r300.m3u8')
64
64
  end
65
65
  end
66
+
67
+ context 'only segment some files' do
68
+ setup do
69
+ # prepare files
70
+ @c1 = VideoConverter.new(:input => @input_file, :output => [{:video_bitrate => 300, :filename => 'sd/1.mp4'}, {:video_bitrate => 400, :filename => 'sd/2.mp4'}, {:video_bitrate => 500, :filename => 'hd/1.mp4'}, {:video_bitrate => 600, :filename => 'hd/2.mp4'}])
71
+ @c1.run
72
+ @input1, @input2, @input3, @input4 = @c1.output_array.outputs.map { |output| output.local_path }
73
+ # test segmentation
74
+ @c2 = VideoConverter.new(:input => [@input1, @input2, @input3, @input4], :output => [
75
+ {:path => @input1, :type => :segmented, :filename => '1.m3u8', :copy_video => true},
76
+ {:path => @input2, :type => :segmented, :filename => '2.m3u8', :copy_video => true},
77
+ {:path => @input3, :type => :segmented, :filename => '3.m3u8', :copy_video => true},
78
+ {:path => @input4, :type => :segmented, :filename => '4.m3u8', :copy_video => true},
79
+ {:type => :playlist, :streams => [{:path => '1.m3u8'}, {:path => '2.m3u8'}], :filename => 'playlist1.m3u8'},
80
+ {:type => :playlist, :streams => [{:path => '3.m3u8'}, {:path => '4.m3u8'}], :filename => 'playlist2.m3u8'}
81
+ ])
82
+ @res = @c2.run
83
+ @work_dir = File.join(VideoConverter::Output.work_dir, @c2.uid)
84
+ end
85
+ should 'create chunks' do
86
+ 4.times do |n|
87
+ assert Dir.entries(File.join(@work_dir, "#{n + 1}")).count > 0
88
+ end
89
+ end
90
+ should 'create quality playlists' do
91
+ 4.times do |n|
92
+ playlist = "#{n + 1}.m3u8"
93
+ assert File.exists?(File.join(@work_dir, playlist))
94
+ assert File.read(File.join(@work_dir, playlist)).include?('s-00001')
95
+ end
96
+ end
97
+ should 'create group playlist' do
98
+ playlist = File.join(@work_dir, 'playlist1.m3u8')
99
+ assert File.exists?(playlist)
100
+ assert File.read(playlist).include?('1.m3u8')
101
+ assert File.read(playlist).include?('2.m3u8')
102
+
103
+ playlist = File.join(@work_dir, 'playlist2.m3u8')
104
+ assert File.exists?(playlist)
105
+ assert File.read(playlist).include?('3.m3u8')
106
+ assert File.read(playlist).include?('4.m3u8')
107
+ end
108
+ should 'not convert inputs' do
109
+ assert_equal VideoConverter::Input.new(@input1).metadata[:video_bitrate_in_kbps], VideoConverter::Input.new(File.join(@work_dir, '1.ts')).metadata[:video_bitrate_in_kbps]
110
+ assert_equal VideoConverter::Input.new(@input2).metadata[:video_bitrate_in_kbps], VideoConverter::Input.new(File.join(@work_dir, '2.ts')).metadata[:video_bitrate_in_kbps]
111
+ assert_equal VideoConverter::Input.new(@input3).metadata[:video_bitrate_in_kbps], VideoConverter::Input.new(File.join(@work_dir, '3.ts')).metadata[:video_bitrate_in_kbps]
112
+ assert_equal VideoConverter::Input.new(@input4).metadata[:video_bitrate_in_kbps], VideoConverter::Input.new(File.join(@work_dir, '4.ts')).metadata[:video_bitrate_in_kbps]
113
+ end
114
+ end
66
115
  end
67
116
  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.2.1
4
+ version: 0.2.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: 2013-04-11 00:00:00.000000000 Z
12
+ date: 2013-04-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -94,6 +94,7 @@ files:
94
94
  - lib/video_converter/command.rb
95
95
  - lib/video_converter/ffmpeg.rb
96
96
  - lib/video_converter/input.rb
97
+ - lib/video_converter/input_array.rb
97
98
  - lib/video_converter/live_segmenter.rb
98
99
  - lib/video_converter/output.rb
99
100
  - lib/video_converter/output_array.rb
@@ -119,7 +120,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
119
120
  version: '0'
120
121
  segments:
121
122
  - 0
122
- hash: 690094087006965726
123
+ hash: 3078997600954711389
123
124
  required_rubygems_version: !ruby/object:Gem::Requirement
124
125
  none: false
125
126
  requirements:
@@ -128,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
128
129
  version: '0'
129
130
  segments:
130
131
  - 0
131
- hash: 690094087006965726
132
+ hash: 3078997600954711389
132
133
  requirements:
133
134
  - ffmpeg, version 1.2 or greated configured with libx264 and libfaac
134
135
  - live_segmenter to convert to hls