uencode 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.autotest ADDED
File without changes
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .rake_tasks~
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in uencode.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,8 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :notification => true do
5
+ watch(%r{^spec/.+_spec\.rb})
6
+ watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+ end
data/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # UEncode
2
+
3
+ A simple Ruby gem to consume the [uEncode](http://www.uencode.com) API.
4
+
5
+ ## Synopsis
6
+
7
+ ``` ruby
8
+ require 'uencode'
9
+
10
+ # In Rails you can put this inside an initializer
11
+ UEncode.configure do |c|
12
+ c.customer_key = "your_uencode_api_key"
13
+ end
14
+
15
+ job = UEncode::Job.new :source => "http://your_source_video_url/foo.avi", :userdata => "This is a simple test"
16
+
17
+ job.configure_video_output { |c| c.destination = "http://your_destination_url/transcoded.mp4"; c.container = "mpeg4" }
18
+
19
+ video = UEncode::Medium.new
20
+
21
+ video.configure_video { |c| c.bitrate = 300000; c.codec = "h264"}
22
+
23
+ video.configure_audio do |c|
24
+ c.bitrate = 64000
25
+ c.codec = "aac"
26
+ c.samplerate = 44100
27
+ c.channels = 1
28
+ end
29
+
30
+ job << video
31
+
32
+ capture = UEncode::CaptureOutput.new :destination => "http://whatever.com/foo.zip", :rate => "every 30s"
33
+ job.add_capture capture
34
+
35
+ request = UEncode::Request.new job
36
+ puts job.to_xml
37
+ response = request.send
38
+
39
+ puts response.code # => 'Ok'
40
+ puts response.message # => 'Your job was created successfully'
41
+ puts response.jobid # => 1234567
42
+ puts response.userdata # => 'This is a simple test'
43
+ ```
44
+
45
+ ## Accepted parameters
46
+
47
+ Currently all the uEncode API parameters are supported (or so I think :)
48
+
49
+ Currently the gem does not validate parameters' values, so pay attention at the API docs.
50
+
51
+ You can see the whole list of accepted parameters in the [uEncode API documentation](http://www.uencode.com/api/300#response_codes).
52
+
53
+ For the UEncode classes that map to each complex API parameters (like Crop, Size, FrameRate, etc) take a look at the spec file at /spec/elements_spec.rb or read the docs.
54
+
55
+ ## Running the specs
56
+
57
+ * bundle install
58
+ * rspec spec
59
+
60
+
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+
3
+ require 'bundler'
4
+ Bundler::GemHelper.install_tasks
data/lib/uencode.rb ADDED
@@ -0,0 +1,35 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'singleton'
5
+ require 'nokogiri'
6
+ require 'httparty'
7
+
8
+ module UEncode
9
+ class << self
10
+ attr_accessor :customer_key
11
+
12
+ def configure
13
+ yield self
14
+ end
15
+ end
16
+
17
+ module AttrSetting
18
+ def set_attributes(options)
19
+ self.class.const_get("ATTRIBUTES").each { |attr| instance_variable_set(:"@#{attr}", options[attr]) }
20
+ end
21
+
22
+ def initialize(options)
23
+ set_attributes options
24
+ end
25
+
26
+ def self.included(klass)
27
+ attr = klass.const_get "ATTRIBUTES"
28
+ klass.send(:attr_reader, *attr)
29
+ end
30
+ end
31
+ end
32
+
33
+ require "uencode/elements"
34
+ require "uencode/request"
35
+ require "uencode/response"
@@ -0,0 +1,233 @@
1
+ module UEncode
2
+ class Crop
3
+ ATTRIBUTES = [:width, :height, :x, :y]
4
+
5
+ def to_xml
6
+ %Q{
7
+ <crop>
8
+ <width>#{width}</width>
9
+ <height>#{height}</height>
10
+ <x>#{x}</x>
11
+ <y>#{y}</y>
12
+ </crop>
13
+ }
14
+ end
15
+ end
16
+
17
+ class Size
18
+ ATTRIBUTES = [:width, :height]
19
+
20
+ def to_xml
21
+ %Q{
22
+ <size>
23
+ <width>#{width}</width>
24
+ <height>#{height}</heignt>
25
+ </size>
26
+ }
27
+ end
28
+ end
29
+
30
+ module RateElement
31
+ def to_xml
32
+ %Q{
33
+ <#{root_name}>
34
+ <numerator>#{numerator}</numerator>
35
+ <denominator>#{denominator}</denominator>
36
+ </#{root_name}>
37
+ }
38
+ end
39
+ end
40
+
41
+ class FrameRate
42
+ include RateElement
43
+ ATTRIBUTES = [:numerator, :denominator]
44
+
45
+ private
46
+ def root_name; "framerate"; end
47
+ end
48
+
49
+ class Par < FrameRate
50
+ include RateElement
51
+ ATTRIBUTES = [:numerator, :denominator]
52
+
53
+ private
54
+ def root_name; "par"; end
55
+ end
56
+
57
+ class CaptureOutput
58
+ ATTRIBUTES = [:destination, :rate, :stretch, :crop, :size]
59
+
60
+ def initialize(options)
61
+ super
62
+ @stretch = false if @stretch.nil?
63
+ end
64
+
65
+ def to_xml
66
+ %Q{
67
+ <output>
68
+ <capture>
69
+ <rate>#{rate}</rate>
70
+ <destination>#{destination}</destination>
71
+ #{@crop ? @crop.to_xml : ""}
72
+ #{@size ? @size.to_xml : ""}
73
+ </capture>
74
+ <//output>
75
+ }
76
+ end
77
+ end
78
+
79
+ class VideoOutput
80
+ ATTRIBUTES = [:destination, :container]
81
+
82
+ include Enumerable
83
+
84
+ # a list of Medium
85
+ attr_reader :items
86
+ attr_writer :destination, :container
87
+
88
+ def initialize(options)
89
+ @items = []
90
+ super
91
+ end
92
+
93
+ def each
94
+ @items.each { |item| yield item }
95
+ end
96
+
97
+ def to_xml
98
+ %Q{
99
+ <output>
100
+ <video>
101
+ <destination>#{destination}</destination>
102
+ <container>#{container}</container>
103
+ <media>
104
+ #{@items.inject("") { |s, item| s << item.to_xml }}
105
+ </media>
106
+ </video>
107
+ </output>
108
+ }
109
+ end
110
+ end
111
+
112
+ # Medium is a single video to transcode
113
+ class Medium
114
+ def initialize
115
+ @video_config = VideoConfig.new
116
+ @audio_config = AudioConfig.new
117
+ end
118
+
119
+ def configure_video
120
+ yield @video_config
121
+ end
122
+
123
+ def configure_audio
124
+ yield @audio_config
125
+ end
126
+
127
+ def video
128
+ @video_config
129
+ end
130
+
131
+ def audio
132
+ @audio_config
133
+ end
134
+
135
+ def to_xml
136
+ %Q{
137
+ <medium>
138
+ <video>
139
+ <bitrate>#{video.bitrate}</birate>
140
+ <codec>#{video.codec}</birate>
141
+ #{!video.cbr.nil? ? '<cbr>' + video.cbr.to_s + '</cbr>' : ""}
142
+ #{video.crop ? video.crop.to_xml : ""}
143
+ #{video.deinterlace.nil? ? "" : '<deinterlace>' + video.deinterlace.to_s + '</deinterlace>'}
144
+ #{video.framerate ? video.framerate.to_xml : ""}
145
+ #{video.height.nil? ? "" : '<height>' + video.height.to_s + '</height>'}
146
+ #{video.keyframe_interval.nil? ? "" : '<keyframe_interval>' + video.keyframe_interval.to_s + '</keyframe_interval>'}
147
+ #{video.maxbitrate.nil? ? "" : '<maxbitrate>' + video.maxbitrate.to_s + '</maxbitrate>'}
148
+ #{video.par ? video.par.to_xml : ""}
149
+ #{video.profile.nil? ? "" : '<profile>' + video.profile + '</profile>'}
150
+ #{video.passes.nil? ? "" : '<passes>' + video.passes.to_s + '</passes>'}
151
+ #{[nil, false].include?(video.stretch) ? "" : '<stretch>' + video.stretch.to_s + '</stretch>'}
152
+ #{video.width.nil? ? "" : '<width>' + video.width.to_s + '</width>'}
153
+ </video>
154
+ <audio>
155
+ #{audio.codec.nil? ? "" : '<codec>' + audio.codec + '</codec>'}
156
+ #{audio.bitrate.nil? ? "" : '<bitrate>' + audio.bitrate.to_s + '</bitrate>'}
157
+ #{audio.channels.nil? ? "" : '<channels>' + audio.channels.to_s + '</channels>'}
158
+ #{audio.samplerate.nil? ? "" : '<samplerate>' + audio.samplerate.to_s + '</samplerate>'}
159
+ </audio>
160
+ </medium>
161
+ }
162
+ end
163
+ end
164
+
165
+ # The video configs for each Medium
166
+ class VideoConfig
167
+ attr_accessor :bitrate, :codec, :cbr, :crop, :deinterlace, :framerate, :height, :keyframe_interval,
168
+ :maxbitrate, :par, :profile, :passes, :stretch, :width
169
+
170
+ def initialize
171
+ @cbr = false
172
+ @deinterlace = false
173
+ @profile = "main"
174
+ @passes = 1
175
+ @stretch = false
176
+ end
177
+ end
178
+
179
+ # The audio configs for each Medium
180
+ class AudioConfig
181
+ attr_accessor :codec, :bitrate, :channels, :samplerate
182
+ end
183
+
184
+ class Job
185
+ ATTRIBUTES = [:source, :userdata, :notify]
186
+
187
+ include Enumerable
188
+
189
+ def initialize(options)
190
+ @video_output = VideoOutput.new options[:video_output] || {}
191
+ @captures = []
192
+ super
193
+ end
194
+
195
+ def configure_video_output
196
+ yield @video_output
197
+ end
198
+
199
+ def items
200
+ @video_output.items
201
+ end
202
+
203
+ def <<(item)
204
+ @video_output.items << item
205
+ end
206
+
207
+ def add_capture(capture)
208
+ @captures << capture
209
+ end
210
+
211
+ def each(&block)
212
+ @video_output.each &block
213
+ end
214
+
215
+ def to_xml
216
+ xml = %Q{
217
+ <job>
218
+ <customerkey>#{UEncode.customer_key}</customerkey>
219
+ <source>#{source}</source>
220
+ #{userdata.nil? ? "" : '<userdata>' + userdata + '</userdata>'}
221
+ #{notify.nil? ? "" : '<notify>' + notify + '</notify>'}
222
+ <outputs>
223
+ #{@video_output.to_xml}
224
+ #{@captures.inject("") { |s, cap| s << cap.to_xml }}
225
+ </outputs>
226
+ </job>
227
+ }
228
+ Nokogiri::XML(xml).to_xml
229
+ end
230
+ end
231
+
232
+ [Size, FrameRate, Crop, VideoOutput, CaptureOutput, Job].each { |klass| klass.send :include, AttrSetting }
233
+ end
@@ -0,0 +1,33 @@
1
+ module UEncode
2
+ class Request
3
+ include HTTParty
4
+
5
+ base_uri "https://www.uencode.com"
6
+ format :xml
7
+
8
+ def initialize(job)
9
+ @job = job
10
+ end
11
+
12
+ def send
13
+ response = self.class.put "/jobs?version=300", :body => @job.to_xml
14
+ parse_response response
15
+ end
16
+
17
+ private
18
+ def parse_response(response_xml)
19
+ doc = Nokogiri::XML response_xml.body
20
+ code = doc.xpath("//code").text
21
+ message = doc.xpath("//message").text
22
+ jobid = doc.xpath("//jobid").text
23
+ userdata = doc.xpath("//userdata").text
24
+
25
+ Response.new(
26
+ :code => code,
27
+ :message => message,
28
+ :jobid => jobid,
29
+ :userdata => userdata
30
+ )
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ module UEncode
2
+ class Response
3
+ class BadRequestError < StandardError; end
4
+ class InvalidKeyError < StandardError; end
5
+ class NotActiveError < StandardError; end
6
+ class ServerError < StandardError; end
7
+ class UnknownError < StandardError; end
8
+
9
+ ATTRIBUTES = [:code, :message, :jobid, :userdata]
10
+
11
+ include AttrSetting
12
+
13
+ def initialize(options)
14
+ check_response_code options[:code], options[:message]
15
+ super
16
+ end
17
+
18
+ private
19
+ def check_response_code(code, message)
20
+ return if code == 'Ok'
21
+ case code
22
+ when 'BadRequest'; raise BadRequestError, message
23
+ when 'InvalidKey'; raise InvalidKeyError, message
24
+ when 'NotActive'; raise NotActiveError, message
25
+ when 'ServerError'; raise ServerError, message
26
+ else raise UnknownError, "#{code}: #{message}"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module UEncode
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,478 @@
1
+ require 'spec_helper'
2
+
3
+ describe UEncode::Medium do
4
+ let(:medium) { UEncode::Medium.new }
5
+
6
+ before :each do
7
+ medium.configure_video do |c|
8
+ c.bitrate = 10000
9
+ c.codec = "mp4"
10
+ c.cbr = false
11
+ c.crop = nil
12
+ c.deinterlace = true
13
+ c.framerate = UEncode::FrameRate.new :numerator => 1000, :denominator => 1001
14
+ c.height = 500
15
+ c.keyframe_interval = 0
16
+ c.maxbitrate = 10000
17
+ c.par = nil
18
+ c.profile = "baseline"
19
+ c.passes = 1
20
+ c.stretch = false
21
+ c.width = 400
22
+ end
23
+
24
+ medium.configure_audio do |c|
25
+ c.codec = "aac"
26
+ c.bitrate = 15000
27
+ c.channels = 2
28
+ c.samplerate = 10000
29
+ end
30
+ end
31
+
32
+ describe "#configure_video" do
33
+ let(:medium) { UEncode::Medium.new }
34
+
35
+ it { medium.video.bitrate.should == 10000 }
36
+ it { medium.video.codec.should == "mp4" }
37
+ it { medium.video.cbr.should == false }
38
+ it { medium.video.crop.should be_nil }
39
+ it { medium.video.deinterlace.should == true }
40
+ it { medium.video.framerate.numerator.should == 1000 }
41
+ it { medium.video.height.should == 500 }
42
+ it { medium.video.keyframe_interval.should == 0 }
43
+ it { medium.video.maxbitrate.should == 10000 }
44
+ it { medium.video.par.should be_nil }
45
+ it { medium.video.profile.should == "baseline" }
46
+ it { medium.video.passes.should == 1 }
47
+ it { medium.video.stretch.should == false }
48
+ it { medium.video.width.should == 400 }
49
+ end
50
+
51
+ describe "#configure_audio" do
52
+ let(:medium) { UEncode::Medium.new }
53
+
54
+ it { medium.audio.codec.should == "aac" }
55
+ it { medium.audio.bitrate.should == 15000 }
56
+ it { medium.audio.channels.should == 2 }
57
+ it { medium.audio.samplerate.should == 10000 }
58
+ end
59
+
60
+ context "video config default values" do
61
+ let(:medium_without_configuration) { described_class.new }
62
+
63
+ it { medium_without_configuration.video.cbr.should == false }
64
+ it { medium_without_configuration.video.deinterlace.should == false }
65
+ it { medium_without_configuration.video.profile.should == "main" }
66
+ it { medium_without_configuration.video.passes.should == 1 }
67
+ it { medium_without_configuration.video.stretch.should == false }
68
+ end
69
+
70
+ describe "#to_xml" do
71
+ let(:xml) { Nokogiri::XML medium.to_xml }
72
+
73
+ before :each do
74
+ medium.configure_video do |c|
75
+ c.crop = UEncode::Crop.new :width => 125, :height => 150, :x => 100, :y => 200
76
+ c.par = UEncode::Par.new :numerator => 10, :denominator => 11
77
+ end
78
+ end
79
+
80
+ it "has a root element named 'media'" do
81
+ xml.root.name.should == 'medium'
82
+ end
83
+
84
+ it "has the correct video configs" do
85
+ config = xml.xpath("//video")
86
+ config.xpath("//video/bitrate").text.should == "10000"
87
+ config.xpath("//video/codec").text.should == "mp4"
88
+ config.xpath("//cbr").text.should == "false"
89
+ config.xpath("//crop/width").text.should == "125"
90
+ config.xpath("//crop/height").text.should == "150"
91
+ config.xpath("//crop/x").text.should == "100"
92
+ config.xpath("//crop/y").text.should == "200"
93
+ config.xpath("//deinterlace").text.should == "true"
94
+ config.xpath("//framerate/numerator").text.should == "1000"
95
+ config.xpath("//framerate/denominator").text.should == "1001"
96
+ config.xpath("//video/height").text.should == "500"
97
+ config.xpath("//keyframe_interval").text.should == "0"
98
+ config.xpath("//maxbitrate").text.should == "10000"
99
+ config.xpath("//par/numerator").text.should == "10"
100
+ config.xpath("//par/denominator").text.should == "11"
101
+ config.xpath("//profile").text.should == "baseline"
102
+ config.xpath("//passes").text.should == "1"
103
+ config.xpath("//video/width").text.should == "400"
104
+ end
105
+
106
+ it "does not include the cbr video config when it's null" do
107
+ medium.configure_video { |c| c.cbr = nil }
108
+ Nokogiri::XML(medium.to_xml).xpath("//video/cbr").should be_empty
109
+ end
110
+
111
+ it "does not include the crop video config when it's null" do
112
+ medium.configure_video { |c| c.crop = nil }
113
+ Nokogiri::XML(medium.to_xml).xpath("//video/crop").should be_empty
114
+ end
115
+
116
+ it "does not include the deinterlace video config when it's null" do
117
+ medium.configure_video { |c| c.deinterlace = nil }
118
+ Nokogiri::XML(medium.to_xml).xpath("//video/deinterlace").should be_empty
119
+ end
120
+
121
+ it "does not include the framerate video config when it's null" do
122
+ medium.configure_video { |c| c.framerate = nil }
123
+ Nokogiri::XML(medium.to_xml).xpath("//video/framerate").should be_empty
124
+ end
125
+
126
+ it "does not include the height video config when it's null" do
127
+ medium.configure_video { |c| c.height = nil }
128
+ Nokogiri::XML(medium.to_xml).xpath("//video/height").should be_empty
129
+ end
130
+
131
+ it "does not include the keyframe_interval video config when it's null" do
132
+ medium.configure_video { |c| c.keyframe_interval = nil }
133
+ Nokogiri::XML(medium.to_xml).xpath("//video/keyframe_interval").should be_empty
134
+ end
135
+
136
+ it "does not include the maxbitrate video config when it's null" do
137
+ medium.configure_video { |c| c.maxbitrate = nil }
138
+ Nokogiri::XML(medium.to_xml).xpath("//video/maxbitrate").should be_empty
139
+ end
140
+
141
+ it "does not include the par video config when it's null" do
142
+ medium.configure_video { |c| c.par = nil }
143
+ Nokogiri::XML(medium.to_xml).xpath("//video/par").should be_empty
144
+ end
145
+
146
+ it "does not include the profile video config when it's null" do
147
+ medium.configure_video { |c| c.profile = nil }
148
+ Nokogiri::XML(medium.to_xml).xpath("//video/profile").should be_empty
149
+ end
150
+
151
+ it "does not include the passes video config when it's null" do
152
+ medium.configure_video { |c| c.passes = nil }
153
+ Nokogiri::XML(medium.to_xml).xpath("//video/passes").should be_empty
154
+ end
155
+
156
+ it "does not include the stretch video config when it's null" do
157
+ medium.configure_video { |c| c.stretch = nil }
158
+ Nokogiri::XML(medium.to_xml).xpath("//video/stretch").should be_empty
159
+ end
160
+
161
+ it "does not include the width video config when it's null" do
162
+ medium.configure_video { |c| c.width = nil }
163
+ Nokogiri::XML(medium.to_xml).xpath("//video/width").should be_empty
164
+ end
165
+
166
+ it "does not include the stretch video config when it's false" do
167
+ medium.configure_video { |c| c.stretch = false }
168
+ Nokogiri::XML(medium.to_xml) .xpath("//video/stretch").should be_empty
169
+ end
170
+
171
+ it "has the correct audio configs" do
172
+ xml.xpath("//audio/codec").text.should == "aac"
173
+ xml.xpath("//audio/bitrate").text.should == "15000"
174
+ xml.xpath("//audio/channels").text.should == "2"
175
+ xml.xpath("//audio/samplerate").text.should == "10000"
176
+ end
177
+
178
+ it "does not include the bitrate audio config when it's null" do
179
+ medium.configure_audio { |c| c.bitrate = nil }
180
+ Nokogiri::XML(medium.to_xml).xpath("//audio/bitrate").should be_empty
181
+ end
182
+
183
+ it "does not include the channels audio config when it's null" do
184
+ medium.configure_audio { |c| c.channels = nil }
185
+ Nokogiri::XML(medium.to_xml).xpath("//audio/channels").should be_empty
186
+ end
187
+
188
+ it "does not include the samplerate audio config when it's null" do
189
+ medium.configure_audio { |c| c.samplerate = nil }
190
+ Nokogiri::XML(medium.to_xml).xpath("//audio/samplerate").should be_empty
191
+ end
192
+ end
193
+ end
194
+
195
+ describe UEncode::VideoOutput do
196
+ subject { described_class.new :destination => "http://foobar.com/bla.avi", :container => "mpeg4" }
197
+
198
+ its(:destination) { should == "http://foobar.com/bla.avi" }
199
+ its(:container) { should == "mpeg4" }
200
+
201
+
202
+ describe "#to_xml" do
203
+ let(:video_output) { described_class.new :destination => "http://foo.com/bar.mp4", :container => "mpeg4" }
204
+ let(:xml) { Nokogiri::XML video_output.to_xml }
205
+
206
+
207
+ it "has a root element named 'output'" do
208
+ xml.root.name.should == 'output'
209
+ end
210
+
211
+ it "has the correct destination value" do
212
+ xml.xpath("//output/video/destination").text.should == "http://foo.com/bar.mp4"
213
+ end
214
+
215
+ it "has the correct container value" do
216
+ xml.xpath("//output/video/container").text.should == "mpeg4"
217
+ end
218
+ end
219
+ end
220
+
221
+ describe UEncode::CaptureOutput do
222
+ subject { described_class.new({
223
+ :destination => "http://whatever.com/foo.jpg",
224
+ :rate => "at 20s",
225
+ :stretch => false
226
+ }) }
227
+
228
+ its(:destination) { should == "http://whatever.com/foo.jpg" }
229
+ its(:rate) { should == "at 20s" }
230
+ its(:stretch) { should == false }
231
+
232
+ context "default values" do
233
+ let(:capture) { described_class.new :rate => "every 10s" }
234
+
235
+ it { capture.stretch.should == false }
236
+ end
237
+
238
+ describe "#to_xml" do
239
+ let(:crop) { UEncode::Crop.new :width => 125, :height => 140, :x => 100, :y => 200 }
240
+ let(:size) { UEncode::Size.new :width => 400, :height => 500 }
241
+ let(:xml) {
242
+ Nokogiri::XML described_class.new(
243
+ :destination => "http://foo.com/bla.mp4",
244
+ :stretch => false,
245
+ :rate => 'every 10s',
246
+ :crop => crop,
247
+ :size => size
248
+ ).to_xml
249
+ }
250
+
251
+ it "has a root element named capture" do
252
+ xml.root.name.should == 'output'
253
+ end
254
+
255
+ it "has the correct value for the rate attribute" do
256
+ xml.xpath("//output/capture/rate").text.should == "every 10s"
257
+ end
258
+
259
+ it "has the correct value for the destination attribute" do
260
+ xml.xpath("//output/capture/destination").text.should == "http://foo.com/bla.mp4"
261
+ end
262
+
263
+ it "has the correct value for the crop attribute" do
264
+ xml.xpath("//output/capture/crop/width").text.should == "125"
265
+ xml.xpath("//output/capture/crop/height").text.should == "140"
266
+ end
267
+
268
+ it "has the correct value for the size attribute" do
269
+ xml.xpath("//output/capture/size/width").text.should == "400"
270
+ xml.xpath("//output/capture/size/height").text.should == "500"
271
+ end
272
+ end
273
+ end
274
+
275
+ describe UEncode::Crop do
276
+ subject { described_class.new :width => 100, :height => 200, :x => 100, :y => 150 }
277
+
278
+ its(:width) { should == 100 }
279
+ its(:height) { should == 200 }
280
+ its(:x) { should == 100 }
281
+ its(:y) { should = 150 }
282
+
283
+ describe "#to_xml" do
284
+ let(:xml) { Nokogiri::XML described_class.new(:width => 100, :height => 200, :x => 100, :y => 150).to_xml }
285
+
286
+ it "has a root element named 'crop'" do
287
+ xml.root.name.should == 'crop'
288
+ end
289
+
290
+ it "has the correct value for the width attribute" do
291
+ xml.xpath("//width").text.should == "100"
292
+ end
293
+
294
+ it "has the correct value for the height attribute" do
295
+ xml.xpath("//height").text.should == "200"
296
+ end
297
+
298
+ it "has the correct value for the x attribute" do
299
+ xml.xpath("//x").text.should == "100"
300
+ end
301
+
302
+ it "has the correct value for the y attribute" do
303
+ xml.xpath("//y").text.should == "150"
304
+ end
305
+ end
306
+ end
307
+
308
+ shared_examples_for "an element that represents a rate number" do
309
+ subject { described_class.new :numerator => numerator, :denominator => denominator }
310
+
311
+ its(:numerator) { should == numerator }
312
+ its(:denominator) { should == denominator }
313
+
314
+ describe "#to_xml" do
315
+ let(:xml) { Nokogiri::XML described_class.new(:numerator => numerator, :denominator => denominator).to_xml }
316
+
317
+ it "has a root element named '#{name}'" do
318
+ xml.root.name.should == name
319
+ end
320
+
321
+ it "has the correct value for the numerator attribute" do
322
+ xml.xpath("//numerator").text.should == numerator.to_s
323
+ end
324
+
325
+ it "has the correct value for the denominator attribute" do
326
+ xml.xpath("//denominator").text.should == denominator.to_s
327
+ end
328
+ end
329
+ end
330
+
331
+ describe UEncode::FrameRate do
332
+ let(:name) { "framerate" }
333
+ let(:numerator) { 1000 }
334
+ let(:denominator) { 1001 }
335
+
336
+ it_should_behave_like "an element that represents a rate number"
337
+ end
338
+
339
+ describe UEncode::Par do
340
+ let(:name) { "par" }
341
+ let(:numerator) { 10 }
342
+ let(:denominator) { 11 }
343
+
344
+ it_should_behave_like "an element that represents a rate number"
345
+ end
346
+
347
+ describe UEncode::Size do
348
+ subject { described_class.new :width => 100, :height => 200 }
349
+
350
+ its(:height) { should == 200 }
351
+ its(:width) { should == 100 }
352
+
353
+ describe "#to_xml" do
354
+ let(:xml) { Nokogiri::XML described_class.new(:width => 200, :height => 250).to_xml }
355
+
356
+ it "has a root element named 'size'" do
357
+ xml.root.name.should == 'size'
358
+ end
359
+
360
+ it "has the correct value for the width attribute" do
361
+ xml.xpath("//width").text.should == "200"
362
+ end
363
+
364
+ it "has the correct value for the height attribute" do
365
+ xml.xpath("//height").text.should == "250"
366
+ end
367
+ end
368
+ end
369
+
370
+ describe UEncode::Job do
371
+ subject { described_class.new :source => "http://foo.com/bar.avi", :userdata => "some text", :notify => "http://my_url.com" }
372
+
373
+ its(:source) { should == "http://foo.com/bar.avi" }
374
+ its(:userdata) { should == "some text" }
375
+ its(:notify) { should == "http://my_url.com" }
376
+ its(:items) { should == [] }
377
+
378
+ describe "#<<" do
379
+ it "adds new elements to items" do
380
+ subject << "foo"
381
+ subject.items.should == ["foo"]
382
+ end
383
+ end
384
+
385
+ it "is enumerable" do
386
+ subject << "foo"
387
+ subject << "bar"
388
+ subject.map { |item| item }.should == ["foo", "bar"]
389
+ end
390
+
391
+ describe "#to_xml" do
392
+ let(:job) { UEncode::Job.new({
393
+ :customerkey => "0123456789",
394
+ :source => "http://whatever.com/foo.avi",
395
+ :userdata => "some text",
396
+ :notify => "http://notify.me/meh"
397
+ })}
398
+
399
+ let(:xml) { Nokogiri::XML job.to_xml }
400
+
401
+ before :each do
402
+ video1 = UEncode::Medium.new
403
+ video1.configure_video do |c|
404
+ c.bitrate = 1000
405
+ c.codec = 'mp4'
406
+ end
407
+ video1.configure_audio do |c|
408
+ c.codec = 'aac'
409
+ end
410
+ video2 = UEncode::Medium.new
411
+ video2.configure_video do |c|
412
+ c.bitrate = 1500
413
+ c.codec = 'mpeg2'
414
+ end
415
+ video2.configure_audio do |c|
416
+ c.codec = 'passthru'
417
+ end
418
+ job << video1
419
+ job << video2
420
+
421
+ job.configure_video_output do |c|
422
+ c.destination = "http://whatever.com/foo1.mp4"
423
+ c.container = "mpeg4"
424
+ end
425
+
426
+ capture1 = UEncode::CaptureOutput.new :destination => "http://whatever.com/foo.zip", :rate => "every 30s"
427
+ job.add_capture capture1
428
+ capture2 = UEncode::CaptureOutput.new :destination => "http://whatever.com/bar.zip", :rate => "every 10s"
429
+ job.add_capture capture2
430
+ end
431
+
432
+ it "has a root element named 'job'" do
433
+ xml.root.name.should == 'job'
434
+ end
435
+
436
+ it "has the correct customer key value" do
437
+ xml.xpath("//job/customerkey").text.should == "1q2w3e4r5t"
438
+ end
439
+
440
+ it "has the correct source attribute" do
441
+ xml.xpath("//job/source").text.should == "http://whatever.com/foo.avi"
442
+ end
443
+
444
+ it "has the correct user data value" do
445
+ xml.xpath("//job/userdata").text.should == 'some text'
446
+ end
447
+
448
+ it "has the correct notify value" do
449
+ xml.xpath("//job/notify").text.should == "http://notify.me/meh"
450
+ end
451
+
452
+ it "does not include the userdata attribute when it's null" do
453
+ job.instance_variable_set :@userdata, nil
454
+ xml.xpath("//job/userdata").should be_empty
455
+ end
456
+
457
+ it "does not include the notify attribute when it's null" do
458
+ job.instance_variable_set :@notify, nil
459
+ xml.xpath("//job/notify").should be_empty
460
+ end
461
+
462
+ it "contains the correct content to represent each video output item" do
463
+ xml.xpath("//job/outputs/output/video/media/medium").length.should == 2
464
+ end
465
+
466
+ it "has the correct video output destination" do
467
+ xml.xpath("//job/outputs/output/video/destination").text.should == "http://whatever.com/foo1.mp4"
468
+ end
469
+
470
+ it "has the correct video output container" do
471
+ xml.xpath("//job/outputs/output/video/container").text.should == "mpeg4"
472
+ end
473
+
474
+ it "contains the correct content to represent the video captures" do
475
+ xml.xpath("//job/outputs/output/capture").length.should == 2
476
+ end
477
+ end
478
+ end
@@ -0,0 +1,87 @@
1
+ ---
2
+ - !ruby/struct:VCR::HTTPInteraction
3
+ request: !ruby/struct:VCR::Request
4
+ method: :put
5
+ uri: https://www.uencode.com:443/jobs?version=300
6
+ body: |
7
+ <?xml version="1.0"?>
8
+ <job>
9
+ <customerkey>1q2w3e4r5t</customerkey>
10
+ <source>http://dailydigital-files.s3.amazonaws.com/staging/3/iphone.mp4</source>
11
+ <userdata>This is a simple test</userdata>
12
+
13
+ <outputs>
14
+
15
+ <output>
16
+ <video>
17
+ <destination>http://dailydigital-files.s3.amazonaws.com/staging/3/iphone_transcoded.mp4</destination>
18
+ <container>mpeg4</container>
19
+ <media>
20
+
21
+ <medium>
22
+ <video>
23
+ <bitrate>300000</bitrate>
24
+ <codec>h264</codec>
25
+ <cbr>false</cbr>
26
+
27
+ <deinterlace>false</deinterlace>
28
+
29
+
30
+
31
+
32
+
33
+ <profile>main</profile>
34
+ <passes>1</passes>
35
+
36
+
37
+ </video>
38
+ <audio>
39
+ <codec>aac</codec>
40
+ <bitrate>64000</bitrate>
41
+ <channels>1</channels>
42
+ <samplerate>44100</samplerate>
43
+ </audio>
44
+ </medium>
45
+
46
+ </media>
47
+ </video>
48
+ </output>
49
+
50
+
51
+ </outputs>
52
+ </job>
53
+
54
+ headers:
55
+ response: !ruby/struct:VCR::Response
56
+ status: !ruby/struct:VCR::ResponseStatus
57
+ code: 200
58
+ message: OK
59
+ headers:
60
+ date:
61
+ - Wed, 18 May 2011 21:25:41 GMT
62
+ server:
63
+ - Apache/2.2.16 (Ubuntu)
64
+ etag:
65
+ - "\"1c1e90e26e3f60eeb74014a6aeb5ac22\""
66
+ cache-control:
67
+ - max-age=0, private, must-revalidate
68
+ x-ua-compatible:
69
+ - IE=Edge,chrome=1
70
+ set-cookie:
71
+ - _uencode_api_session=BAh7BiIPc2Vzc2lvbl9pZCIlOWM4ZWM4ZGUxMzNiNTNiNWZiOWMwMDA4ODkzMmY3ZWI%3D--697ce08be3ca2a9f6347474fe7fe5476333e9408; path=/; HttpOnly
72
+ x-runtime:
73
+ - "1.007000"
74
+ content-length:
75
+ - "202"
76
+ content-type:
77
+ - application/xml;charset=utf-8
78
+ body: |
79
+ <?xml version="1.0" encoding="UTF-8"?>
80
+ <response>
81
+ <code>Ok</code>
82
+ <jobid>6068</jobid>
83
+ <message>You job was created successfully.</message>
84
+ <userdata>This is a simple test</userdata>
85
+ </response>
86
+
87
+ http_version: "1.1"
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe UEncode::Request do
4
+ context "being created" do
5
+ let(:job) { UEncode::Job.new :source => "http://whatever.com/foo/avi" }
6
+ let(:request) { UEncode::Request.new job }
7
+
8
+ it "initializes the job attribute" do
9
+ request.instance_variable_get(:@job).should == job
10
+ end
11
+ end
12
+
13
+ describe "#send" do
14
+ let(:job) { UEncode::Job.new :source => "http://dailydigital-files.s3.amazonaws.com/staging/3/iphone.mp4", :userdata => "This is a simple test" }
15
+ let(:request) { UEncode::Request.new job }
16
+
17
+ before :each do
18
+ job.configure_video_output do |c|
19
+ c.destination = "http://dailydigital-files.s3.amazonaws.com/staging/3/iphone_transcoded.mp4"
20
+ c.container = "mpeg4"
21
+ end
22
+ video1 = UEncode::Medium.new
23
+ video1.configure_video { |c| c.bitrate = 300000; c.codec = "h264"}
24
+ video1.configure_audio do |c|
25
+ c.bitrate = 64000
26
+ c.codec = "aac"
27
+ c.samplerate = 44100
28
+ c.channels = 1
29
+ end
30
+ job << video1
31
+ end
32
+
33
+ around :each do |example|
34
+ VCR.use_cassette "job_with_one_video_and_no_capture", &example
35
+ end
36
+
37
+ it "returns a response containing the jobid" do
38
+ request.send.jobid.should =~ /\A\d+\z/
39
+ end
40
+
41
+ it "returns a response containing a code" do
42
+ request.send.code.should == 'Ok'
43
+ end
44
+
45
+ it "returns a response containing the previously sent user data" do
46
+ request.send.userdata.should == "This is a simple test"
47
+ end
48
+
49
+ it "returns a response containing a message" do
50
+ request.send.message.should == "You job was created successfully."
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ describe UEncode::Response do
4
+ context "errors" do
5
+ let(:response) { UEncode::Response.new :code => code, :message => "whatever" }
6
+
7
+ context "when initialized with a 'BadRequest' code" do
8
+ let(:code) { 'BadRequest' }
9
+
10
+ it "raises a UEncode::Response::BadRequestError error containing the error message" do
11
+ expect { response }.to raise_error(UEncode::Response::BadRequestError, "whatever")
12
+ end
13
+ end
14
+
15
+ context "when initialized with a 'InvalidKey' code" do
16
+ let(:code) { 'InvalidKey' }
17
+
18
+ it "raises a UEncode::Response::InvalidKeyError containing the error message" do
19
+ expect { response }.to raise_error(UEncode::Response::InvalidKeyError, "whatever")
20
+ end
21
+ end
22
+
23
+ context "when initialized with a 'NotActive' code" do
24
+ let(:code) { 'NotActive' }
25
+
26
+ it "raises a UEncode::Response::NotActiveError containing the error message" do
27
+ expect { response }.to raise_error(UEncode::Response::NotActiveError, "whatever")
28
+ end
29
+ end
30
+
31
+ context "when initialized with a 'ServerError' code" do
32
+ let(:code) { 'ServerError' }
33
+
34
+ it "raises a UEncode::Response::ServerError containing the error message" do
35
+ expect { response }.to raise_error(UEncode::Response::ServerError, "whatever")
36
+ end
37
+ end
38
+
39
+ context "when initialized with an unknown code" do
40
+ let(:code) { 'Ffffuuuuu' }
41
+
42
+ it "raises a UEncode::Response::UnknownError containing the error code and the error message" do
43
+ expect { response }.to raise_error(UEncode::Response::UnknownError, "Ffffuuuuu: whatever")
44
+ end
45
+ end
46
+ end
47
+
48
+ end
@@ -0,0 +1,17 @@
1
+ require 'uencode'
2
+ require 'rspec'
3
+ require 'rspec/autorun'
4
+ require "vcr"
5
+
6
+
7
+ VCR.config do |c|
8
+ library_dir = File.join(File.dirname(__FILE__), 'fixtures/')
9
+ c.cassette_library_dir = library_dir
10
+ c.stub_with :webmock
11
+ c.allow_http_connections_when_no_cassette = false
12
+ c.ignore_localhost = true
13
+ end
14
+
15
+ UEncode.configure do |c|
16
+ c.customer_key = "1q2w3e4r5t"
17
+ end
@@ -0,0 +1,14 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'spec_helper'
5
+
6
+ describe UEncode do
7
+ it "has a customer_key configuration parameter" do
8
+ UEncode.configure do |c|
9
+ c.customer_key = "1234567890"
10
+ end
11
+ UEncode.customer_key.should == "1234567890"
12
+ end
13
+ end
14
+
data/uencode.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "uencode/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "uencode"
7
+ s.version = UEncode::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Cássio Marques"]
10
+ s.email = ["cassiommc@gmail.com"]
11
+ s.homepage = "http://github.com/cassiomarques/uencode"
12
+ s.summary = %q{Simple UEncode API client written in Ruby}
13
+ s.description = %q{UEncode API client}
14
+
15
+ s.rubyforge_project = "uencode"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_development_dependency "rspec", "2.6.0"
23
+ s.add_development_dependency "vcr", "1.9.0"
24
+ s.add_development_dependency "guard"
25
+ s.add_development_dependency "guard-rspec"
26
+ s.add_development_dependency "growl"
27
+ s.add_development_dependency "rb-fsevent"
28
+ s.add_development_dependency "webmock"
29
+
30
+ s.add_dependency "nokogiri", "1.4.4"
31
+ s.add_dependency "httparty"
32
+ end
metadata ADDED
@@ -0,0 +1,177 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: uencode
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - "C\xC3\xA1ssio Marques"
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-05-18 00:00:00 -03:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rspec
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - "="
23
+ - !ruby/object:Gem::Version
24
+ version: 2.6.0
25
+ type: :development
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: vcr
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - "="
34
+ - !ruby/object:Gem::Version
35
+ version: 1.9.0
36
+ type: :development
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: guard
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: guard-rspec
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ type: :development
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
61
+ name: growl
62
+ prerelease: false
63
+ requirement: &id005 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ type: :development
70
+ version_requirements: *id005
71
+ - !ruby/object:Gem::Dependency
72
+ name: rb-fsevent
73
+ prerelease: false
74
+ requirement: &id006 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ type: :development
81
+ version_requirements: *id006
82
+ - !ruby/object:Gem::Dependency
83
+ name: webmock
84
+ prerelease: false
85
+ requirement: &id007 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: "0"
91
+ type: :development
92
+ version_requirements: *id007
93
+ - !ruby/object:Gem::Dependency
94
+ name: nokogiri
95
+ prerelease: false
96
+ requirement: &id008 !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - "="
100
+ - !ruby/object:Gem::Version
101
+ version: 1.4.4
102
+ type: :runtime
103
+ version_requirements: *id008
104
+ - !ruby/object:Gem::Dependency
105
+ name: httparty
106
+ prerelease: false
107
+ requirement: &id009 !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: "0"
113
+ type: :runtime
114
+ version_requirements: *id009
115
+ description: UEncode API client
116
+ email:
117
+ - cassiommc@gmail.com
118
+ executables: []
119
+
120
+ extensions: []
121
+
122
+ extra_rdoc_files: []
123
+
124
+ files:
125
+ - .autotest
126
+ - .gitignore
127
+ - Gemfile
128
+ - Guardfile
129
+ - README.md
130
+ - Rakefile
131
+ - lib/uencode.rb
132
+ - lib/uencode/elements.rb
133
+ - lib/uencode/request.rb
134
+ - lib/uencode/response.rb
135
+ - lib/uencode/version.rb
136
+ - spec/elements_spec.rb
137
+ - spec/fixtures/job_with_one_video_and_no_capture.yml
138
+ - spec/request_spec.rb
139
+ - spec/response_spec.rb
140
+ - spec/spec_helper.rb
141
+ - spec/uencode_spec.rb
142
+ - uencode.gemspec
143
+ has_rdoc: true
144
+ homepage: http://github.com/cassiomarques/uencode
145
+ licenses: []
146
+
147
+ post_install_message:
148
+ rdoc_options: []
149
+
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: "0"
158
+ required_rubygems_version: !ruby/object:Gem::Requirement
159
+ none: false
160
+ requirements:
161
+ - - ">="
162
+ - !ruby/object:Gem::Version
163
+ version: "0"
164
+ requirements: []
165
+
166
+ rubyforge_project: uencode
167
+ rubygems_version: 1.6.2
168
+ signing_key:
169
+ specification_version: 3
170
+ summary: Simple UEncode API client written in Ruby
171
+ test_files:
172
+ - spec/elements_spec.rb
173
+ - spec/fixtures/job_with_one_video_and_no_capture.yml
174
+ - spec/request_spec.rb
175
+ - spec/response_spec.rb
176
+ - spec/spec_helper.rb
177
+ - spec/uencode_spec.rb