vtools 0.0.1

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.
Files changed (47) hide show
  1. data/INSTALL +0 -0
  2. data/LICENSE +20 -0
  3. data/README.md +131 -0
  4. data/Rakefile +29 -0
  5. data/bin/vtools +22 -0
  6. data/doc/CONFIG.md +36 -0
  7. data/doc/HOOKS.md +37 -0
  8. data/doc/LIB_EXAMPLE.md +109 -0
  9. data/extconf.rb +7 -0
  10. data/lib/vtools.rb +79 -0
  11. data/lib/vtools/config.rb +91 -0
  12. data/lib/vtools/convert_options.rb +155 -0
  13. data/lib/vtools/converter.rb +98 -0
  14. data/lib/vtools/errors.rb +21 -0
  15. data/lib/vtools/handler.rb +43 -0
  16. data/lib/vtools/harvester.rb +71 -0
  17. data/lib/vtools/job.rb +48 -0
  18. data/lib/vtools/options.rb +101 -0
  19. data/lib/vtools/shared_methods.rb +131 -0
  20. data/lib/vtools/storage.rb +67 -0
  21. data/lib/vtools/thumbnailer.rb +93 -0
  22. data/lib/vtools/thumbs_options.rb +80 -0
  23. data/lib/vtools/version.rb +6 -0
  24. data/lib/vtools/version.rb~ +4 -0
  25. data/lib/vtools/video.rb +158 -0
  26. data/setup.rb +1585 -0
  27. data/spec/config_spec.rb +142 -0
  28. data/spec/convert_options_spec.rb +284 -0
  29. data/spec/converter_spec.rb +167 -0
  30. data/spec/errors_spec.rb +39 -0
  31. data/spec/fixtures/outputs/file_with_iso-8859-1.txt +35 -0
  32. data/spec/fixtures/outputs/file_with_no_audio.txt +18 -0
  33. data/spec/fixtures/outputs/file_with_non_supported_audio.txt +29 -0
  34. data/spec/fixtures/outputs/file_with_start_value.txt +19 -0
  35. data/spec/fixtures/outputs/file_with_surround_sound.txt +19 -0
  36. data/spec/handler_spec.rb +81 -0
  37. data/spec/harvester_spec.rb +189 -0
  38. data/spec/job_spec.rb +130 -0
  39. data/spec/options_spec.rb +52 -0
  40. data/spec/shared_methods_spec.rb +351 -0
  41. data/spec/spec_helper.rb +20 -0
  42. data/spec/storage_spec.rb +106 -0
  43. data/spec/thumbnailer_spec.rb +178 -0
  44. data/spec/thumbs_options_spec.rb +159 -0
  45. data/spec/video_spec.rb +274 -0
  46. data/vtools.gemspec +29 -0
  47. metadata +177 -0
@@ -0,0 +1,142 @@
1
+ require "spec_helper"
2
+ require "config"
3
+
4
+ describe VTools::CONFIG do
5
+ context "VTools::CONFIG" do
6
+
7
+ before :all do
8
+ @config = VTools::CONFIG.dup
9
+ @config[:video_set] = VTools::CONFIG[:video_set].dup
10
+ @config[:thumb_set] = VTools::CONFIG[:thumb_set].dup
11
+ end
12
+
13
+ before :each do
14
+ VTools::CONFIG.replace @config.dup
15
+ VTools::CONFIG[:video_set] = @config[:video_set].dup
16
+ VTools::CONFIG[:thumb_set] = @config[:thumb_set].dup
17
+ end
18
+
19
+ # specs
20
+ context "#load!" do
21
+
22
+ it "executes configs without merge" do
23
+ VTools::CONFIG[:config_file] = false
24
+
25
+ VTools.should_not_receive(:keys_to_sym)
26
+ VTools::CONFIG.should_not_receive(:append!)
27
+
28
+ expect { VTools::CONFIG.load! }.to_not raise_error
29
+ end
30
+
31
+ it "merges configs sucessfully" do
32
+ VTools::CONFIG[:config_file] = 'exsitent/file'
33
+ YAML.stub!(:load_file).and_return { {} } # stub file access method
34
+
35
+ VTools.should_receive(:keys_to_sym)
36
+ VTools::CONFIG.should_receive(:append!)
37
+
38
+ expect { VTools::CONFIG.load! }.to_not raise_error
39
+ end
40
+
41
+ it "breaks executing due to invalid yaml" do
42
+ VTools::CONFIG[:config_file] = 'nonexsitent/file'
43
+ expect { VTools::CONFIG.load! }.to raise_error VTools::ConfigError, /Invalid config data /
44
+ end
45
+ end
46
+
47
+ context "#append!" do
48
+
49
+ # common validator behavior
50
+ def validate_data config_hash
51
+
52
+ VTools::CONFIG.append! config_hash
53
+
54
+ # validate data
55
+ VTools::CONFIG.each do |name, content|
56
+ if config_hash.include? name
57
+ content.should == config_hash[name]
58
+ else # the rest data should remain untouched
59
+ content.should == @config[name]
60
+ end
61
+ end
62
+ end
63
+
64
+ it "merges permitted data directly" do
65
+ data = {
66
+ :ffmpeg_binary => 'binary-ffmpeg',
67
+ :thumb_binary => 'binary-ffmpeg',
68
+
69
+ :max_jobs => 'jobs',
70
+ :harvester_timer=> 'timer',
71
+ :temp_dir => 'temp',
72
+
73
+ :video_storage => 'videos',
74
+ :thumb_storage => 'thumbs',
75
+ }
76
+
77
+ validate_data data
78
+ end
79
+
80
+ it "merges permitted data as array" do
81
+ data = { :library => ['data-lib'], }
82
+
83
+ validate_data data
84
+ end
85
+
86
+ it "merges permitted data as hash" do
87
+ data = {
88
+ :video_set => {:test => [1, 2, 3]},
89
+ :thumb_set => {:test => [1, 2, 3]},
90
+ }
91
+
92
+ VTools::CONFIG.append! data
93
+
94
+ # validate data
95
+ VTools::CONFIG.each do |name, content|
96
+ if data.include?(name)
97
+ content.should include(data[name])
98
+ content[:test].should == data[name][:test]
99
+ else # the rest data should remain
100
+ content.should == @config[name]
101
+ end
102
+ end
103
+ end
104
+
105
+ it "skips denied data" do
106
+ invalid_data = {
107
+ :video_set => {:test => 3},
108
+ :thumb_set => :test,
109
+ :library => 'data-lib',
110
+ }
111
+
112
+ # save original values
113
+ VTools::CONFIG.append! invalid_data
114
+
115
+ # invalid data should not be placed & the rest data should remain the same
116
+ VTools::CONFIG.each { |key| VTools::CONFIG[key].should == @config[key] }
117
+ end
118
+
119
+ it "merges permitted & skips denied data (complex)" do
120
+
121
+ mixed_data = {
122
+ :video_set => {
123
+ :invalid => 3,
124
+ :valid => [1,2,3],
125
+ },
126
+ :thumb_set => {
127
+ :valid => [1,2,3],
128
+ },
129
+ :library => 'invalid',
130
+ }
131
+ VTools::CONFIG.append! mixed_data
132
+
133
+ VTools::CONFIG[:thumb_set].should include :valid
134
+ VTools::CONFIG[:thumb_set][:valid].should == mixed_data[:thumb_set][:valid]
135
+
136
+ VTools::CONFIG[:video_set].should_not include :valid
137
+ VTools::CONFIG[:video_set].should_not include :invalid
138
+ VTools::CONFIG[:library].should_not include 'invalid'
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,284 @@
1
+ require "spec_helper"
2
+ require "convert_options"
3
+
4
+ describe VTools::ConvertOptions do
5
+
6
+ # hooks
7
+ before :all do
8
+ @options_base = VTools::ConvertOptions.new({})
9
+ end
10
+
11
+ before :each do
12
+ @options = @options_base.dup
13
+ end
14
+
15
+ # specs
16
+ context "#[]=" do
17
+
18
+ it "places width & height" do
19
+ # place initial value
20
+ @options.merge!({:s => "720x360"})
21
+
22
+ # set new values
23
+ @options[:width] = 1024
24
+ @options[:height] = 768
25
+
26
+ @options[:width].should == 1024
27
+ @options[:height].should == 768
28
+ @options[:s].should be nil
29
+ @options[:resolution].should be nil
30
+ end
31
+
32
+ it "places s" do
33
+ @options[:s] = "720x360"
34
+
35
+ @options[:s].should == "720x360"
36
+ @options[:resolution].should == "720x360"
37
+ @options[:width].should be 720
38
+ @options[:height].should be 360
39
+ end
40
+
41
+ it "places resolution" do
42
+ # place initial value
43
+ @options.merge!({:s => "720x360"})
44
+
45
+ # set new values
46
+ @options[:resolution] = "1024x768"
47
+
48
+ @options[:s].should == "1024x768"
49
+ @options[:resolution].should == "1024x768"
50
+ end
51
+
52
+ it "places duration" do
53
+ # forward
54
+ @options[:t] = 25
55
+ @options[:t].should == 25
56
+ @options[:duration].should == 25
57
+
58
+ # reverse
59
+ @options[:duration] = 45
60
+ @options[:t].should == 45
61
+ @options[:duration].should == 45
62
+ end
63
+
64
+ it "places other" do
65
+ @options[:a] = 123
66
+ @options[:a].should == 123
67
+ end
68
+ end
69
+
70
+
71
+ context "#to_s" do
72
+
73
+ # skps ignored values
74
+ it "reates valid string representation" do
75
+
76
+ # still empty
77
+ @options.to_s.should == ""
78
+
79
+ # skips disallowed keywords
80
+ @options.merge!({
81
+ :width => 1024,
82
+ :height => 768,
83
+ :resolution => "600x400",
84
+ :extension => '.flv',
85
+ :preserve_aspect => false,
86
+ :duration => 123,
87
+ :postfix => "video",
88
+ :s => "640x480",
89
+ })
90
+ @options.to_s.should == "-s 640x480"
91
+
92
+ # validate with aspect
93
+ @options[:aspect] = 1.2
94
+ @options.to_s.should == "-s 640x480 -aspect 1.2"
95
+ end
96
+ end
97
+
98
+ context "#perform" do
99
+
100
+ it "receives recalculate" do
101
+ @options.should_receive(:recalculate).once.and_return{ |str| str.split("x").map(&:to_i) }
102
+
103
+ values = {
104
+ :duration => 123,
105
+ :resolution => "600x360",
106
+ :preserve_aspect => true,
107
+ }
108
+
109
+ @options.method(:perform).call values
110
+
111
+ values.delete(:preserve_aspect)
112
+ @options.method(:perform).call values
113
+ end
114
+
115
+ it "converts data valid" do
116
+ @options.should_not_receive(:recalculate)
117
+
118
+ values = {
119
+ :duration => 123,
120
+ :resolution => "640x480",
121
+ :width => 600,
122
+ :height => 400,
123
+ :aspect => 1.3,
124
+ }
125
+
126
+ # major priority
127
+ @options.method(:perform).call values
128
+ values[:t].should == 123
129
+ values[:s].should == "640x480"
130
+
131
+ # middle priority
132
+ values.delete(:resolution)
133
+ @options.method(:perform).call values
134
+ values[:s].should == "600x400"
135
+
136
+ # minor priority
137
+ values[:s] = "1024x768"
138
+ values.delete(:preserve_aspect)
139
+ values.delete(:width)
140
+ values.delete(:height)
141
+ @options.method(:perform).call values
142
+ values[:s].should == "1024x768"
143
+ end
144
+
145
+ it "deletes invalid dimmensions definition" do
146
+ @options.should_not_receive(:recalculate)
147
+
148
+ values = { :width => 600 }
149
+ @options[:s] = "600x400"
150
+ @options.method(:perform).call values
151
+ values[:s].should be nil
152
+
153
+ values = { :height => 600 }
154
+ @options[:s] = "600x400"
155
+ @options.method(:perform).call values
156
+ values[:s].should be nil
157
+ end
158
+ end
159
+
160
+ context "#parse!" do
161
+
162
+ # set predefined data
163
+ VTools::CONFIG[:video_set][:x264_180p] = [
164
+ 'libx264', 'libfaac', '240x180', '96k', '64k',
165
+ 22050, 2, 'mp4', '_180', 'normal'
166
+ ]
167
+
168
+ let :conf_hash do
169
+ { :s => "240x180", :vcodec => "libx264", :acodec => "libfaac",
170
+ :vb => "96k", :ab => "64k", :ar => 22050, :ac => 2,
171
+ :extension => "mp4", :postfix => "_180", :vpre => "normal" }
172
+ end
173
+
174
+ def make_stubs
175
+ @options.stub(:keys_to_sym).and_return{ |hsh| hsh }
176
+ @options.stub(:perform).and_return{ |hsh| hsh }
177
+ end
178
+
179
+ it "parses custom hash" do
180
+ make_stubs
181
+
182
+ @options.method(:parse!).call({:s => "640x480"}).should == {:s => "640x480"}
183
+ @options.delete(:s)
184
+ @options.method(:parse!).call({:width => 640, :height => 480}).should == {:width => 640, :height => 480}
185
+ end
186
+
187
+ it "parses predefined set" do
188
+ make_stubs
189
+
190
+ @options.method(:parse!).call("x264_180p").should include conf_hash
191
+ end
192
+
193
+ it "parses complex set" do
194
+ make_stubs
195
+
196
+ complex = conf_hash.dup
197
+ complex[:s] = "1024x768"
198
+
199
+ @options.method(:parse!).call({:set => "x264_180p", :s => "1024x768"}).should include complex
200
+ end
201
+
202
+ it "raises error" do
203
+ make_stubs
204
+
205
+ expect {@options.method(:parse!).call(123) }.to raise_error VTools::ConfigError
206
+ expect {@options.method(:parse!).call("nonexistent") }.to raise_error VTools::ConfigError
207
+ end
208
+ end
209
+
210
+ context "#recalculate" do
211
+
212
+ it "no rescale" do
213
+ @options[:aspect] = nil
214
+ width, height = @options.method(:recalculate).call "1024x768"
215
+ width.should == 1024
216
+ height.should == 768
217
+ end
218
+
219
+ it "rescale by aspect > 1 (wide video)" do
220
+ @options[:aspect] = 6.to_f / 5.to_f # original video aspect is 6:5
221
+ { # set max expected video dimm => rescale
222
+ "1024x768" => [922, 768], # we create 4:3
223
+ "1024x576" => [692, 576], # we create 16:9
224
+ }.each do |accept, result|
225
+ width, height = @options.method(:recalculate).call accept
226
+ width.should == result[0]
227
+ height.should == result[1]
228
+ end
229
+
230
+ @options[:aspect] = 17.to_f / 8.to_f # original video aspect is 17:8
231
+ { # set max expected video dimm => rescale_result
232
+ "1024x768" => [1024, 482], # we create 4:3
233
+ "600x436" => [600, 282], # we create 11:8
234
+ }.each do |accept, result|
235
+ width, height = @options.method(:recalculate).call accept
236
+ width.should == result[0]
237
+ height.should == result[1]
238
+ end
239
+ end
240
+
241
+ it "rescale by aspect < 1 (tall video)" do
242
+ @options[:aspect] = 5.to_f / 6.to_f # original video aspect is 5:6
243
+ { # set max expected video dimm => rescale
244
+ "1024x768" => [640, 768], # we create 4:3
245
+ "1024x576" => [480, 576], # we create 16:9
246
+ }.each do |accept, result|
247
+ width, height = @options.method(:recalculate).call accept
248
+ width.should == result[0]
249
+ height.should == result[1]
250
+ end
251
+
252
+ @options[:aspect] = 8.to_f / 17.to_f # original video aspect is 8:17
253
+ { # set max expected video dimm => rescale_result
254
+ "1024x768" => [362, 768], # we create 4:3
255
+ "600x436" => [206, 436], # we create 11:8
256
+ }.each do |accept, result|
257
+ width, height = @options.method(:recalculate).call accept
258
+ width.should == result[0]
259
+ height.should == result[1]
260
+ end
261
+ end
262
+
263
+ it "rescale by aspect = 1 (square video)" do
264
+ @options[:aspect] = 1.to_f / 1.to_f # original video aspect is 1:1
265
+ { # set max expected video dimm => rescale
266
+ "1024x768" => [768, 768], # we create 4:3
267
+ "1024x576" => [576, 576], # we create 16:9
268
+ }.each do |accept, result|
269
+ width, height = @options.method(:recalculate).call accept
270
+ width.should == result[0]
271
+ height.should == result[1]
272
+ end
273
+ end
274
+ end
275
+
276
+ context "#initialize" do
277
+ it "valid calls methods" do
278
+ config = { :width => 1024, :height => 768 }
279
+
280
+ VTools::ConvertOptions.any_instance.should_receive(:parse!).with(config)
281
+ options = VTools::ConvertOptions.new config
282
+ end
283
+ end
284
+ end
@@ -0,0 +1,167 @@
1
+ require "spec_helper"
2
+ require "converter"
3
+
4
+ describe VTools::Converter do
5
+
6
+ # hooks
7
+ before do
8
+ @converter = VTools::Converter.new nil
9
+ end
10
+
11
+ let(:video) { double nil }
12
+
13
+ # specs
14
+ context "#run" do
15
+
16
+ let(:output_array) { [] }
17
+ let(:ffmpeg_7) { {10.02 => "time=10.02", 15.34 => "time=15.34", 20.45 => "time=20.45"} }
18
+ let(:ffmpeg_8) { {10.02 => "time=00:00:10.02", 15.34 => "time=00:00:15.34", 20.45 => "time=00:00:20.45", 61.05 => "time=00:01:01.05"} }
19
+ let(:ffmpeg_no_time) { {0.0 => "broken pipe"} }
20
+
21
+ # hooks
22
+ before do
23
+ @output_file = nil
24
+ @options = {}
25
+ end
26
+
27
+
28
+ def prepare_converter values_set
29
+ # prepare output lines
30
+ output_array.push *values_set.values
31
+ # output iterator
32
+ (io = double(nil)).stub!(:each) { |*args, blk| output_array.each { |line| blk.yield(line) } }
33
+ Open3.stub!(:popen3).and_return{ |*args, block| block.yield(nil, nil, io) }
34
+
35
+ @options.should_receive(:to_s).and_return { "test.options" }
36
+
37
+ video.stub(:convert_options){ @options }
38
+ video.stub(:name) {"video/name"}
39
+ video.stub(:path) {"video/path"}
40
+ video.stub(:duration) { values_set.keys.last }
41
+
42
+ @converter.instance_variable_set(:@video, video)
43
+
44
+ VTools::CONFIG[:ffmpeg_binary] = "tested.ffmpeg"
45
+ @output_file = "/#{video.name}#{@options[:postfix]}.#{@options[:extension]}"
46
+ # "#{CONFIG[:ffmpeg_binary]} -y -i '#{@video.path}' #{@options} '#{@output_file}'"
47
+ exec_com = "tested.ffmpeg -y -i 'video/path' test.options '#{@output_file}'"
48
+
49
+ VTools::Handler.should_receive(:exec).with(:before_convert, video, exec_com)
50
+ VTools.should_receive(:fix_encoding).exactly(values_set.size).times
51
+ @converter.should_receive(:generate_path)
52
+ end
53
+
54
+ it "converts video for ffmpeg 0.7" do
55
+ prepare_converter ffmpeg_7
56
+
57
+ ffmpeg_7.each do |sec, time_str|
58
+ VTools::Handler.should_receive(:exec).with(:in_convert, video, sec/ffmpeg_7.keys.last)
59
+ end
60
+
61
+ VTools::Handler.should_receive(:exec).with(:convert_success, video, @output_file)
62
+ @converter.should_receive(:encoding_invalid?) { false }
63
+ @converter.should_receive(:encoded)
64
+
65
+ @converter.run
66
+ end
67
+
68
+ it "converts video for ffmpeg 0.8" do
69
+ prepare_converter ffmpeg_8
70
+
71
+ ffmpeg_8.each do |sec, time_str|
72
+ VTools::Handler.should_receive(:exec).with(:in_convert, video, sec/ffmpeg_8.keys.last)
73
+ end
74
+
75
+ VTools::Handler.should_receive(:exec).with(:convert_success, video, @output_file)
76
+ @converter.should_receive(:encoding_invalid?) { false }
77
+ @converter.should_receive(:encoded)
78
+
79
+ @converter.run
80
+ end
81
+
82
+ context "fails encoding" do
83
+
84
+ it "--no time received" do
85
+ prepare_converter ffmpeg_no_time
86
+ expect { @converter.run }.to raise_error VTools::ProcessError, "broken pipe"
87
+ end
88
+
89
+ it "--result file is invalid" do
90
+ VTools::Handler.stub(:exec).and_return nil
91
+ prepare_converter ffmpeg_7
92
+ @converter.should_receive(:encoding_invalid?) { "test.fail" }
93
+ expect { @converter.run }.to raise_error VTools::ProcessError, "test.fail"
94
+ end
95
+ end
96
+ end
97
+
98
+ context "#encoding_invalid?" do
99
+
100
+ before do
101
+ VTools::CONFIG[:validate_duration] = nil
102
+ end
103
+
104
+ def prepare_converter
105
+ File.stub!(:exists?).and_return { true }
106
+ video.stub(:duration) { 200.2 } # set duration in sec
107
+ @converter.stub_chain(:encoded, :valid?).and_return { true }
108
+ @converter.instance_variable_set(:@video, video)
109
+ end
110
+
111
+ context "encoding is valid" do
112
+
113
+ it "no manual duration set" do
114
+ prepare_converter
115
+ @converter.instance_variable_set(:@options, {}) # no manual duration set
116
+ @converter.stub_chain(:encoded, :duration).and_return { 200.2 }
117
+ VTools::CONFIG[:validate_duration] = true
118
+
119
+ @converter.encoding_invalid?.should == false
120
+ end
121
+
122
+ it "duration set manually" do
123
+ prepare_converter
124
+ @converter.instance_variable_set(:@options, { :duration => 115.3 }) # manual duration set
125
+ @converter.stub_chain(:encoded, :duration).and_return { 115.3 }
126
+ VTools::CONFIG[:validate_duration] = true
127
+
128
+ @converter.encoding_invalid?.should == false
129
+ end
130
+
131
+ it "without duration test" do
132
+ prepare_converter
133
+ @converter.encoding_invalid?.should == false
134
+ end
135
+ end
136
+
137
+ context "encoding is invalid" do
138
+
139
+ it "no encoded file" do
140
+ File.stub!(:exists?).and_return { false }
141
+ @converter.encoding_invalid?.should == "No output file created"
142
+ end
143
+
144
+ it "encoded media is invalid video instance" do
145
+ File.stub!(:exists?).and_return { true }
146
+ @converter.stub_chain(:encoded, :valid?).and_return { false }
147
+ @converter.encoding_invalid?.should == "Encoded file is invalid"
148
+ end
149
+
150
+ it "invalid duration auto" do
151
+ prepare_converter
152
+ VTools::CONFIG[:validate_duration] = true
153
+ @converter.instance_variable_set(:@options, { :duration => 115.3 }) # no manual duration set
154
+ @converter.stub_chain(:encoded, :duration).and_return { 220.2 } # original 220
155
+ @converter.encoding_invalid?.should =~ /Encoded file duration is invalid \(original\/specified:/
156
+ end
157
+
158
+ it "invalid duration manual" do
159
+ prepare_converter
160
+ VTools::CONFIG[:validate_duration] = true
161
+ @converter.instance_variable_set(:@options, {}) # no manual duration set
162
+ @converter.stub_chain(:encoded, :duration).and_return { 320.2 } # original 220
163
+ @converter.encoding_invalid?.should =~ /Encoded file duration is invalid \(original\/specified:/
164
+ end
165
+ end
166
+ end
167
+ end