video_converter 0.1.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.
data/.Rakefile.swp ADDED
Binary file
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.swp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in video_converter.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 novikov
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,4 @@
1
+ video_converter
2
+ ===============
3
+
4
+ ffmpeg, mencoder video converter to mp4, m3u8
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ t.test_files = FileList['test/*_test.rb']
7
+ end
8
+
9
+ desc "Run tests"
10
+ task :default => :test
@@ -0,0 +1,70 @@
1
+ # encoding: utf-8
2
+
3
+ module VideoConverter
4
+ class Base
5
+ attr_accessor :input, :profile, :one_pass, :type, :paral, :log, :id, :playlist_dir, :chunk_base, :no_convert
6
+
7
+ def initialize params
8
+ self.no_convert = params[:no_convert].nil? ? VideoConverter.no_convert : params[:no_convert]
9
+ unless no_convert
10
+ self.input = params[:input] or raise ArgumentError.new('Input is needed')
11
+ raise ArgumentError.new('Input does not exist') unless File.exists?(input)
12
+ end
13
+ self.profile = params[:profile] or raise ArgumentError.new('Profile is needed')
14
+ self.type = params[:type].nil? ? VideoConverter.type : params[:type].to_sym
15
+ raise ArgumentError.new("Incorrect type #{type}") unless [:hls, :mp4].include?(type)
16
+ if type == :hls
17
+ self.playlist_dir = params[:playlist_dir] or raise ArgumentError.new("Playlist dir is needed")
18
+ end
19
+ self.one_pass = params[:one_pass].nil? ? Ffmpeg.one_pass : params[:one_pass]
20
+ self.paral = params[:paral].nil? ? VideoConverter.paral : params[:paral]
21
+ if params[:log].nil?
22
+ self.log = '/dev/null'
23
+ else
24
+ self.log = params[:log]
25
+ FileUtils.mkdir_p File.dirname(log)
26
+ end
27
+ self.id = object_id
28
+ self.chunk_base = params[:chunk_base]
29
+ end
30
+
31
+ def run
32
+ process = VideoConverter::Process.new(id)
33
+ process.pid = `cat /proc/self/stat`.split[3]
34
+ actions = []
35
+ actions << :convert unless no_convert
36
+ actions << :live_segment if type == :hls
37
+ actions.each do |action|
38
+ process.status = action.to_s
39
+ process.progress = 0
40
+ res = send action
41
+ if res
42
+ process.status = "#{action}_success"
43
+ else
44
+ process.status = "#{action}_error"
45
+ return false
46
+ end
47
+ end
48
+ true
49
+ end
50
+
51
+ private
52
+
53
+ def convert
54
+ params = {}
55
+ [:input, :profile, :one_pass, :paral, :log].each do |param|
56
+ params[param] = self.send(param)
57
+ end
58
+ Ffmpeg.new(params).run
59
+ end
60
+
61
+ def live_segment
62
+ params = {}
63
+ [:profile, :playlist_dir, :paral, :chunk_base, :log, :no_convert].each do |param|
64
+ params[param] = self.send(param)
65
+ end
66
+ params[:delete_input] = false if no_convert
67
+ LiveSegmenter.new(params).run
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ module VideoConverter
4
+ class Command
5
+ class << self
6
+ attr_accessor :debug, :verbose
7
+ end
8
+ self.debug = false
9
+ self.verbose = false
10
+
11
+ attr_accessor :command
12
+
13
+ def initialize command, params = {}
14
+ res = command.clone
15
+ params.each do |param, value|
16
+ res.gsub! "%{#{param}}", value.to_s
17
+ end
18
+ self.command = res
19
+ raise ArgumentError.new("Command is not parsed '#{self.command}'") if self.command.match(/%{[\w\-.]+}/)
20
+ end
21
+
22
+ def execute params = {}
23
+ puts command if params[:verbose] || self.class.verbose
24
+ if params[:debug] || self.class.debug
25
+ true
26
+ else
27
+ system command
28
+ end
29
+ end
30
+
31
+ def to_s
32
+ command
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,106 @@
1
+ # encoding: utf-8
2
+
3
+ module VideoConverter
4
+ class Ffmpeg
5
+ class << self
6
+ attr_accessor :bin, :one_pass, :one_pass_command, :first_pass_command, :second_pass_command, :metadata_command
7
+ end
8
+
9
+ self.bin = '/usr/local/bin/ffmpeg'
10
+
11
+ self.one_pass = false
12
+
13
+ self.one_pass_command = "%{bin} -i %{input} -y -aspect %{aspect} -acodec copy -vcodec libx264 -g 100 -keyint_min 50 -b:v %{bitrate}k -bt %{bitrate}k -threads %{threads} -f mp4 %{file} 1>%{log} 2>&1 || exit 1"
14
+
15
+ self.first_pass_command = "%{bin} -i %{input} -y -aspect %{aspect} -an -vcodec libx264 -g 100 -keyint_min 50 -pass 1 -passlogfile %{input}.log -b:v 700k -bt 700k -threads %{threads} -f mp4 /dev/null 1>>%{log} 2>&1 || exit 1"
16
+
17
+ self.second_pass_command = "%{bin} -i %{input} -y -aspect %{aspect} -acodec copy -vcodec libx264 -g 100 -keyint_min 50 -pass 2 -passlogfile %{input}.log -b:v %{bitrate}k -bt %{bitrate}k -threads %{threads} -f mp4 %{file} 1>%{log} 2>&1 || exit 1"
18
+
19
+ self.metadata_command = "%{bin} -i %{input} 2>&1"
20
+
21
+ attr_accessor :input, :profile, :one_pass, :paral, :log
22
+
23
+ def initialize params
24
+ params.each do |param, value|
25
+ self.send("#{param}=", value)
26
+ end
27
+ end
28
+
29
+ def run
30
+ res = true
31
+ threads = []
32
+ Profile.groups(profile).each do |qualities|
33
+ unless one_pass
34
+ group_command = Command.new self.class.first_pass_command, common_params.merge(qualities.first.to_hash)
35
+ res &&= group_command.execute
36
+ end
37
+ qualities.each do |quality|
38
+ if one_pass
39
+ quality_command = Command.new self.class.one_pass_command, common_params.merge(quality.to_hash)
40
+ else
41
+ quality_command = Command.new self.class.second_pass_command, common_params.merge(quality.to_hash)
42
+ end
43
+ if paral
44
+ threads << Thread.new { res &&= quality_command.execute }
45
+ else
46
+ res &&= quality_command.execute
47
+ end
48
+ end
49
+ end
50
+ threads.each { |t| t.join } if paral
51
+ res
52
+ end
53
+
54
+ def metadata
55
+ metadata = {}
56
+ s = `#{Command.new self.class.metadata_command, common_params}`
57
+ if (m = s.match(/Stream.*?Audio:\s*(\w+).*?(\d+)\s*Hz.*?(\d+)\s*kb\/s$/).to_a).any?
58
+ metadata[:audio_codec] = m[1]
59
+ metadata[:audio_sample_rate] = m[2].to_i
60
+ metadata[:audio_bitrate_in_kbps] = m[3].to_i
61
+ end
62
+ if (m = s.scan(/Stream #\d+:\d+/)).any?
63
+ metadata[:channels] = m.count
64
+ end
65
+ if (m = s.match(/Duration:\s+(\d+):(\d+):([\d.]+).*?bitrate:\s*(\d+)\s*kb\/s/).to_a).any?
66
+ metadata[:duration_in_ms] = ((m[1].to_i * 3600 + m[2].to_i * 60 + m[3].to_f) * 1000).to_i
67
+ metadata[:total_bitrate_in_kbps] = m[4].to_i
68
+ end
69
+ if (m = s.match(/Stream.*?Video:\s(\S+).*?,\s*((\d+)x(\d+)).*?(\d+)\s*kb\/s.*?([\d.]+)\s*fps/).to_a).any?
70
+ metadata[:video_codec] = m[1]
71
+ metadata[:width] = m[3].to_i
72
+ metadata[:height] = m[4].to_i
73
+ metadata[:video_bitrate_in_kbps] = m[5].to_i
74
+ metadata[:frame_rate] = m[6].to_f
75
+ end
76
+ if metadata.any?
77
+ if is_url?
78
+ url = URI.parse(input)
79
+ Net::HTTP.start(url.host) do |http|
80
+ response = http.request_head url.path
81
+ metadata[:file_size_in_bytes] = response['content-length'].to_i
82
+ puts response['content-type']
83
+ end
84
+ elsif is_local?
85
+ metadata[:file_size_in_bytes] = File.size(input)
86
+ end
87
+ metadata[:format] = File.extname(input).sub('.', '')
88
+ end
89
+ metadata
90
+ end
91
+
92
+ private
93
+
94
+ def common_params
95
+ { :bin => self.class.bin, :input => input, :log => log }
96
+ end
97
+
98
+ def is_url?
99
+ !!input.match(/^http:\/\//)
100
+ end
101
+
102
+ def is_local?
103
+ File.file?(input)
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,98 @@
1
+ # encoding: utf-8
2
+
3
+ module VideoConverter
4
+ class LiveSegmenter
5
+ class << self
6
+ attr_accessor :bin, :ffprobe_bin, :chunks_command, :segment_length, :filename_prefix, :encoding_profile, :delete_input
7
+ end
8
+
9
+ self.bin = '/usr/local/bin/live_segmenter'
10
+
11
+ self.ffprobe_bin = '/usr/local/bin/ffprobe'
12
+
13
+ self.segment_length = 10
14
+
15
+ self.filename_prefix = 's'
16
+
17
+ self.encoding_profile = 's'
18
+
19
+ self.delete_input = true
20
+
21
+ self.chunks_command = '%{ffmpeg_bin} -i %{input} -vcodec libx264 -acodec copy -f mpegts pipe:1 2>>/dev/null | %{bin} %{segment_length} %{dir} %{filename_prefix} %{encoding_profile} 1>>%{log} 2>&1'
22
+
23
+ attr_accessor :profile, :playlist_dir, :paral, :segment_length, :filename_prefix, :encoding_profile, :delete_input, :chunk_base, :log
24
+
25
+ def initialize params
26
+ [:profile, :playlist_dir].each do |param|
27
+ self.send("#{param}=", params[param])
28
+ end
29
+ [:segment_length, :filename_prefix, :encoding_profile, :delete_input].each do |param|
30
+ self.send("#{param}=", params[param].nil? ? self.class.send(param) : params[param])
31
+ end
32
+ self.chunk_base = params[:chunk_base] ? params[:chunk_base] : '.'
33
+ self.chunk_base += '/' unless chunk_base.end_with?('/')
34
+ self.log = params[:log]
35
+ end
36
+
37
+ def run
38
+ res = true
39
+ threads = []
40
+ p = Proc.new do |profile|
41
+ input = profile.to_hash[:file]
42
+ output = profile.to_hash[:dir]
43
+ make_chunks(input, output) && gen_quality_playlist(output, "#{File.basename(output)}.m3u8")
44
+ end
45
+ [profile].flatten.each do |profile|
46
+ if paral
47
+ threads << Thread.new { res &&= p.call(profile) }
48
+ else
49
+ res &&= p.call(profile)
50
+ end
51
+ end
52
+ gen_group_playlists
53
+ end
54
+
55
+ private
56
+
57
+ def make_chunks input, output
58
+ params = {}
59
+ [:segment_length, :filename_prefix, :encoding_profile].each { |param| params[param] = self.send(param) }
60
+ command = Command.new self.class.chunks_command, params.merge(:input => input, :dir => output).merge(common_params)
61
+ res = command.execute
62
+ FileUtils.rm input if delete_input
63
+ res
64
+ end
65
+
66
+ def gen_quality_playlist chunks_dir, playlist_name
67
+ res = ''
68
+ durations = []
69
+ Dir::glob(File.join(chunks_dir, 's-*[0-9].ts')).each do |chunk|
70
+ durations << (duration = chunk_duration chunk)
71
+ res += "#EXTINF:#%0.2f\n" % duration
72
+ res += "#{chunk_base}#{File.basename(chunks_dir)}/#{File.basename(chunk)}\n"
73
+ end
74
+ res = "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-TARGETDURATION:#{durations.max}\n#EXT-X-MEDIA-SEQUENCE:0\n" + res + "#EXT-X-ENDLIST"
75
+ File.open(File.join(playlist_dir, playlist_name), 'w') { |f| f.write res }
76
+ end
77
+
78
+ def gen_group_playlists
79
+ Profile.groups(profile).each_with_index do |group, index|
80
+ res = "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-PLAYLIST-TYPE:VOD"
81
+ group.sort { |g1, g2| g1.to_hash[:bandwidth] <=> g2.to_hash[:bandwidth] }.each do |quality|
82
+ res += "#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=#{quality.to_hash[:bandwidth] * 1000}\n"
83
+ res += File.join(chunk_base, playlist_dir, File.basename(quality.to_hash[:dir]) + '.m3u8')
84
+ end
85
+ res += "#EXT-X-ENDLIST"
86
+ File.open(File.join(playlist_dir, "playlist#{index + 1}.m3u8"), 'w') { |f| f.write res }
87
+ end
88
+ end
89
+
90
+ def common_params
91
+ { :ffmpeg_bin => Ffmpeg.bin, :bin => self.class.bin, :log => log }
92
+ end
93
+
94
+ def chunk_duration chunk
95
+ s = `#{self.class.ffprobe_bin} #{chunk} 2>&1`.match(/Duration:.*(?:[0-9]{2}):(?:[0-9]{2}):([0-9]{2}(?:\.[0-9]{2})?)/).to_a[1].to_f
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ module VideoConverter
4
+ class Process
5
+ attr_accessor :id, :status, :progress, :pid
6
+
7
+ class << self
8
+ attr_accessor :path
9
+ end
10
+ self.path = 'tmp/processes'
11
+
12
+ def initialize id
13
+ self.id = id
14
+ Dir.mkdir(self.class.path) unless Dir.exists?(self.class.path)
15
+ end
16
+
17
+ [:status, :progress, :pid].each do |attr|
18
+ define_method(attr) { File.open(send("#{attr}_file".to_sym), 'r') { |f| f.read } rescue nil }
19
+ define_method("#{attr}=") { |value| File.open(send("#{attr}_file".to_sym), 'w') { |f| f.write value } }
20
+ end
21
+
22
+ def stop
23
+ `pkill -P #{pid}`
24
+ end
25
+
26
+ private
27
+
28
+ [:status, :progress, :pid].each do |attr|
29
+ define_method("#{attr}_file".to_sym) { File.join self.class.path, "#{id}_#{attr}" }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+
3
+ module VideoConverter
4
+ class Profile
5
+ class << self
6
+ attr_accessor :needed_params, :default_params
7
+ end
8
+
9
+ self.needed_params = [:bitrate]
10
+ self.default_params = {:aspect => '4:3', :threads => 1}
11
+
12
+ attr_accessor :params
13
+
14
+ def initialize params
15
+ self.class.needed_params.each do |needed_param|
16
+ raise ArgumentError.new("#{needed_param} is needed") unless params[needed_param]
17
+ end
18
+ self.params = self.class.default_params.merge params
19
+ raise ArgumentError.new("Output file or output dir is needed") unless params[:file] || params[:dir]
20
+ self.params[:dir] = params[:dir] || File.dirname(params[:file])
21
+ self.params[:file] = params[:file] || File.join(params[:dir], "#{object_id}.mp4")
22
+ FileUtils.mkdir_p self.params[:dir]
23
+ self.params[:bandwidth] = params[:bandwidth] || params[:bitrate]
24
+ end
25
+
26
+ def to_hash
27
+ params
28
+ end
29
+
30
+ def self.groups profiles
31
+ groups = profiles.is_a?(Array) ? profiles : [profiles]
32
+ groups.map do |qualities|
33
+ qualities.is_a?(Array) ? qualities : [qualities]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module VideoConverter
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,31 @@
1
+ require "video_converter/version"
2
+ require "video_converter/profile"
3
+ require "video_converter/base"
4
+ require "video_converter/command"
5
+ require "video_converter/process"
6
+ require "video_converter/ffmpeg"
7
+ require "video_converter/live_segmenter"
8
+ require "fileutils"
9
+ require "net/http"
10
+
11
+ module VideoConverter
12
+ class << self
13
+ attr_accessor :type, :paral, :no_convert
14
+ end
15
+
16
+ self.type = :mp4
17
+ self.paral = true
18
+ self.no_convert = false
19
+
20
+ def self.new params
21
+ VideoConverter::Base.new params
22
+ end
23
+
24
+ def self.find id
25
+ VideoConverter::Process.new id
26
+ end
27
+
28
+ def self.metadata input
29
+ VideoConverter::Ffmpeg.new(:input => input).metadata
30
+ end
31
+ end
@@ -0,0 +1,37 @@
1
+ require 'test_helper'
2
+
3
+ class FfmpegTest < Test::Unit::TestCase
4
+ context 'metadata' do
5
+ context 'when input does not exist' do
6
+ should 'be empty' do
7
+ assert VideoConverter::Ffmpeg.new(:input => 'tmp/fixtures/not_existed_file').metadata.empty?
8
+ end
9
+ end
10
+
11
+ context 'when input is dir' do
12
+ should 'be empty' do
13
+ assert VideoConverter::Ffmpeg.new(:input => 'tmp/fixtures').metadata.empty?
14
+ end
15
+ end
16
+
17
+ context 'when input is not video file' do
18
+ should 'be empty' do
19
+ assert VideoConverter::Ffmpeg.new(:input => __FILE__).metadata.empty?
20
+ end
21
+ end
22
+
23
+ context 'when input is video file' do
24
+ should 'not be empty' do
25
+ h = {:audio_bitrate_in_kbps=>97, :audio_codec=>"aac", :audio_sample_rate=>44100, :channels=>2, :duration_in_ms=>4019, :total_bitrate_in_kbps=>1123, :frame_rate=>29.97, :height=>360, :video_bitrate_in_kbps=>1020, :video_codec=>"h264", :width=>640, :file_size_in_bytes=>564356, :format=>"mp4"}
26
+ assert_equal h, VideoConverter::Ffmpeg.new(:input => 'test/fixtures/test.mp4').metadata
27
+ end
28
+ end
29
+
30
+ context 'when input is url' do
31
+ should 'not be empty' do
32
+ h = {:audio_codec=>"aac", :audio_sample_rate=>48000, :audio_bitrate_in_kbps=>83, :channels=>2, :duration_in_ms=>5570, :total_bitrate_in_kbps=>551, :video_codec=>"h264", :width=>560, :height=>320, :video_bitrate_in_kbps=>465, :frame_rate=>30.0, :file_size_in_bytes=>383631, :format=>"mp4"}
33
+ assert_equal h, VideoConverter::Ffmpeg.new(:input => 'http://techslides.com/demos/sample-videos/small.mp4').metadata
34
+ end
35
+ end
36
+ end
37
+ end
Binary file
@@ -0,0 +1,121 @@
1
+ #options: 640x360 fps=30000/1001 timebase=1001/30000 bitdepth=8 cabac=1 ref=1 deblock=1:0:0 analyse=0x1:0 me=dia subme=2 psy=1 psy_rd=1.00:0.00 mixed_ref=0 me_range=16 chroma_me=1 trellis=0 8x8dct=0 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=0 threads=1 lookahead_threads=1 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=100 keyint_min=50 scenecut=40 intra_refresh=0 rc_lookahead=40 rc=abr mbtree=1 bitrate=700 ratetol=1.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00
2
+ in:0 out:0 type:I dur:2 cpbdur:2 q:33.14 aq:18.00 tex:205 mv:380 misc:103 imb:920 pmb:0 smb:0 d:- ref:;
3
+ in:1 out:1 type:i dur:2 cpbdur:2 q:29.14 aq:20.24 tex:4049 mv:2267 misc:100 imb:920 pmb:0 smb:0 d:- ref:;
4
+ in:2 out:2 type:P dur:2 cpbdur:2 q:32.05 aq:23.37 tex:3246 mv:1941 misc:797 imb:298 pmb:157 smb:465 d:- ref:0 w:0,2,-17 ;
5
+ in:3 out:3 type:P dur:2 cpbdur:2 q:28.05 aq:21.34 tex:8333 mv:4310 misc:333 imb:754 pmb:135 smb:31 d:- ref:0 w:1,3,-8 ;
6
+ in:4 out:4 type:P dur:2 cpbdur:2 q:24.05 aq:18.13 tex:16844 mv:5507 misc:289 imb:634 pmb:261 smb:25 d:- ref:0 w:5,43,-5,6,83,-38,78,-28 ;
7
+ in:5 out:5 type:P dur:2 cpbdur:2 q:23.54 aq:18.12 tex:20991 mv:6179 misc:350 imb:594 pmb:301 smb:25 d:- ref:0 w:5,39,-3,6,76,-24,83,-38 ;
8
+ in:6 out:6 type:P dur:2 cpbdur:2 q:23.72 aq:18.61 tex:24730 mv:6631 misc:327 imb:566 pmb:338 smb:16 d:- ref:0 w:6,77,-3,6,75,-22,77,-26 ;
9
+ in:7 out:7 type:P dur:2 cpbdur:2 q:24.08 aq:19.67 tex:23056 mv:6587 misc:285 imb:563 pmb:350 smb:7 d:- ref:0 w:6,73,-2,6,71,-14,73,-18 ;
10
+ in:8 out:8 type:P dur:2 cpbdur:2 q:24.34 aq:19.98 tex:24310 mv:6658 misc:296 imb:501 pmb:403 smb:16 d:- ref:0 w:6,73,-3,6,75,-22,71,-14 ;
11
+ in:9 out:9 type:P dur:2 cpbdur:2 q:24.64 aq:20.83 tex:23461 mv:6741 misc:414 imb:436 pmb:442 smb:42 d:- ref:0 w:6,71,-2,6,71,-14,64,0 ;
12
+ in:10 out:10 type:P dur:2 cpbdur:2 q:24.90 aq:21.09 tex:26155 mv:6973 misc:352 imb:466 pmb:428 smb:26 d:- ref:0 w:5,35,-1,6,69,-10,64,0 ;
13
+ in:11 out:11 type:P dur:2 cpbdur:2 q:25.25 aq:21.57 tex:25134 mv:6943 misc:339 imb:457 pmb:443 smb:20 d:- ref:0 w:6,69,-1,6,68,-8,70,-12 ;
14
+ in:12 out:12 type:P dur:2 cpbdur:2 q:25.54 aq:21.90 tex:26632 mv:6876 misc:412 imb:420 pmb:460 smb:40 d:- ref:0 w:6,69,-1,6,69,-10,67,-6 ;
15
+ in:13 out:13 type:P dur:2 cpbdur:2 q:25.88 aq:22.43 tex:24703 mv:6714 misc:519 imb:411 pmb:437 smb:72 d:- ref:0 w:4,17,-1,6,67,-6,68,-8 ;
16
+ in:14 out:14 type:P dur:2 cpbdur:2 q:26.14 aq:22.84 tex:25446 mv:7196 misc:390 imb:447 pmb:437 smb:36 d:- ref:0 w:6,67,0,6,68,-8,67,-6 ;
17
+ in:15 out:15 type:P dur:2 cpbdur:2 q:26.42 aq:23.21 tex:26798 mv:7010 misc:464 imb:452 pmb:411 smb:57 d:- ref:0 w:4,17,-1,6,68,-8,69,-10 ;
18
+ in:16 out:16 type:P dur:2 cpbdur:2 q:26.73 aq:23.70 tex:24895 mv:6860 misc:421 imb:367 pmb:484 smb:69 d:- ref:0 w:6,67,-1 ;
19
+ in:17 out:17 type:P dur:2 cpbdur:2 q:26.97 aq:24.00 tex:25065 mv:6580 misc:603 imb:350 pmb:464 smb:106 d:- ref:0 w:6,67,-1,6,67,-6,67,-6 ;
20
+ in:18 out:18 type:P dur:2 cpbdur:2 q:27.21 aq:24.39 tex:24375 mv:7175 misc:434 imb:409 pmb:459 smb:52 d:- ref:0 w:6,67,-2,6,66,-4,67,-6 ;
21
+ in:19 out:19 type:P dur:2 cpbdur:2 q:27.44 aq:24.61 tex:26022 mv:6799 misc:499 imb:372 pmb:467 smb:81 d:- ref:0 w:5,33,0 ;
22
+ in:20 out:20 type:P dur:2 cpbdur:2 q:27.70 aq:25.06 tex:25445 mv:6872 misc:475 imb:355 pmb:482 smb:83 d:- ref:0 w:5,33,0 ;
23
+ in:21 out:21 type:P dur:2 cpbdur:2 q:27.93 aq:25.37 tex:24717 mv:6969 misc:554 imb:378 pmb:455 smb:87 d:- ref:0 w:5,33,-1,6,66,-4,66,-4 ;
24
+ in:22 out:22 type:P dur:2 cpbdur:2 q:28.15 aq:25.55 tex:26902 mv:6863 misc:451 imb:444 pmb:401 smb:75 d:- ref:0 ;
25
+ in:23 out:23 type:P dur:2 cpbdur:2 q:28.41 aq:25.79 tex:26636 mv:6915 misc:529 imb:404 pmb:410 smb:106 d:- ref:0 w:6,65,-1 ;
26
+ in:24 out:24 type:P dur:2 cpbdur:2 q:28.66 aq:26.24 tex:24210 mv:6889 misc:557 imb:399 pmb:420 smb:101 d:- ref:0 w:6,65,-1,6,66,-4,66,-4 ;
27
+ in:25 out:25 type:P dur:2 cpbdur:2 q:28.86 aq:26.29 tex:23847 mv:6719 misc:594 imb:353 pmb:430 smb:137 d:- ref:0 w:6,65,-1 ;
28
+ in:26 out:26 type:P dur:2 cpbdur:2 q:29.04 aq:26.51 tex:25242 mv:6973 misc:441 imb:406 pmb:427 smb:87 d:- ref:0 ;
29
+ in:27 out:27 type:P dur:2 cpbdur:2 q:29.26 aq:26.70 tex:25484 mv:7034 misc:506 imb:393 pmb:432 smb:95 d:- ref:0 ;
30
+ in:28 out:28 type:P dur:2 cpbdur:2 q:29.47 aq:27.10 tex:25042 mv:6797 misc:521 imb:366 pmb:437 smb:117 d:- ref:0 ;
31
+ in:29 out:29 type:P dur:2 cpbdur:2 q:29.67 aq:27.32 tex:23727 mv:6518 misc:635 imb:338 pmb:421 smb:161 d:- ref:0 ;
32
+ in:30 out:30 type:P dur:2 cpbdur:2 q:29.84 aq:27.35 tex:26996 mv:6966 misc:502 imb:360 pmb:448 smb:112 d:- ref:0 ;
33
+ in:31 out:31 type:P dur:2 cpbdur:2 q:30.06 aq:27.58 tex:22884 mv:6358 misc:686 imb:263 pmb:450 smb:207 d:- ref:0 ;
34
+ in:32 out:32 type:P dur:2 cpbdur:2 q:30.19 aq:27.64 tex:23387 mv:6580 misc:601 imb:297 pmb:459 smb:164 d:- ref:0 ;
35
+ in:33 out:33 type:P dur:2 cpbdur:2 q:30.33 aq:27.83 tex:22916 mv:6440 misc:636 imb:295 pmb:457 smb:168 d:- ref:0 ;
36
+ in:34 out:34 type:P dur:2 cpbdur:2 q:30.46 aq:27.82 tex:21683 mv:6188 misc:689 imb:236 pmb:439 smb:245 d:- ref:0 ;
37
+ in:35 out:35 type:P dur:2 cpbdur:2 q:30.56 aq:28.06 tex:21790 mv:6219 misc:703 imb:263 pmb:434 smb:223 d:- ref:0 ;
38
+ in:36 out:36 type:P dur:2 cpbdur:2 q:30.66 aq:28.08 tex:19031 mv:5826 misc:735 imb:213 pmb:443 smb:264 d:- ref:0 ;
39
+ in:37 out:37 type:P dur:2 cpbdur:2 q:30.71 aq:28.22 tex:21104 mv:6127 misc:697 imb:282 pmb:430 smb:208 d:- ref:0 ;
40
+ in:38 out:38 type:P dur:2 cpbdur:2 q:30.80 aq:27.93 tex:18956 mv:5969 misc:779 imb:161 pmb:460 smb:299 d:- ref:0 ;
41
+ in:39 out:39 type:P dur:2 cpbdur:2 q:30.85 aq:27.81 tex:20882 mv:5861 misc:745 imb:221 pmb:444 smb:255 d:- ref:0 ;
42
+ in:41 out:40 type:P dur:2 cpbdur:2 q:30.93 aq:28.07 tex:28752 mv:7223 misc:561 imb:364 pmb:446 smb:110 d:- ref:0 ;
43
+ in:40 out:41 type:b dur:2 cpbdur:2 q:30.89 aq:30.01 tex:7803 mv:4482 misc:707 imb:27 pmb:310 smb:583 d:- ref:0 ;
44
+ in:43 out:42 type:P dur:2 cpbdur:2 q:31.00 aq:28.08 tex:26935 mv:7148 misc:581 imb:303 pmb:469 smb:148 d:- ref:0 ;
45
+ in:42 out:43 type:b dur:2 cpbdur:2 q:30.96 aq:30.11 tex:7909 mv:4292 misc:679 imb:29 pmb:300 smb:591 d:- ref:0 ;
46
+ in:45 out:44 type:P dur:2 cpbdur:2 q:31.03 aq:27.98 tex:26739 mv:6872 misc:677 imb:281 pmb:462 smb:177 d:- ref:0 ;
47
+ in:44 out:45 type:b dur:2 cpbdur:2 q:31.01 aq:29.63 tex:6849 mv:4516 misc:667 imb:22 pmb:315 smb:583 d:- ref:0 ;
48
+ in:47 out:46 type:P dur:2 cpbdur:2 q:31.05 aq:27.76 tex:25936 mv:6521 misc:655 imb:239 pmb:458 smb:223 d:- ref:0 ;
49
+ in:46 out:47 type:b dur:2 cpbdur:2 q:31.04 aq:30.40 tex:7107 mv:4175 misc:662 imb:19 pmb:300 smb:601 d:- ref:0 ;
50
+ in:49 out:48 type:P dur:2 cpbdur:2 q:31.05 aq:27.87 tex:27152 mv:6383 misc:665 imb:269 pmb:432 smb:219 d:- ref:0 ;
51
+ in:48 out:49 type:b dur:2 cpbdur:2 q:31.05 aq:31.23 tex:6677 mv:4091 misc:704 imb:22 pmb:301 smb:597 d:- ref:0 ;
52
+ in:51 out:50 type:P dur:2 cpbdur:2 q:31.06 aq:27.83 tex:23699 mv:6311 misc:654 imb:197 pmb:445 smb:278 d:- ref:0 ;
53
+ in:50 out:51 type:b dur:2 cpbdur:2 q:31.06 aq:31.44 tex:7277 mv:4334 misc:653 imb:26 pmb:316 smb:578 d:- ref:0 ;
54
+ in:53 out:52 type:P dur:2 cpbdur:2 q:31.04 aq:27.97 tex:25826 mv:6713 misc:573 imb:277 pmb:435 smb:208 d:- ref:0 ;
55
+ in:52 out:53 type:b dur:2 cpbdur:2 q:31.05 aq:30.39 tex:7357 mv:4487 misc:676 imb:26 pmb:322 smb:572 d:- ref:0 ;
56
+ in:54 out:54 type:P dur:2 cpbdur:2 q:31.04 aq:28.09 tex:22368 mv:5575 misc:705 imb:151 pmb:473 smb:296 d:- ref:0 ;
57
+ in:55 out:55 type:P dur:2 cpbdur:2 q:31.11 aq:28.43 tex:22624 mv:5356 misc:676 imb:130 pmb:475 smb:315 d:- ref:0 ;
58
+ in:56 out:56 type:P dur:2 cpbdur:2 q:31.18 aq:28.68 tex:22984 mv:5376 misc:752 imb:96 pmb:513 smb:311 d:- ref:0 ;
59
+ in:57 out:57 type:P dur:2 cpbdur:2 q:31.25 aq:28.62 tex:24476 mv:5388 misc:648 imb:112 pmb:515 smb:293 d:- ref:0 ;
60
+ in:58 out:58 type:P dur:2 cpbdur:2 q:31.34 aq:28.63 tex:23638 mv:5248 misc:658 imb:117 pmb:507 smb:296 d:- ref:0 ;
61
+ in:59 out:59 type:P dur:2 cpbdur:2 q:31.42 aq:28.96 tex:22401 mv:5055 misc:688 imb:112 pmb:483 smb:325 d:- ref:0 ;
62
+ in:60 out:60 type:P dur:2 cpbdur:2 q:31.48 aq:28.46 tex:42376 mv:7096 misc:336 imb:345 pmb:543 smb:32 d:- ref:0 w:6,65,-3 ;
63
+ in:61 out:61 type:P dur:2 cpbdur:2 q:31.76 aq:28.94 tex:17183 mv:4556 misc:717 imb:87 pmb:452 smb:381 d:- ref:0 ;
64
+ in:62 out:62 type:P dur:2 cpbdur:2 q:31.76 aq:29.07 tex:23387 mv:5228 misc:681 imb:97 pmb:518 smb:305 d:- ref:0 ;
65
+ in:63 out:63 type:P dur:2 cpbdur:2 q:31.84 aq:29.21 tex:26071 mv:5377 misc:648 imb:126 pmb:501 smb:293 d:- ref:0 ;
66
+ in:64 out:64 type:P dur:2 cpbdur:2 q:31.93 aq:29.29 tex:25836 mv:5059 misc:729 imb:112 pmb:493 smb:315 d:- ref:0 ;
67
+ in:65 out:65 type:P dur:2 cpbdur:2 q:32.03 aq:28.99 tex:26955 mv:5356 misc:689 imb:129 pmb:514 smb:277 d:- ref:0 ;
68
+ in:66 out:66 type:P dur:2 cpbdur:2 q:32.13 aq:29.10 tex:25827 mv:5203 misc:738 imb:126 pmb:512 smb:282 d:- ref:0 ;
69
+ in:67 out:67 type:P dur:2 cpbdur:2 q:32.23 aq:29.30 tex:24530 mv:5245 misc:673 imb:122 pmb:520 smb:278 d:- ref:0 ;
70
+ in:68 out:68 type:P dur:2 cpbdur:2 q:32.30 aq:29.41 tex:24731 mv:4905 misc:724 imb:111 pmb:506 smb:303 d:- ref:0 ;
71
+ in:69 out:69 type:P dur:2 cpbdur:2 q:32.38 aq:29.56 tex:25374 mv:4770 misc:720 imb:116 pmb:494 smb:310 d:- ref:0 ;
72
+ in:70 out:70 type:P dur:2 cpbdur:2 q:32.46 aq:29.69 tex:22297 mv:4759 misc:712 imb:128 pmb:491 smb:301 d:- ref:0 ;
73
+ in:71 out:71 type:P dur:2 cpbdur:2 q:32.51 aq:29.74 tex:16703 mv:4591 misc:754 imb:99 pmb:461 smb:360 d:- ref:0 ;
74
+ in:72 out:72 type:P dur:2 cpbdur:2 q:32.51 aq:29.20 tex:17551 mv:4877 misc:732 imb:134 pmb:449 smb:337 d:- ref:0 ;
75
+ in:73 out:73 type:P dur:2 cpbdur:2 q:32.52 aq:29.52 tex:16919 mv:4693 misc:748 imb:112 pmb:452 smb:356 d:- ref:0 ;
76
+ in:75 out:74 type:P dur:2 cpbdur:2 q:32.52 aq:29.28 tex:25128 mv:5752 misc:672 imb:169 pmb:487 smb:264 d:- ref:0 ;
77
+ in:74 out:75 type:b dur:2 cpbdur:2 q:32.52 aq:32.38 tex:5715 mv:3413 misc:608 imb:27 pmb:244 smb:649 d:- ref:0 ;
78
+ in:76 out:76 type:P dur:2 cpbdur:2 q:32.49 aq:29.78 tex:15885 mv:4424 misc:723 imb:104 pmb:451 smb:365 d:- ref:0 ;
79
+ in:77 out:77 type:P dur:2 cpbdur:2 q:32.48 aq:29.42 tex:16511 mv:4423 misc:810 imb:103 pmb:438 smb:379 d:- ref:0 ;
80
+ in:78 out:78 type:P dur:2 cpbdur:2 q:32.47 aq:29.58 tex:15842 mv:4754 misc:764 imb:113 pmb:457 smb:350 d:- ref:0 ;
81
+ in:79 out:79 type:P dur:2 cpbdur:2 q:32.47 aq:29.76 tex:17153 mv:4646 misc:753 imb:116 pmb:454 smb:350 d:- ref:0 ;
82
+ in:80 out:80 type:P dur:2 cpbdur:2 q:32.47 aq:29.62 tex:17509 mv:4765 misc:758 imb:122 pmb:457 smb:341 d:- ref:0 ;
83
+ in:81 out:81 type:P dur:2 cpbdur:2 q:32.47 aq:30.20 tex:16122 mv:4382 misc:808 imb:112 pmb:407 smb:401 d:- ref:0 ;
84
+ in:82 out:82 type:P dur:2 cpbdur:2 q:32.46 aq:30.02 tex:15440 mv:4399 misc:785 imb:102 pmb:409 smb:409 d:- ref:0 ;
85
+ in:83 out:83 type:P dur:2 cpbdur:2 q:32.45 aq:30.46 tex:15462 mv:4532 misc:790 imb:86 pmb:434 smb:400 d:- ref:0 ;
86
+ in:85 out:84 type:P dur:2 cpbdur:2 q:32.44 aq:30.16 tex:24122 mv:6105 misc:637 imb:319 pmb:456 smb:145 d:- ref:0 w:5,31,0,6,64,0,62,4 ;
87
+ in:84 out:85 type:b dur:2 cpbdur:2 q:32.44 aq:31.32 tex:6184 mv:4225 misc:711 imb:50 pmb:282 smb:588 d:- ref:0 ;
88
+ in:87 out:86 type:P dur:2 cpbdur:2 q:32.41 aq:30.25 tex:23201 mv:5815 misc:744 imb:246 pmb:468 smb:206 d:- ref:0 w:7,121,1,6,61,6,62,4 ;
89
+ in:86 out:87 type:b dur:2 cpbdur:2 q:32.42 aq:31.31 tex:5353 mv:3660 misc:747 imb:41 pmb:264 smb:615 d:- ref:0 ;
90
+ in:89 out:88 type:P dur:2 cpbdur:2 q:32.37 aq:29.96 tex:23360 mv:5700 misc:700 imb:264 pmb:455 smb:201 d:- ref:0 w:7,121,1,6,61,6,61,6 ;
91
+ in:88 out:89 type:b dur:2 cpbdur:2 q:32.39 aq:31.44 tex:5700 mv:3560 misc:772 imb:50 pmb:276 smb:594 d:- ref:0 ;
92
+ in:91 out:90 type:P dur:2 cpbdur:2 q:32.33 aq:29.77 tex:20051 mv:5795 misc:722 imb:264 pmb:445 smb:211 d:- ref:0 w:7,121,1,6,60,8,61,6 ;
93
+ in:90 out:91 type:b dur:2 cpbdur:2 q:32.35 aq:31.03 tex:5095 mv:3481 misc:824 imb:42 pmb:306 smb:572 d:- ref:0 ;
94
+ in:93 out:92 type:P dur:2 cpbdur:2 q:32.26 aq:29.64 tex:19332 mv:5664 misc:812 imb:251 pmb:430 smb:239 d:- ref:0 w:4,15,1,6,60,8,60,8 ;
95
+ in:92 out:93 type:b dur:2 cpbdur:2 q:32.29 aq:30.59 tex:4641 mv:3684 misc:731 imb:46 pmb:261 smb:613 d:- ref:0 ;
96
+ in:95 out:94 type:P dur:2 cpbdur:2 q:32.18 aq:29.45 tex:20130 mv:5813 misc:721 imb:310 pmb:436 smb:174 d:- ref:0 w:7,121,0,6,60,8,61,6 ;
97
+ in:94 out:95 type:b dur:2 cpbdur:2 q:32.22 aq:30.77 tex:4876 mv:3497 misc:707 imb:40 pmb:259 smb:621 d:- ref:0 ;
98
+ in:97 out:96 type:P dur:2 cpbdur:2 q:32.11 aq:29.43 tex:15411 mv:5139 misc:850 imb:243 pmb:389 smb:288 d:- ref:0 w:7,119,1,6,59,10,59,10 ;
99
+ in:96 out:97 type:b dur:2 cpbdur:2 q:32.15 aq:29.82 tex:4459 mv:3598 misc:711 imb:61 pmb:243 smb:616 d:- ref:0 ;
100
+ in:98 out:98 type:P dur:2 cpbdur:2 q:32.00 aq:28.84 tex:12074 mv:4437 misc:809 imb:216 pmb:361 smb:343 d:- ref:0 w:7,123,1 ;
101
+ in:99 out:99 type:P dur:2 cpbdur:2 q:31.96 aq:29.85 tex:7668 mv:3892 misc:888 imb:178 pmb:279 smb:463 d:- ref:0 w:7,123,1,6,61,6,64,0 ;
102
+ in:100 out:100 type:I dur:2 cpbdur:2 q:29.14 aq:26.06 tex:49418 mv:15983 misc:119 imb:920 pmb:0 smb:0 d:- ref:w:7,123,1,6,61,6,64,0 ;
103
+ in:102 out:101 type:P dur:2 cpbdur:2 q:32.12 aq:28.94 tex:11064 mv:4379 misc:837 imb:199 pmb:313 smb:408 d:- ref:0 w:6,59,1,6,59,10,60,8 ;
104
+ in:101 out:102 type:b dur:2 cpbdur:2 q:32.12 aq:28.93 tex:3230 mv:2835 misc:751 imb:48 pmb:216 smb:656 d:- ref:0 ;
105
+ in:103 out:103 type:P dur:2 cpbdur:2 q:31.96 aq:28.30 tex:8788 mv:3945 misc:907 imb:184 pmb:314 smb:422 d:- ref:0 w:6,61,1,6,60,8,60,8 ;
106
+ in:104 out:104 type:P dur:2 cpbdur:2 q:31.90 aq:27.99 tex:8506 mv:4178 misc:876 imb:200 pmb:324 smb:396 d:- ref:0 w:6,61,1,6,60,8,64,0 ;
107
+ in:105 out:105 type:P dur:2 cpbdur:2 q:31.83 aq:28.29 tex:9136 mv:4302 misc:898 imb:241 pmb:325 smb:354 d:- ref:0 w:7,119,2,6,64,0,59,10 ;
108
+ in:107 out:106 type:P dur:2 cpbdur:2 q:31.78 aq:27.99 tex:11777 mv:5042 misc:845 imb:265 pmb:381 smb:274 d:- ref:0 w:3,7,3,6,58,12,58,12 ;
109
+ in:106 out:107 type:b dur:2 cpbdur:2 q:31.81 aq:28.13 tex:3444 mv:2414 misc:798 imb:53 pmb:216 smb:651 d:- ref:0 ;
110
+ in:108 out:108 type:P dur:2 cpbdur:2 q:31.63 aq:27.64 tex:8423 mv:3728 misc:953 imb:180 pmb:344 smb:396 d:- ref:0 w:7,119,2,6,64,0,60,8 ;
111
+ in:109 out:109 type:P dur:2 cpbdur:2 q:31.57 aq:27.42 tex:9777 mv:4228 misc:939 imb:222 pmb:362 smb:336 d:- ref:0 w:7,117,2,6,60,8,64,0 ;
112
+ in:110 out:110 type:P dur:2 cpbdur:2 q:31.52 aq:27.28 tex:9009 mv:4443 misc:908 imb:255 pmb:348 smb:317 d:- ref:0 w:7,117,3,6,60,8,59,10 ;
113
+ in:111 out:111 type:P dur:2 cpbdur:2 q:31.46 aq:27.05 tex:7301 mv:3981 misc:918 imb:218 pmb:326 smb:376 d:- ref:0 w:6,59,2,6,60,8,64,0 ;
114
+ in:112 out:112 type:P dur:2 cpbdur:2 q:31.39 aq:26.82 tex:8446 mv:4488 misc:938 imb:279 pmb:323 smb:318 d:- ref:0 w:5,29,3,6,64,0,58,12 ;
115
+ in:113 out:113 type:P dur:2 cpbdur:2 q:31.33 aq:27.00 tex:8078 mv:4181 misc:917 imb:267 pmb:313 smb:340 d:- ref:0 w:7,115,2,6,58,12,58,12 ;
116
+ in:114 out:114 type:P dur:2 cpbdur:2 q:31.27 aq:26.55 tex:7409 mv:4054 misc:969 imb:294 pmb:262 smb:364 d:- ref:0 w:6,57,3,6,59,10,64,0 ;
117
+ in:115 out:115 type:P dur:2 cpbdur:2 q:31.20 aq:26.08 tex:7408 mv:4489 misc:879 imb:334 pmb:313 smb:273 d:- ref:0 w:7,113,2,6,64,0,58,12 ;
118
+ in:116 out:116 type:P dur:2 cpbdur:2 q:31.13 aq:24.99 tex:6417 mv:4080 misc:919 imb:378 pmb:208 smb:334 d:- ref:0 w:3,7,3,6,56,16,55,18 ;
119
+ in:117 out:117 type:P dur:2 cpbdur:2 q:31.06 aq:24.44 tex:5770 mv:3770 misc:988 imb:329 pmb:203 smb:388 d:- ref:0 w:3,7,2,6,57,14,57,14 ;
120
+ in:118 out:118 type:P dur:2 cpbdur:2 q:30.98 aq:24.21 tex:4146 mv:3085 misc:969 imb:305 pmb:169 smb:446 d:- ref:0 w:5,27,3,6,64,0,55,18 ;
121
+ in:119 out:119 type:P dur:2 cpbdur:2 q:30.89 aq:23.18 tex:4318 mv:3051 misc:943 imb:355 pmb:140 smb:425 d:- ref:0 w:6,55,2,6,50,28,50,28 ;
@@ -0,0 +1,5 @@
1
+ $:.unshift File.expand_path('../lib', File.dirname(__FILE__))
2
+
3
+ require 'test/unit'
4
+ require 'shoulda-context'
5
+ require 'video_converter'
@@ -0,0 +1,90 @@
1
+ require 'test_helper'
2
+
3
+ class VideoConverterTest < Test::Unit::TestCase
4
+ context 'run' do
5
+ setup do
6
+ @input = 'test/fixtures/test.mp4'
7
+ end
8
+
9
+ context 'with type mp4' do
10
+ setup do
11
+ @profiles = []
12
+ @profiles << (@p11 = VideoConverter::Profile.new(:bitrate => 300, :file => 'tmp/test11.mp4'))
13
+ @profiles << (@p12 = VideoConverter::Profile.new(:bitrate => 400, :file => 'tmp/test12.mp4'))
14
+ @profiles << (@p21 = VideoConverter::Profile.new(:bitrate => 700, :file => 'tmp/test21.mp4'))
15
+ @profiles << (@p22 = VideoConverter::Profile.new(:bitrate => 700, :file => 'tmp/test22.mp4'))
16
+ @c = VideoConverter.new(:input => @input, :profile => [[@p11, @p12], [@p21, @p22]], :verbose => false, :log => 'tmp/test.log', :type => :mp4)
17
+ @res = @c.run
18
+ end
19
+ should 'convert files' do
20
+ 4.times do |n|
21
+ file = "tmp/test#{n / 2 + 1}#{n.even? ? 1 : 2}.mp4"
22
+ assert File.exists?(file)
23
+ assert File.size(file) > 0
24
+ end
25
+ end
26
+ should 'return success convert process' do
27
+ assert VideoConverter.find(@c.id)
28
+ assert @res
29
+ assert_equal 'convert_success', VideoConverter.find(@c.id).status
30
+ end
31
+ should 'write log file' do
32
+ assert File.exists?('tmp/test.log')
33
+ assert !File.read('tmp/test.log').empty?
34
+ end
35
+ end
36
+
37
+ context 'with type hls' do
38
+ setup do
39
+ @profiles = []
40
+ @profiles << (@p11 = VideoConverter::Profile.new(:bitrate => 300, :dir => 'tmp/test11'))
41
+ @profiles << (@p12 = VideoConverter::Profile.new(:bitrate => 400, :dir => 'tmp/test12'))
42
+ @profiles << (@p21 = VideoConverter::Profile.new(:bitrate => 700, :dir => 'tmp/test21'))
43
+ @profiles << (@p22 = VideoConverter::Profile.new(:bitrate => 700, :dir => 'tmp/test22'))
44
+ end
45
+ context '' do
46
+ setup do
47
+ @c = VideoConverter.new(:input => @input, :profile => [[@p11, @p12], [@p21, @p22]], :verbose => false, :log => 'tmp/test.log', :type => :hls, :playlist_dir => 'tmp')
48
+ @res = @c.run
49
+ end
50
+ should 'create chunks' do
51
+ @profiles.each do |profile|
52
+ assert File.exists?(File.join(profile.to_hash[:dir], 's-00001.ts'))
53
+ end
54
+ end
55
+ should 'create quality playlists' do
56
+ @profiles.each do |profile|
57
+ assert File.exists?(File.join(File.dirname(profile.to_hash[:dir]), File.basename(profile.to_hash[:dir]) + '.m3u8'))
58
+ end
59
+ end
60
+ should 'create group playlists' do
61
+ playlist1 = File.join('tmp', 'playlist1.m3u8')
62
+ playlist2 = File.join('tmp', 'playlist2.m3u8')
63
+ assert File.exists? playlist1
64
+ assert File.exists? playlist2
65
+ assert File.read(playlist1).include?('test11')
66
+ assert File.read(playlist1).include?('test12')
67
+ assert !File.read(playlist1).include?('test21')
68
+ assert !File.read(playlist1).include?('test22')
69
+ assert File.read(playlist2).include?('test21')
70
+ assert File.read(playlist2).include?('test22')
71
+ assert !File.read(playlist2).include?('test11')
72
+ assert !File.read(playlist2).include?('test12')
73
+ end
74
+ end
75
+
76
+ context 'with no_convert flag' do
77
+ setup do
78
+ @c = VideoConverter.new(:profile => VideoConverter::Profile.new(:file => 'test/fixtures/test.mp4', :dir => '/tmp/test', :bitrate => 300), :no_convert => true, :type => :hls, :playlist_dir => 'tmp')
79
+ @res = @c.run
80
+ end
81
+ should 'return true' do
82
+ assert @res
83
+ end
84
+ should 'create needed files' do
85
+ assert File.exists? '/tmp/test/s-00001.ts'
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'video_converter/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "video_converter"
8
+ spec.version = VideoConverter::VERSION
9
+ spec.authors = ["novikov"]
10
+ spec.email = ["novikov@inventos.ru"]
11
+ spec.description = %q{This gem allows you to convert video files using ffmpeg, mencoder and other user defined utilities to mp4, m3u8, and any other formats}
12
+ spec.summary = %q{Ffmpeg, mencoder based converter to mp4, m3u8}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "test-unit"
24
+ spec.add_development_dependency "shoulda-context"
25
+
26
+ spec.requirements << 'ffmpeg, version 1.2 or greated configured with libx264 and libfaac'
27
+ spec.requirements << 'live_segmenter to convert to hls'
28
+ end
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: video_converter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - novikov
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '1.3'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: test-unit
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: shoulda-context
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: This gem allows you to convert video files using ffmpeg, mencoder and
79
+ other user defined utilities to mp4, m3u8, and any other formats
80
+ email:
81
+ - novikov@inventos.ru
82
+ executables: []
83
+ extensions: []
84
+ extra_rdoc_files: []
85
+ files:
86
+ - .Rakefile.swp
87
+ - .gitignore
88
+ - Gemfile
89
+ - LICENSE.txt
90
+ - README.md
91
+ - Rakefile
92
+ - lib/video_converter.rb
93
+ - lib/video_converter/base.rb
94
+ - lib/video_converter/command.rb
95
+ - lib/video_converter/ffmpeg.rb
96
+ - lib/video_converter/live_segmenter.rb
97
+ - lib/video_converter/process.rb
98
+ - lib/video_converter/profile.rb
99
+ - lib/video_converter/version.rb
100
+ - test/ffmpeg_test.rb
101
+ - test/fixtures/test.mp4
102
+ - test/fixtures/test.mp4.log-0.log
103
+ - test/fixtures/test.mp4.log-0.log.mbtree
104
+ - test/test_helper.rb
105
+ - test/video_converter_test.rb
106
+ - video_converter.gemspec
107
+ homepage: ''
108
+ licenses:
109
+ - MIT
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ! '>='
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ segments:
121
+ - 0
122
+ hash: -2129030905366019713
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ none: false
125
+ requirements:
126
+ - - ! '>='
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ segments:
130
+ - 0
131
+ hash: -2129030905366019713
132
+ requirements:
133
+ - ffmpeg, version 1.2 or greated configured with libx264 and libfaac
134
+ - live_segmenter to convert to hls
135
+ rubyforge_project:
136
+ rubygems_version: 1.8.25
137
+ signing_key:
138
+ specification_version: 3
139
+ summary: Ffmpeg, mencoder based converter to mp4, m3u8
140
+ test_files:
141
+ - test/ffmpeg_test.rb
142
+ - test/fixtures/test.mp4
143
+ - test/fixtures/test.mp4.log-0.log
144
+ - test/fixtures/test.mp4.log-0.log.mbtree
145
+ - test/test_helper.rb
146
+ - test/video_converter_test.rb