animoto 0.0.0.alpha0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +39 -0
- data/lib/animoto/asset.rb +25 -0
- data/lib/animoto/client.rb +226 -0
- data/lib/animoto/content_type.rb +47 -0
- data/lib/animoto/directing_and_rendering_job.rb +19 -0
- data/lib/animoto/directing_and_rendering_manifest.rb +25 -0
- data/lib/animoto/directing_job.rb +18 -0
- data/lib/animoto/directing_manifest.rb +115 -0
- data/lib/animoto/errors.rb +3 -0
- data/lib/animoto/footage.rb +15 -0
- data/lib/animoto/image.rb +14 -0
- data/lib/animoto/job.rb +37 -0
- data/lib/animoto/manifest.rb +21 -0
- data/lib/animoto/rendering_job.rb +24 -0
- data/lib/animoto/rendering_manifest.rb +37 -0
- data/lib/animoto/resource.rb +153 -0
- data/lib/animoto/song.rb +16 -0
- data/lib/animoto/standard_envelope.rb +27 -0
- data/lib/animoto/storyboard.rb +22 -0
- data/lib/animoto/title_card.rb +26 -0
- data/lib/animoto/video.rb +29 -0
- data/lib/animoto/visual.rb +30 -0
- data/lib/animoto.rb +5 -0
- data/spec/animoto/asset_spec.rb +1 -0
- data/spec/animoto/client_spec.rb +119 -0
- data/spec/animoto/directing_and_rendering_job_spec.rb +45 -0
- data/spec/animoto/directing_and_rendering_manifest_spec.rb +143 -0
- data/spec/animoto/directing_job_spec.rb +48 -0
- data/spec/animoto/directing_manifest_spec.rb +186 -0
- data/spec/animoto/footage_spec.rb +56 -0
- data/spec/animoto/image_spec.rb +41 -0
- data/spec/animoto/job_spec.rb +128 -0
- data/spec/animoto/rendering_job_spec.rb +57 -0
- data/spec/animoto/rendering_manifest_spec.rb +115 -0
- data/spec/animoto/resource_spec.rb +55 -0
- data/spec/animoto/song_spec.rb +54 -0
- data/spec/animoto/standard_envelope_spec.rb +0 -0
- data/spec/animoto/storyboard_spec.rb +8 -0
- data/spec/animoto/title_card_spec.rb +42 -0
- data/spec/animoto/video_spec.rb +1 -0
- data/spec/animoto/visual_spec.rb +0 -0
- data/spec/animoto_spec.rb +5 -0
- data/spec/spec_helper.rb +10 -0
- metadata +127 -0
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
Animoto API Client
|
2
|
+
==================
|
3
|
+
|
4
|
+
## Workflow
|
5
|
+
|
6
|
+
require 'animoto/client'
|
7
|
+
include Animoto
|
8
|
+
|
9
|
+
client = Client.new("username", "password")
|
10
|
+
|
11
|
+
manifest = DirectingManifest.new(:title => "Amazing Title!", :producer => "Fishy Joe")
|
12
|
+
manifest << Image.new("http://website.com/picture.png")
|
13
|
+
manifest << Image.new("http://website.com/hooray.png", :spotlit => true)
|
14
|
+
manifest << TitleCard.new("Woohoo!", "Hooray for everything!")
|
15
|
+
manifest << Footage.new("http://website.com/movie.mp4", :duration => 3.5)
|
16
|
+
manifest << Song.new("http://website.com/song.mp3", :artist => "Fishy Joe")
|
17
|
+
|
18
|
+
directing_job = client.direct!(manifest)
|
19
|
+
while directing_job.pending?
|
20
|
+
sleep(30)
|
21
|
+
client.reload!(directing_job)
|
22
|
+
end
|
23
|
+
|
24
|
+
if storyboard = directing_job.storyboard
|
25
|
+
manifest = RenderingManifest.new(storyboard, :resolution => "720p", :framerate => 24, :format => 'h264')
|
26
|
+
rendering_job = client.render!(manifest)
|
27
|
+
while rendering_job.pending?
|
28
|
+
sleep(30)
|
29
|
+
client.reload!(rendering_job)
|
30
|
+
end
|
31
|
+
|
32
|
+
if video = rendering_job.video
|
33
|
+
puts video.url
|
34
|
+
else
|
35
|
+
raise rendering_job.errors.first
|
36
|
+
end
|
37
|
+
else
|
38
|
+
raise directing_job.errors.first
|
39
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Animoto
|
2
|
+
class Asset
|
3
|
+
|
4
|
+
attr_accessor :source_url
|
5
|
+
|
6
|
+
def initialize source_url, options = {}
|
7
|
+
@source_url = source_url
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns a representation of this asset as a Hash. Used mainly for generating
|
11
|
+
# manifests.
|
12
|
+
#
|
13
|
+
# @return [Hash] this asset as a Hash
|
14
|
+
def to_hash
|
15
|
+
{ 'source_url' => @source_url }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns a representation of this asset as JSON.
|
19
|
+
#
|
20
|
+
# @return [String] this asset as JSON
|
21
|
+
def to_json
|
22
|
+
self.to_hash.to_json
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https'
|
4
|
+
require 'json'
|
5
|
+
require 'yaml'
|
6
|
+
|
7
|
+
require 'animoto/errors'
|
8
|
+
require 'animoto/content_type'
|
9
|
+
require 'animoto/standard_envelope'
|
10
|
+
require 'animoto/resource'
|
11
|
+
require 'animoto/asset'
|
12
|
+
require 'animoto/visual'
|
13
|
+
require 'animoto/footage'
|
14
|
+
require 'animoto/image'
|
15
|
+
require 'animoto/song'
|
16
|
+
require 'animoto/title_card'
|
17
|
+
require 'animoto/manifest'
|
18
|
+
require 'animoto/directing_manifest'
|
19
|
+
require 'animoto/rendering_manifest'
|
20
|
+
require 'animoto/directing_and_rendering_manifest'
|
21
|
+
require 'animoto/storyboard'
|
22
|
+
require 'animoto/video'
|
23
|
+
require 'animoto/job'
|
24
|
+
require 'animoto/directing_and_rendering_job'
|
25
|
+
require 'animoto/directing_job'
|
26
|
+
require 'animoto/rendering_job'
|
27
|
+
|
28
|
+
module Animoto
|
29
|
+
class Client
|
30
|
+
API_ENDPOINT = "http://api2-staging.animoto.com/"
|
31
|
+
API_VERSION = 1
|
32
|
+
BASE_CONTENT_TYPE = "application/vnd.animoto"
|
33
|
+
HTTP_METHOD_MAP = {
|
34
|
+
:get => Net::HTTP::Get,
|
35
|
+
:post => Net::HTTP::Post
|
36
|
+
}
|
37
|
+
|
38
|
+
attr_accessor :key, :secret
|
39
|
+
attr_reader :format
|
40
|
+
|
41
|
+
# Creates a new Client object which handles credentials, versioning, making requests, and
|
42
|
+
# parsing responses.
|
43
|
+
#
|
44
|
+
# If you have your key and secret in ~/.animotorc or /etc/.animotorc, those credentials will
|
45
|
+
# be read from those files (in that order) whenever you make a new Client if you don't specify
|
46
|
+
# them explicitly.
|
47
|
+
#
|
48
|
+
# @param [String] key the API key for your account
|
49
|
+
# @param [String] secret the secret key for your account
|
50
|
+
# @return [Client]
|
51
|
+
# @raise [ArgumentError] if no credentials are supplied
|
52
|
+
def initialize *args
|
53
|
+
@debug = ENV['DEBUG']
|
54
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
55
|
+
@key = args[0]
|
56
|
+
@secret = args[1]
|
57
|
+
unless @key && @secret
|
58
|
+
home_path = File.expand_path '~/.animotorc'
|
59
|
+
config = if File.exist?(home_path)
|
60
|
+
YAML.load(File.read(home_path))
|
61
|
+
elsif File.exist?('/etc/.animotorc')
|
62
|
+
YAML.load(File.read('/etc/.animotorc'))
|
63
|
+
end
|
64
|
+
if config
|
65
|
+
@key ||= config['key']
|
66
|
+
@secret ||= config['secret']
|
67
|
+
else
|
68
|
+
raise ArgumentError, "You must supply your key and secret"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
@format = 'json'
|
72
|
+
uri = URI.parse(API_ENDPOINT)
|
73
|
+
@http = Net::HTTP.new uri.host, uri.port
|
74
|
+
# @http.use_ssl = true
|
75
|
+
end
|
76
|
+
|
77
|
+
# Finds a resource by its URL.
|
78
|
+
#
|
79
|
+
# @param [Class] klass the Resource class you're finding
|
80
|
+
# @param [String] url the URL of the resource you want
|
81
|
+
# @param [Hash] options
|
82
|
+
# @return [Resource] the Resource object found
|
83
|
+
def find klass, url, options = {}
|
84
|
+
klass.load(find_request(klass, url, options))
|
85
|
+
end
|
86
|
+
|
87
|
+
# Sends a request to start directing a storyboard.
|
88
|
+
#
|
89
|
+
# @param [DirectingManifest] manifest the manifest to direct
|
90
|
+
# @param [Hash] options
|
91
|
+
# @return [DirectingJob] a job to monitor the status of the directing
|
92
|
+
def direct! manifest, options = {}
|
93
|
+
DirectingJob.load(send_manifest(manifest, DirectingJob.endpoint, options))
|
94
|
+
end
|
95
|
+
|
96
|
+
# Sends a request to start rendering a video.
|
97
|
+
#
|
98
|
+
# @param [RenderingManifest] manifest the manifest to render
|
99
|
+
# @param [Hash] options
|
100
|
+
# @return [RenderingJob] a job to monitor the status of the rendering
|
101
|
+
def render! manifest, options = {}
|
102
|
+
RenderingJob.load(send_manifest(manifest, RenderingJob.endpoint, options))
|
103
|
+
end
|
104
|
+
|
105
|
+
# Sends a request to start directing and rendering a video.
|
106
|
+
#
|
107
|
+
# @param [DirectingAndRenderingManifest] manifest the manifest to direct and render
|
108
|
+
# @param [Hash] options
|
109
|
+
# @return [DirectingAndRenderingJob] a job to monitor the status of the directing and rendering
|
110
|
+
def direct_and_render! manifest, options = {}
|
111
|
+
DirectingAndRenderingJob.load(send_manifest(manifest, DirectingAndRenderingJob.endpoint, options))
|
112
|
+
end
|
113
|
+
|
114
|
+
# Update a resource with the latest attributes. Useful to update the state of a Job to
|
115
|
+
# see if it's ready if you are not using HTTP callbacks.
|
116
|
+
#
|
117
|
+
# @param [Resource] resource the resource to update
|
118
|
+
# @param [Hash] options
|
119
|
+
# @return [Resource] the given resource with the latest attributes
|
120
|
+
def reload! resource, options = {}
|
121
|
+
resource.load(find_request(resource.class, resource.url, options))
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
# Builds a request to find a resource.
|
127
|
+
#
|
128
|
+
# @param [Class] klass the Resource class you're looking for
|
129
|
+
# @param [String] url the URL of the resource
|
130
|
+
# @param [Hash] options
|
131
|
+
# @return [Hash] deserialized JSON response body
|
132
|
+
def find_request klass, url, options = {}
|
133
|
+
request(:get, URI.parse(url).path, nil, { "Accept" => content_type_of(klass) }, options)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Builds a request requiring a manifest.
|
137
|
+
#
|
138
|
+
# @param [Manifest] manifest the manifest being acted on
|
139
|
+
# @param [String] endpoint the endpoint to send the request to
|
140
|
+
# @param [Hash] options
|
141
|
+
# @return [Hash] deserialized JSON response body
|
142
|
+
def send_manifest manifest, endpoint, options = {}
|
143
|
+
request(:post, endpoint, manifest.to_json, { "Accept" => "application/#{format}", "Content-Type" => content_type_of(manifest) }, options)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Makes a request and parses the response.
|
147
|
+
#
|
148
|
+
# @param [Symbol] method which HTTP method to use (should be lowercase, i.e. :get instead of :GET)
|
149
|
+
# @param [String] uri the request path
|
150
|
+
# @param [String, nil] body the request body
|
151
|
+
# @param [Hash<String,String>] headers the request headers (will be sent as-is, which means you should
|
152
|
+
# specify "Content-Type" => "..." instead of, say, :content_type => "...")
|
153
|
+
# @param [Hash] options
|
154
|
+
# @return [Hash] deserialized JSON response body
|
155
|
+
def request method, uri, body, headers = {}, options = {}
|
156
|
+
req = build_request method, uri, body, headers, options
|
157
|
+
read_response @http.request(req)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Builds the request object.
|
161
|
+
#
|
162
|
+
# @param [Symbol] method which HTTP method to use (should be lowercase, i.e. :get instead of :GET)
|
163
|
+
# @param [String] uri the request path
|
164
|
+
# @param [String, nil] body the request body
|
165
|
+
# @param [Hash<String,String>] headers the request headers (will be sent as-is, which means you should
|
166
|
+
# specify "Content-Type" => "..." instead of, say, :content_type => "...")
|
167
|
+
# @param [Hash] options
|
168
|
+
# @return [Net::HTTPRequest] the request object
|
169
|
+
def build_request method, uri, body, headers, options
|
170
|
+
req = HTTP_METHOD_MAP[method].new uri
|
171
|
+
req.body = body
|
172
|
+
req.initialize_http_header headers
|
173
|
+
req.basic_auth key, secret
|
174
|
+
req
|
175
|
+
end
|
176
|
+
|
177
|
+
# Verifies and parses the response.
|
178
|
+
#
|
179
|
+
# @param [Net::HTTPResponse] response the response object
|
180
|
+
# @return [Hash] deserialized JSON response body
|
181
|
+
def read_response response
|
182
|
+
check_status response
|
183
|
+
parse_response response
|
184
|
+
end
|
185
|
+
|
186
|
+
# Checks the status of the response to make sure it's successful.
|
187
|
+
#
|
188
|
+
# @param [Net::HTTPResponse] response the response object
|
189
|
+
# @return [nil]
|
190
|
+
# @raise [Error,RuntimeError] if the response code isn't in the 200 range
|
191
|
+
def check_status response
|
192
|
+
unless (200..299).include?(response.code.to_i)
|
193
|
+
if response.body
|
194
|
+
begin
|
195
|
+
json = JSON.parse(response.body)
|
196
|
+
errors = json['response']['status']['errors']
|
197
|
+
rescue => e
|
198
|
+
raise response.message
|
199
|
+
else
|
200
|
+
raise Animoto::Error.new(errors.collect { |e| e['message'] }.join(', '))
|
201
|
+
end
|
202
|
+
else
|
203
|
+
raise response.message
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Parses a JSON response body into a Hash.
|
209
|
+
# @param [Net::HTTPResponse] response the response object
|
210
|
+
# @return [Hash] deserialized JSON response body
|
211
|
+
def parse_response response
|
212
|
+
JSON.parse(response.body)
|
213
|
+
end
|
214
|
+
|
215
|
+
# Creates the full content type string given a Resource class or instance
|
216
|
+
# @param [Class,ContentType] klass_or_instance the class or instance to build the
|
217
|
+
# content type for
|
218
|
+
# @return [String] the full content type with the version and format included (i.e.
|
219
|
+
# "application/vnd.animoto.storyboard-v1+json")
|
220
|
+
def content_type_of klass_or_instance
|
221
|
+
klass = klass_or_instance.is_a?(Class) ? klass_or_instance : klass_or_instance.class
|
222
|
+
"#{BASE_CONTENT_TYPE}.#{klass.content_type}-v#{API_VERSION}+#{format}"
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Animoto
|
2
|
+
module ContentType
|
3
|
+
|
4
|
+
# When included, includes the InstanceMethods module and extends the
|
5
|
+
# ClassMethods module.
|
6
|
+
def self.included base
|
7
|
+
base.class_eval {
|
8
|
+
include Animoto::ContentType::InstanceMethods
|
9
|
+
extend Animoto::ContentType::ClassMethods
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
module InstanceMethods
|
14
|
+
# Returns the content type for this class.
|
15
|
+
#
|
16
|
+
# @return [String] the content type
|
17
|
+
def content_type
|
18
|
+
self.class.content_type
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
# @overload content_type(type)
|
24
|
+
# Sets the content type for this class.
|
25
|
+
# @param [String] content_type the type
|
26
|
+
# @return [String] the content type
|
27
|
+
# @overload content_type()
|
28
|
+
# Returns the content type for this class.
|
29
|
+
# @return [String] the content type
|
30
|
+
def content_type type = nil
|
31
|
+
@content_type = type if type
|
32
|
+
@content_type || infer_content_type
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# If no content type is explicitly set, this will infer the name of the content
|
38
|
+
# type from the class name by lowercasing and underscoring the base name of the
|
39
|
+
# class. For example, Animoto::DirectingJob becomes "directing_job".
|
40
|
+
#
|
41
|
+
# @return [String] the inferred content type
|
42
|
+
def infer_content_type
|
43
|
+
name.split('::').last.gsub(/(^)?([A-Z])/) { "#{'_' unless $1}#{$2.downcase}" }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Animoto
|
2
|
+
class DirectingAndRenderingJob < Animoto::Job
|
3
|
+
|
4
|
+
endpoint '/jobs/directing_and_rendering'
|
5
|
+
|
6
|
+
def self.unpack_standard_envelope body
|
7
|
+
super.merge(:video_url => body['response']['payload'][payload_key]['links']['video'])
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :video, :video_url
|
11
|
+
|
12
|
+
def instantiate attributes = {}
|
13
|
+
@video_url = attributes[:video_url]
|
14
|
+
@video = Animoto::Video.new(:url => @video_url) if @video_url
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Animoto
|
2
|
+
class DirectingAndRenderingManifest < Animoto::DirectingManifest
|
3
|
+
|
4
|
+
attr_accessor :resolution, :framerate, :format
|
5
|
+
|
6
|
+
def initialize options = {}
|
7
|
+
super
|
8
|
+
@resolution = options[:resolution]
|
9
|
+
@framerate = options[:framerate]
|
10
|
+
@format = options[:format]
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_hash options = {}
|
14
|
+
hash = super
|
15
|
+
directing_job = hash.delete('directing_job')
|
16
|
+
hash['directing_and_rendering_job'] = directing_job.merge('rendering_manifest' => { 'rendering_profile' => {}})
|
17
|
+
profile = hash['directing_and_rendering_job']['rendering_manifest']['rendering_profile']
|
18
|
+
profile['vertical_resolution'] = resolution
|
19
|
+
profile['framerate'] = framerate
|
20
|
+
profile['format'] = format
|
21
|
+
hash
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Animoto
|
2
|
+
class DirectingJob < Animoto::Job
|
3
|
+
|
4
|
+
endpoint '/jobs/directing'
|
5
|
+
|
6
|
+
def self.unpack_standard_envelope body
|
7
|
+
super.merge(:storyboard_url => body['response']['payload'][payload_key]['links']['storyboard'])
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :storyboard, :storyboard_url
|
11
|
+
|
12
|
+
def instantiate attributes = {}
|
13
|
+
@storyboard_url = attributes[:storyboard_url]
|
14
|
+
@storyboard = Animoto::Storyboard.new(:url => @storyboard_url) if @storyboard_url
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module Animoto
|
2
|
+
class DirectingManifest < Animoto::Manifest
|
3
|
+
|
4
|
+
attr_accessor :title, :producer, :pacing, :http_callback_url, :http_callback_format
|
5
|
+
attr_reader :visuals, :song, :style
|
6
|
+
|
7
|
+
# Creates a new DirectingManifest.
|
8
|
+
#
|
9
|
+
# @param [Hash] options
|
10
|
+
# @option options [String] :title the title of this project
|
11
|
+
# @option options [String] :producer the name of the producer of this project
|
12
|
+
# @option options ['default','half','double'] :pacing ('default') the pacing for this project
|
13
|
+
# @option options [String] :http_callback_url a URL to receive a callback when this job is done
|
14
|
+
# @option options ['json','xml'] :http_callback_format the format of the callback
|
15
|
+
def initialize options = {}
|
16
|
+
@title = options[:title]
|
17
|
+
@producer = options[:producer]
|
18
|
+
@pacing = options[:pacing] || 'default'
|
19
|
+
@style = 'original'
|
20
|
+
@visuals = []
|
21
|
+
@song = nil
|
22
|
+
@http_callback_url = options[:http_callback_url]
|
23
|
+
@http_callback_format = options[:http_callback_format]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Adds a TitleCard to this manifest.
|
27
|
+
#
|
28
|
+
# @see TitleCard
|
29
|
+
# @return [TitleCard] the new TitleCard
|
30
|
+
def add_title_card *args
|
31
|
+
card = TitleCard.new *args
|
32
|
+
@visuals << card
|
33
|
+
card
|
34
|
+
end
|
35
|
+
|
36
|
+
# Adds an Image to this manifest.
|
37
|
+
#
|
38
|
+
# @see Image
|
39
|
+
# @return [Image] the new Image
|
40
|
+
def add_image *args
|
41
|
+
image = Image.new *args
|
42
|
+
@visuals << image
|
43
|
+
image
|
44
|
+
end
|
45
|
+
|
46
|
+
# Adds Footage to this manifest.
|
47
|
+
#
|
48
|
+
# @see Footage
|
49
|
+
# @return [Footage] the new Footage
|
50
|
+
def add_footage *args
|
51
|
+
footage = Footage.new *args
|
52
|
+
@visuals << footage
|
53
|
+
footage
|
54
|
+
end
|
55
|
+
|
56
|
+
# Adds a Song to this manifest. Right now, a manifest can only have one song. Adding
|
57
|
+
# a second replaces the first.
|
58
|
+
#
|
59
|
+
# @see Song
|
60
|
+
# @return [Song] the new Song
|
61
|
+
def add_song *args
|
62
|
+
@song = Song.new *args
|
63
|
+
end
|
64
|
+
|
65
|
+
# Adds a visual/song to this manifest.
|
66
|
+
#
|
67
|
+
# @param [Visual,Song] asset the asset to add
|
68
|
+
# @raise [ArgumentError] if the asset isn't a Song or Visual
|
69
|
+
def add_visual asset
|
70
|
+
case asset
|
71
|
+
when Animoto::Song
|
72
|
+
@song = asset
|
73
|
+
when Animoto::Visual
|
74
|
+
@visuals << asset
|
75
|
+
else
|
76
|
+
raise ArgumentError
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Adds a visual/song to this manifest.
|
81
|
+
#
|
82
|
+
# @param [Visual,Song] asset the asset to add
|
83
|
+
# @return [self]
|
84
|
+
def << asset
|
85
|
+
add_visual asset
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns a representation of this manifest as a Hash.
|
90
|
+
#
|
91
|
+
# @return [Hash] the manifest as a Hash
|
92
|
+
# @raise [ArgumentError] if a callback URL is specified but not the format
|
93
|
+
def to_hash options = {}
|
94
|
+
hash = { 'directing_job' => { 'directing_manifest' => {} } }
|
95
|
+
job = hash['directing_job']
|
96
|
+
if http_callback_url
|
97
|
+
raise ArgumentError, "You must specify a http_callback_format (either 'xml' or 'json')" if http_callback_format.nil?
|
98
|
+
job['http_callback'] = http_callback_url
|
99
|
+
job['http_callback_format'] = http_callback_format
|
100
|
+
end
|
101
|
+
manifest = job['directing_manifest']
|
102
|
+
manifest['style'] = style
|
103
|
+
manifest['pacing'] = pacing if pacing
|
104
|
+
manifest['title'] = title if title
|
105
|
+
manifest['producer_name'] = producer if producer
|
106
|
+
manifest['visuals'] = []
|
107
|
+
visuals.each do |visual|
|
108
|
+
manifest['visuals'] << visual.to_hash
|
109
|
+
end
|
110
|
+
manifest['song'] = song.to_hash if song
|
111
|
+
hash
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Animoto
|
2
|
+
class Footage < Animoto::Asset
|
3
|
+
include Animoto::Visual
|
4
|
+
|
5
|
+
attr_accessor :audio_mix, :start_time, :duration
|
6
|
+
|
7
|
+
def to_hash
|
8
|
+
hash = super
|
9
|
+
hash['audio_mix'] = 'MIX' if audio_mix
|
10
|
+
hash['start_time'] = start_time if start_time
|
11
|
+
hash['duration'] = duration if duration
|
12
|
+
hash
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/animoto/job.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Animoto
|
2
|
+
class Job < Animoto::Resource
|
3
|
+
|
4
|
+
def self.unpack_standard_envelope body
|
5
|
+
super.merge(:state => body['response']['payload'][payload_key]['state'])
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_reader :url, :state, :errors
|
9
|
+
|
10
|
+
# Returns true if the state of this job is 'failed'.
|
11
|
+
#
|
12
|
+
# @return [Boolean] whether or not the job has failed.
|
13
|
+
def failed?
|
14
|
+
@state == 'failed'
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns true if the state of this job is 'completed'.
|
18
|
+
#
|
19
|
+
# @return [Boolean] whether or not the job is completed
|
20
|
+
def completed?
|
21
|
+
@state == 'completed'
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns true if the job is neither failed or completed.
|
25
|
+
#
|
26
|
+
# @return [Boolean] whether or not the job is still pending
|
27
|
+
def pending?
|
28
|
+
!failed? && !completed?
|
29
|
+
end
|
30
|
+
|
31
|
+
def instantiate attributes = {}
|
32
|
+
@state = attributes[:state]
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Animoto
|
2
|
+
class Manifest
|
3
|
+
include ContentType
|
4
|
+
|
5
|
+
# Returns a representation of this manifest as a Hash, used to populate
|
6
|
+
# request bodies when directing, rendering, etc.
|
7
|
+
#
|
8
|
+
# @return [Hash] the manifest as a Hash
|
9
|
+
def to_hash
|
10
|
+
{}
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns a representation of this manifest as JSON.
|
14
|
+
#
|
15
|
+
# @return [String] the manifest as JSON
|
16
|
+
def to_json
|
17
|
+
self.to_hash.to_json
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Animoto
|
2
|
+
class RenderingJob < Animoto::Job
|
3
|
+
|
4
|
+
endpoint '/jobs/rendering'
|
5
|
+
|
6
|
+
def self.unpack_standard_envelope body
|
7
|
+
super.merge({
|
8
|
+
:storyboard_url => body['response']['payload'][payload_key]['links']['storyboard'],
|
9
|
+
:video_url => body['response']['payload'][payload_key]['links']['video']
|
10
|
+
})
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :storyboard, :storyboard_url, :video, :video_url
|
14
|
+
|
15
|
+
def instantiate attributes = {}
|
16
|
+
@storyboard_url = attributes[:storyboard_url]
|
17
|
+
@storyboard = Animoto::Storyboard.new(:url => @storyboard_url) if @storyboard_url
|
18
|
+
@video_url = attributes[:video_url]
|
19
|
+
@video = Animoto::Video.new(:url => @video_url) if @video_url
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Animoto
|
2
|
+
class RenderingManifest < Animoto::Manifest
|
3
|
+
|
4
|
+
attr_accessor :storyboard, :resolution, :framerate, :format,
|
5
|
+
:http_callback_url, :http_callback_format
|
6
|
+
|
7
|
+
def initialize storyboard, options = {}
|
8
|
+
@storyboard = storyboard
|
9
|
+
@resolution = options[:resolution]
|
10
|
+
@framerate = options[:framerate]
|
11
|
+
@format = options[:format]
|
12
|
+
@http_callback_url = options[:http_callback_url]
|
13
|
+
@http_callback_format = options[:http_callback_format]
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns a representation of this manifest as a Hash.
|
17
|
+
#
|
18
|
+
# @return [Hash] this manifest as a Hash
|
19
|
+
# @raise [ArgumentError] if a callback URL was specified but not the format
|
20
|
+
def to_hash
|
21
|
+
hash = { 'rendering_job' => { 'rendering_manifest' => { 'rendering_profile' => {} } } }
|
22
|
+
job = hash['rendering_job']
|
23
|
+
if http_callback_url
|
24
|
+
raise ArgumentError, "You must specify a http_callback_format (either 'xml' or 'json')" if http_callback_format.nil?
|
25
|
+
job['http_callback'] = http_callback_url
|
26
|
+
job['http_callback_format'] = http_callback_format
|
27
|
+
end
|
28
|
+
manifest = job['rendering_manifest']
|
29
|
+
manifest['storyboard_url'] = storyboard.url
|
30
|
+
profile = manifest['rendering_profile']
|
31
|
+
profile['vertical_resolution'] = resolution
|
32
|
+
profile['framerate'] = framerate
|
33
|
+
profile['format'] = format
|
34
|
+
hash
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|