flixcloud-flix_cloud-gem 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Zencoder
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,104 @@
1
+ FlixCloud Gem
2
+ =============
3
+
4
+ The gem for interacting with the API on [FlixCloud](http://flixcloud.com).
5
+
6
+ See [http://flixcloud.com/api](http://flixcloud.com/api) for more details on the API.
7
+
8
+ Examples
9
+ ========
10
+
11
+ Rails
12
+ -----
13
+
14
+ If you're using rails, you can add the following to your environment.rb:
15
+
16
+ config.gem 'flixcloud-flix_cloud-gem', :lib => 'flix_cloud', :source => 'http://gems.github.com'
17
+ config.gem 'sevenwire-http_client', :lib => 'http_client', :source => 'http://gems.github.com'
18
+ config.gem 'crack'
19
+
20
+ Then you can run:
21
+
22
+ rake gems:install
23
+ rake gems:unpack
24
+
25
+ Ruby or Other Frameworks
26
+ ------------------------
27
+
28
+ Install the gems (sudo may be necessary):
29
+
30
+ gem install flixcloud-flix_cloud-gem --source http://gems.github.com
31
+ gem install sevenwire-http_client --source http://gems.github.com
32
+ gem install crack
33
+
34
+ Then in your code you can just require the FlixCloud gem:
35
+
36
+ require 'rubygems'
37
+ require 'flix_cloud'
38
+
39
+ Creating Jobs
40
+ -------------
41
+
42
+ You can create jobs using the Job class:
43
+
44
+ job = FlixCloud::Job.new(:api_key => 'your-api-key-here',
45
+ :recipe_id => 1,
46
+ :input_url => 'http://url/to/your/input/file',
47
+ :output_url => 'ftp://url/to/your/output/file',
48
+ :output_user => 'username-for-ftp-here',
49
+ :output_password => 'password-for-ftp-here',
50
+ :watermark_url => 's3://url/to/your/watermark/file')
51
+ job.valid? # true or false
52
+ job.save # true or false
53
+ job.id # returns the id of the saved job, or nil if it failed to save
54
+ job.initialized_at # returns the time the job was created, or nil if it failed to save
55
+
56
+ Job is modeled after ActiveRecord, so create, create!, save, save!, etc all work.
57
+
58
+ See [this](http://flixcloud.com/api#sending_jobs_to_flix_cloud) for more information.
59
+
60
+ Job Notifications
61
+ -----------------
62
+
63
+ When you receive a notification from FlixCloud, you can process it using the Notification class:
64
+
65
+ job = FlixCloud::Notification.new(notification_xml_or_hash)
66
+ job.successful? # true if the state is 'successful_job', false if not
67
+ job.failed? # true if the state is 'failed_job', false if not
68
+ job.cancelled? # true if the state is 'cancelled_job', false if not
69
+ job.id # the id of the job that finished
70
+ job.finished_job_at # the time the job finished at
71
+ job.initialized_job_at # the time the job was created at
72
+ job.recipe_name # the name of the recipe used to process the job
73
+ job.recipe_id # the id of the recipe used to process the job
74
+ job.state # the state of the finished job. 'successful_job', 'failed_job', or 'cancelled_job'
75
+ job.error_message # the error message given for the job if it failed to process
76
+ job.input_media_file.url # url of the input file
77
+ job.input_media_file.width # width of the input file in pixels
78
+ job.input_media_file.height # height of the input file in pixels
79
+ job.input_media_file.size # size of the input file in bytes
80
+ job.input_media_file.duration # duration of the input file in milliseconds
81
+ job.input_media_file.cost # cost of the input file in millicents (1/1000th of a cent)
82
+ job.output_media_file.url # url of the output file
83
+ job.output_media_file.width # width of the output file in pixels
84
+ job.output_media_file.height # height of the output file in pixels
85
+ job.output_media_file.size # size of the output file in bytes
86
+ job.output_media_file.duration # duration of the output file in milliseconds
87
+ job.output_media_file.cost # cost of the output file in millicents (1/1000th of a cent)
88
+ job.watermark_file.url # url of the watermark file
89
+ job.watermark_file.size # size of the watermark file in bytes
90
+ job.watermark_file.cost # cost of the watermark file in millicents (1/1000th of a cent)
91
+
92
+ See [this](http://flixcloud.com/api#job_notifications) for more information.
93
+
94
+ Note
95
+ ----
96
+
97
+ Creating jobs sends HTTP requests to FlixCloud, which may take some time. It's
98
+ best to do this asynchronously in your application.
99
+
100
+
101
+ COPYRIGHT
102
+ =========
103
+
104
+ Copyright (c) 2009 Zencoder. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,59 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "flix_cloud-gem"
8
+ gem.summary = %{Gem for integrating with http://flixcloud.com}
9
+ gem.email = "nate@sevenwire.com"
10
+ gem.homepage = "http://github.com/zencoder/flix_cloud-gem"
11
+ gem.authors = ["Nathan Sutton"]
12
+ gem.add_dependency('builder', '>= 2.1.2')
13
+ gem.add_dependency('crack', '>= 0.1.1')
14
+ gem.add_dependency('sevenwire-http_client', '>= 0.1.0')
15
+
16
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
+ end
18
+ rescue LoadError
19
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
20
+ end
21
+
22
+ require 'rake/testtask'
23
+ Rake::TestTask.new(:test) do |test|
24
+ test.libs << 'lib' << 'test'
25
+ test.pattern = 'test/**/*_test.rb'
26
+ test.verbose = true
27
+ end
28
+
29
+ begin
30
+ require 'rcov/rcovtask'
31
+ Rcov::RcovTask.new do |test|
32
+ test.libs << 'test'
33
+ test.pattern = 'test/**/*_test.rb'
34
+ test.verbose = true
35
+ end
36
+ rescue LoadError
37
+ task :rcov do
38
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
39
+ end
40
+ end
41
+
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ if File.exist?('VERSION.yml')
48
+ config = YAML.load(File.read('VERSION.yml'))
49
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
50
+ else
51
+ version = ""
52
+ end
53
+
54
+ rdoc.rdoc_dir = 'rdoc'
55
+ rdoc.title = "flix_cloud-gem #{version}"
56
+ rdoc.rdoc_files.include('README*')
57
+ rdoc.rdoc_files.include('lib/**/*.rb')
58
+ end
59
+
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 5
4
+ :patch: 3
@@ -0,0 +1,6 @@
1
+ class FlixCloud::Error < StandardError; end
2
+ class FlixCloud::SaveError < FlixCloud::Error; end
3
+ class FlixCloud::CreateError < FlixCloud::Error; end
4
+ class FlixCloud::ServerBrokeConnection < FlixCloud::Error; end
5
+ class FlixCloud::RequestTimeout < FlixCloud::Error; end
6
+ class FlixCloud::ConnectionRefused < FlixCloud::Error; end
@@ -0,0 +1,22 @@
1
+ module FlixCloud
2
+ module Extensions
3
+ # Both methods ripped directly out of rails
4
+ module Hash
5
+ def deep_merge(other_hash)
6
+ self.merge(other_hash) do |key, oldval, newval|
7
+ oldval = oldval.to_hash if oldval.respond_to?(:to_hash)
8
+ newval = newval.to_hash if newval.respond_to?(:to_hash)
9
+ oldval.class.to_s == 'Hash' && newval.class.to_s == 'Hash' ? oldval.deep_merge(newval) : newval
10
+ end
11
+ end
12
+
13
+ # Returns a new hash with +self+ and +other_hash+ merged recursively.
14
+ # Modifies the receiver in place.
15
+ def deep_merge!(other_hash)
16
+ replace(deep_merge(other_hash))
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ Hash.send(:include, FlixCloud::Extensions::Hash) unless Hash.instance_methods.include?('deep_merge')
@@ -0,0 +1,21 @@
1
+ class FlixCloud::File < FlixCloud::Record
2
+
3
+ attr_accessor :url, :size, :width, :height, :duration, :cost
4
+
5
+ record_column :parameters, 'Parameters'
6
+
7
+ def valid?
8
+ self.errors = []
9
+
10
+ unless url
11
+ self.errors << "url is required"
12
+ end
13
+
14
+ if parameters && !parameters.valid?
15
+ self.errors << {:parameters => parameters.errors}
16
+ end
17
+
18
+ errors.empty?
19
+ end
20
+
21
+ end
@@ -0,0 +1,33 @@
1
+ class FlixCloud::FileLocations < FlixCloud::Record
2
+
3
+ record_column :input, 'File'
4
+ record_column :output, 'File'
5
+ record_column :watermark, 'File'
6
+
7
+ def valid?
8
+ self.errors = []
9
+
10
+ if input
11
+ unless input.valid?
12
+ self.errors << {:input => input.errors}
13
+ end
14
+ else
15
+ self.errors << "input is required"
16
+ end
17
+
18
+ if output
19
+ unless output.valid?
20
+ self.errors << {:output => output.errors}
21
+ end
22
+ else
23
+ self.errors << "output is required"
24
+ end
25
+
26
+ if watermark && !watermark.valid?
27
+ self.errors << {:watermark => watermark.errors}
28
+ end
29
+
30
+ errors.empty?
31
+ end
32
+
33
+ end
@@ -0,0 +1,150 @@
1
+ class FlixCloud::Job < FlixCloud::Record
2
+
3
+ attr_accessor :id, :initialized_at, :api_key, :recipe_id, :recipe_name, :response
4
+
5
+ record_column :file_locations, 'FileLocations'
6
+
7
+ def initialize(attrs={})
8
+ super
9
+ self.shortcut_attributes = attrs
10
+ end
11
+
12
+ def valid?
13
+ self.errors = []
14
+
15
+ if file_locations
16
+ unless file_locations.valid?
17
+ self.errors << {:file_locations => file_locations.errors}
18
+ end
19
+ else
20
+ self.errors << "file_locations is required"
21
+ end
22
+
23
+ if recipe_id || recipe_name
24
+ if recipe_id && recipe_name
25
+ self.errors << "recipe_id and recipe_name cannot both be used"
26
+ end
27
+ else
28
+ self.errors << "recipe_id or recipe_name is required"
29
+ end
30
+
31
+ unless api_key
32
+ self.errors << "api_key is required"
33
+ end
34
+
35
+ errors.empty?
36
+ end
37
+
38
+ def save
39
+ return false unless valid?
40
+
41
+ self.response = post('jobs', to_xml)
42
+
43
+ if response.success?
44
+ self.id = response.body_as_hash['job']['id']
45
+ self.initialized_at = response.body_as_hash['job']['initialized_job_at']
46
+ else
47
+ self.errors = response.errors
48
+ end
49
+
50
+ response.success?
51
+ end
52
+
53
+ def save!
54
+ raise FlixCloud::SaveError unless save
55
+ true
56
+ end
57
+
58
+ def self.create(attrs={})
59
+ job = new(attrs)
60
+ job.save
61
+ job
62
+ end
63
+
64
+ def self.create!(attrs={})
65
+ job = create(attrs)
66
+ raise FlixCloud::CreateError unless job.id
67
+ job
68
+ end
69
+
70
+ def to_xml
71
+ xml = Builder::XmlMarkup.new
72
+
73
+ xml.instruct! :xml, :version => "1.0", :encoding => "UTF-8"
74
+
75
+ xml.tag!("api-request") do
76
+ xml.tag!("api-key", api_key)
77
+
78
+ if recipe_name
79
+ xml.tag!("recipe-name", recipe_name)
80
+ else
81
+ xml.tag!("recipe-id", recipe_id)
82
+ end
83
+
84
+ if file_locations
85
+ xml.tag!("file-locations") do
86
+ if file_locations.input
87
+ xml.input do
88
+ xml.url(file_locations.input.url)
89
+ if file_locations.input.parameters
90
+ xml.parameters do
91
+ xml.user(file_locations.input.parameters.user)
92
+ xml.password(file_locations.input.parameters.password)
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ if file_locations.output
99
+ xml.output do
100
+ xml.url(file_locations.output.url)
101
+ if file_locations.output.parameters
102
+ xml.parameters do
103
+ xml.user(file_locations.output.parameters.user)
104
+ xml.password(file_locations.output.parameters.password)
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ if file_locations.watermark
111
+ xml.watermark do
112
+ xml.url(file_locations.watermark.url)
113
+ if file_locations.watermark.parameters
114
+ xml.parameters do
115
+ xml.user(file_locations.watermark.parameters.user)
116
+ xml.password(file_locations.watermark.parameters.password)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ xml.target!
126
+ end
127
+
128
+
129
+ protected
130
+
131
+ def shortcut_attributes=(attrs)
132
+ translated_attributes = {}
133
+
134
+ attrs.each do |key, value|
135
+ if match = key.to_s.match(/^(input|output|watermark)_(url|user|password)$/)
136
+ file_type = match[1].to_sym
137
+ parameter_type = match[2].to_sym
138
+
139
+ if parameter_type == :url
140
+ translated_attributes.deep_merge!(:file_locations => { file_type => { :url => value}})
141
+ else
142
+ translated_attributes.deep_merge!(:file_locations => { file_type => { :parameters => { parameter_type => value}}})
143
+ end
144
+ end
145
+ end
146
+
147
+ self.attributes = translated_attributes unless translated_attributes.empty?
148
+ end
149
+
150
+ end
@@ -0,0 +1,32 @@
1
+ class FlixCloud::Notification < FlixCloud::Record
2
+ attr_accessor :xml, :id, :finished_job_at, :initialized_job_at,
3
+ :recipe_name, :recipe_id, :state, :error_message
4
+
5
+ record_column :input_media_file, 'File'
6
+ record_column :output_media_file, 'File'
7
+ record_column :watermark_file, 'File'
8
+
9
+ def initialize(attrs={})
10
+ if attrs.is_a?(String)
11
+ self.xml = attrs
12
+ attrs = Crack::XML.parse(attrs)
13
+ end
14
+
15
+ attrs = attrs['job'] if attrs['job']
16
+
17
+ super(attrs)
18
+ end
19
+
20
+ def successful?
21
+ state == 'successful_job'
22
+ end
23
+
24
+ def failed?
25
+ state == 'failed_job'
26
+ end
27
+
28
+ def cancelled?
29
+ state == 'cancelled_job'
30
+ end
31
+
32
+ end
@@ -0,0 +1,19 @@
1
+ class FlixCloud::Parameters < FlixCloud::File
2
+
3
+ attr_accessor :user, :password
4
+
5
+ def valid?
6
+ self.errors = []
7
+
8
+ unless user
9
+ self.errors << "user is required"
10
+ end
11
+
12
+ unless password
13
+ self.errors << "password is required"
14
+ end
15
+
16
+ errors.empty?
17
+ end
18
+
19
+ end
@@ -0,0 +1,46 @@
1
+ class FlixCloud::Record
2
+
3
+ attr_accessor :errors
4
+
5
+ def initialize(attrs={})
6
+ self.errors = []
7
+ self.attributes = attrs
8
+ end
9
+
10
+ def attributes=(attrs)
11
+ attrs.each do |key, value|
12
+ send("#{key}=", value) if respond_to?("#{key}=")
13
+ end
14
+ end
15
+
16
+ def self.record_column(attribute, klass)
17
+ eval %{
18
+ attr_reader :#{attribute}
19
+
20
+ def #{attribute}=(value)
21
+ if @#{attribute}
22
+ @#{attribute}.attributes = value
23
+ else
24
+ @#{attribute} = FlixCloud::#{klass}.new(value)
25
+ end
26
+ end
27
+ }
28
+ end
29
+
30
+
31
+ protected
32
+
33
+ def post(path, body)
34
+ begin
35
+ FlixCloud::Response.new(HttpClient::Resource.new("https://flixcloud.com/#{path}",
36
+ :verify_ssl => OpenSSL::SSL::VERIFY_PEER).post(body, :content_type => 'application/xml', :accept => 'application/xml'))
37
+ rescue HttpClient::ServerBrokeConnection
38
+ raise FlixCloud::ServerBrokeConnection, $!.message
39
+ rescue HttpClient::RequestTimeout
40
+ raise FlixCloud::RequestTimeout, $!.message
41
+ rescue HttpClient::ConnectionRefused
42
+ raise FlixCloud::ConnectionRefused, $!.message
43
+ end
44
+ end
45
+
46
+ end
@@ -0,0 +1,26 @@
1
+ class FlixCloud::Response
2
+
3
+ attr_accessor :code, :body, :errors, :body_as_hash
4
+
5
+ def initialize(response)
6
+ self.code = response.code
7
+ self.body = response.to_s
8
+ self.body_as_hash = Crack::XML.parse(response.to_s)
9
+ self.errors = []
10
+ process_response_xml
11
+ end
12
+
13
+ def success?
14
+ 201 == code.to_i
15
+ end
16
+
17
+
18
+ protected
19
+
20
+ def process_response_xml
21
+ if body_as_hash['errors'] && body_as_hash['errors'].is_a?(Hash) && body_as_hash['errors']['error']
22
+ self.errors = Array(body_as_hash['errors']['error'])
23
+ end
24
+ end
25
+
26
+ end
data/lib/flix_cloud.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'builder'
2
+ require 'http_client'
3
+ require 'crack/xml'
4
+
5
+ module FlixCloud; end
6
+
7
+ %w(record job file_locations file parameters response extensions/hash
8
+ exceptions notification).each do |file|
9
+ require File.dirname(__FILE__) + "/flix_cloud/#{file}"
10
+ end
@@ -0,0 +1,45 @@
1
+ require 'test_helper'
2
+
3
+ class FlixCloud::FileLocationsTest < Test::Unit::TestCase
4
+
5
+ context "When validationg a file locations object with no attributes set" do
6
+ setup do
7
+ @file_locations = FlixCloud::FileLocations.new
8
+ @file_locations.valid?
9
+ end
10
+
11
+ should "require input" do
12
+ assert_match /input is required/, @file_locations.errors.to_s
13
+ end
14
+
15
+ should "require output" do
16
+ assert_match /output is required/, @file_locations.errors.to_s
17
+ end
18
+ end
19
+
20
+ context "When validating a file locations object with input, output, and watermark objects that are invalid" do
21
+ setup do
22
+ @file_locations = FlixCloud::FileLocations.new(:input => {}, :output => {}, :watermark => {})
23
+ @file_locations.valid?
24
+ end
25
+
26
+ should "inherit the input object's errors" do
27
+ assert @file_locations.errors.any?{|error|
28
+ error.is_a?(Hash) && error[:input] && !error[:input].empty?
29
+ }, "Did not inherit input object's errors"
30
+ end
31
+
32
+ should "inherit the output object's errors" do
33
+ assert @file_locations.errors.any?{|error|
34
+ error.is_a?(Hash) && error[:output] && !error[:output].empty?
35
+ }, "Did not inherit output object's errors"
36
+ end
37
+
38
+ should "inherit the watermark object's errors" do
39
+ assert @file_locations.errors.any?{|error|
40
+ error.is_a?(Hash) && error[:watermark] && !error[:watermark].empty?
41
+ }, "Did not inherit watermark object's errors"
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,29 @@
1
+ require 'test_helper'
2
+
3
+ class FlixCloud::FileTest < Test::Unit::TestCase
4
+
5
+ context "When validating a file object with no attributes set" do
6
+ setup do
7
+ @file = FlixCloud::File.new
8
+ @file.valid?
9
+ end
10
+
11
+ should "require url" do
12
+ assert_match /url is required/, @file.errors.to_s
13
+ end
14
+ end
15
+
16
+ context "When validating a file object with a parameters object that is invalid" do
17
+ setup do
18
+ @file = FlixCloud::File.new(:parameters => {})
19
+ @file.valid?
20
+ end
21
+
22
+ should "inherit the parameters object's errors" do
23
+ assert @file.errors.any?{|error|
24
+ error.is_a?(Hash) && error[:parameters] && !error[:parameters].empty?
25
+ }, "Did not inherit parameters object's errors"
26
+ end
27
+ end
28
+
29
+ end