animoto 0.0.0.alpha0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/README.md +39 -0
  2. data/lib/animoto/asset.rb +25 -0
  3. data/lib/animoto/client.rb +226 -0
  4. data/lib/animoto/content_type.rb +47 -0
  5. data/lib/animoto/directing_and_rendering_job.rb +19 -0
  6. data/lib/animoto/directing_and_rendering_manifest.rb +25 -0
  7. data/lib/animoto/directing_job.rb +18 -0
  8. data/lib/animoto/directing_manifest.rb +115 -0
  9. data/lib/animoto/errors.rb +3 -0
  10. data/lib/animoto/footage.rb +15 -0
  11. data/lib/animoto/image.rb +14 -0
  12. data/lib/animoto/job.rb +37 -0
  13. data/lib/animoto/manifest.rb +21 -0
  14. data/lib/animoto/rendering_job.rb +24 -0
  15. data/lib/animoto/rendering_manifest.rb +37 -0
  16. data/lib/animoto/resource.rb +153 -0
  17. data/lib/animoto/song.rb +16 -0
  18. data/lib/animoto/standard_envelope.rb +27 -0
  19. data/lib/animoto/storyboard.rb +22 -0
  20. data/lib/animoto/title_card.rb +26 -0
  21. data/lib/animoto/video.rb +29 -0
  22. data/lib/animoto/visual.rb +30 -0
  23. data/lib/animoto.rb +5 -0
  24. data/spec/animoto/asset_spec.rb +1 -0
  25. data/spec/animoto/client_spec.rb +119 -0
  26. data/spec/animoto/directing_and_rendering_job_spec.rb +45 -0
  27. data/spec/animoto/directing_and_rendering_manifest_spec.rb +143 -0
  28. data/spec/animoto/directing_job_spec.rb +48 -0
  29. data/spec/animoto/directing_manifest_spec.rb +186 -0
  30. data/spec/animoto/footage_spec.rb +56 -0
  31. data/spec/animoto/image_spec.rb +41 -0
  32. data/spec/animoto/job_spec.rb +128 -0
  33. data/spec/animoto/rendering_job_spec.rb +57 -0
  34. data/spec/animoto/rendering_manifest_spec.rb +115 -0
  35. data/spec/animoto/resource_spec.rb +55 -0
  36. data/spec/animoto/song_spec.rb +54 -0
  37. data/spec/animoto/standard_envelope_spec.rb +0 -0
  38. data/spec/animoto/storyboard_spec.rb +8 -0
  39. data/spec/animoto/title_card_spec.rb +42 -0
  40. data/spec/animoto/video_spec.rb +1 -0
  41. data/spec/animoto/visual_spec.rb +0 -0
  42. data/spec/animoto_spec.rb +5 -0
  43. data/spec/spec_helper.rb +10 -0
  44. 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,3 @@
1
+ module Animoto
2
+ class Error < StandardError; end
3
+ 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
@@ -0,0 +1,14 @@
1
+ module Animoto
2
+ class Image < Animoto::Asset
3
+ include Animoto::Visual
4
+
5
+ attr_accessor :rotation
6
+
7
+ def to_hash
8
+ hash = super
9
+ hash['rotation'] = rotation if rotation
10
+ hash['spotlit'] = spotlit? unless @spotlit.nil?
11
+ hash
12
+ end
13
+ end
14
+ end
@@ -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