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