transloadit 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +5 -13
  2. data/.gitignore +3 -1
  3. data/.travis.yml +4 -4
  4. data/CHANGELOG.md +26 -0
  5. data/LICENSE +1 -1
  6. data/README.md +141 -16
  7. data/examples/README.md +185 -0
  8. data/examples/basic/audio-concat-transcoder.rb +40 -0
  9. data/examples/basic/audio-transcoder.rb +37 -0
  10. data/examples/basic/image-transcoder.rb +27 -0
  11. data/examples/basic/main.rb +69 -0
  12. data/examples/basic/media-transcoder.rb +13 -0
  13. data/lib/transloadit.rb +31 -0
  14. data/lib/transloadit/api_model.rb +73 -0
  15. data/lib/transloadit/assembly.rb +103 -63
  16. data/lib/transloadit/exception.rb +27 -0
  17. data/lib/transloadit/request.rb +23 -41
  18. data/lib/transloadit/response/assembly.rb +25 -0
  19. data/lib/transloadit/template.rb +63 -0
  20. data/lib/transloadit/version.rb +1 -1
  21. data/test/fixtures/cassettes/create_template.yml +48 -0
  22. data/test/fixtures/cassettes/delete_template.yml +44 -0
  23. data/test/fixtures/cassettes/fetch_assemblies.yml +44 -0
  24. data/test/fixtures/cassettes/fetch_assembly_notifications.yml +44 -0
  25. data/test/fixtures/cassettes/fetch_assembly_ok.yml +36 -0
  26. data/test/fixtures/cassettes/fetch_billing.yml +44 -0
  27. data/test/fixtures/cassettes/{fetch_bored.yml → fetch_root.yml} +6 -6
  28. data/test/fixtures/cassettes/fetch_template.yml +44 -0
  29. data/test/fixtures/cassettes/fetch_templates.yml +44 -0
  30. data/test/fixtures/cassettes/rate_limit_fail.yml +105 -0
  31. data/test/fixtures/cassettes/rate_limit_succeed.yml +79 -0
  32. data/test/fixtures/cassettes/replay_assembly.yml +49 -0
  33. data/test/fixtures/cassettes/replay_assembly_notification.yml +48 -0
  34. data/test/fixtures/cassettes/submit_assembly.yml +1 -36
  35. data/test/fixtures/cassettes/update_template.yml +48 -0
  36. data/test/test_helper.rb +4 -0
  37. data/test/unit/test_transloadit.rb +14 -1
  38. data/test/unit/transloadit/test_api.rb +50 -0
  39. data/test/unit/transloadit/test_assembly.rb +178 -47
  40. data/test/unit/transloadit/test_request.rb +28 -20
  41. data/test/unit/transloadit/test_response.rb +44 -0
  42. data/test/unit/transloadit/test_template.rb +118 -0
  43. data/transloadit.gemspec +3 -3
  44. metadata +70 -33
@@ -0,0 +1,40 @@
1
+ class AudioConcatTranscoder < MediaTranscoder
2
+ require 'transloadit'
3
+ require_relative 'media-transcoder'
4
+
5
+ # in this example a file is encoded as an mp3, id3 tags are added, and it is stored in s3
6
+ def transcode!(files)
7
+ concat = transloadit_client.step('concat', '/audio/concat', {
8
+ preset: 'mp3',
9
+ use: {
10
+ steps: files.map.each_with_index do |f, i|
11
+ { name: ':original', as: "audio_#{i}", fields: "file_#{i}" }
12
+ end
13
+ },
14
+ result: true
15
+ })
16
+ store = transloadit_client.step('store', '/s3/store', {
17
+ key: ENV.fetch('S3_ACCESS_KEY'),
18
+ secret: ENV.fetch('S3_SECRET_KEY'),
19
+ bucket: ENV.fetch('S3_BUCKET'),
20
+ bucket_region: ENV.fetch('S3_REGION'),
21
+ use: ['concat']
22
+ })
23
+ assembly = transloadit_client.assembly(steps: [concat, store])
24
+ assembly.submit! *open_files(files)
25
+ end
26
+
27
+ def open_files(files)
28
+ files.map do |f|
29
+ open(f)
30
+ end
31
+ end
32
+
33
+ def mp3_metadata
34
+ meta = { publisher: 'Transloadit', title: '${file.name}' }
35
+ meta[:album] = 'Transloadit Compilation'
36
+ meta[:artist] = 'Transloadit'
37
+ meta[:track] = '1/1'
38
+ meta
39
+ end
40
+ end
@@ -0,0 +1,37 @@
1
+ class AudioTranscoder < MediaTranscoder
2
+ require 'transloadit'
3
+ require_relative 'media-transcoder'
4
+
5
+ # in this example a file is encoded as an mp3, id3 tags are added, and it is stored in s3
6
+ def transcode!(file)
7
+ encode_mp3 = transloadit_client.step('mp3_encode', '/audio/encode', {
8
+ use: ':original',
9
+ preset: 'mp3',
10
+ ffmpeg_stack: 'v2.2.3',
11
+ result: true
12
+ })
13
+ write_metadata = transloadit_client.step('mp3', '/meta/write', {
14
+ use: 'mp3_encode',
15
+ ffmpeg_stack: 'v2.2.3',
16
+ result: true,
17
+ data_to_write: mp3_metadata
18
+ })
19
+ store = transloadit_client.step('store', '/s3/store', {
20
+ key: ENV.fetch('S3_ACCESS_KEY'),
21
+ secret: ENV.fetch('S3_SECRET_KEY'),
22
+ bucket: ENV.fetch('S3_BUCKET'),
23
+ bucket_region: ENV.fetch('S3_REGION'),
24
+ use: ['mp3']
25
+ })
26
+ assembly = transloadit_client.assembly(steps: [encode_mp3, write_metadata, store])
27
+ assembly.submit! open(file)
28
+ end
29
+
30
+ def mp3_metadata
31
+ meta = { publisher: 'Transloadit', title: '${file.name}' }
32
+ meta[:album] = 'Transloadit Compilation'
33
+ meta[:artist] = 'Transloadit'
34
+ meta[:track] = '1/1'
35
+ meta
36
+ end
37
+ end
@@ -0,0 +1,27 @@
1
+ class ImageTranscoder < MediaTranscoder
2
+ require 'transloadit'
3
+ require_relative 'media-transcoder'
4
+
5
+ # in this example a file is submitted, optimized, and then stored in s3
6
+ def transcode!(file)
7
+ optimize = transloadit_client.step('image', '/image/optimize', {
8
+ progressive: true,
9
+ use: ':original',
10
+ result: true
11
+ })
12
+ store = transloadit_client.step('store', '/s3/store', {
13
+ key: ENV.fetch('S3_ACCESS_KEY'),
14
+ secret: ENV.fetch('S3_SECRET_KEY'),
15
+ bucket: ENV.fetch('S3_BUCKET'),
16
+ bucket_region: ENV.fetch('S3_REGION'),
17
+ use: 'image'
18
+ })
19
+ assembly = transloadit_client.assembly(steps: [optimize, store])
20
+ assembly.submit! open(file)
21
+ end
22
+
23
+ def get_status!(assembly_id)
24
+ req = Transloadit::Request.new('/assemblies/' + assembly_id.to_s, ENV.fetch('TRANSLOADIT_SECRET'))
25
+ req.get.extend!(Transloadit::Response::Assembly)
26
+ end
27
+ end
@@ -0,0 +1,69 @@
1
+ require 'open-uri'
2
+ require_relative 'media-transcoder'
3
+ require_relative 'image-transcoder'
4
+ require_relative 'audio-transcoder'
5
+ require_relative 'audio-concat-transcoder'
6
+
7
+ p 'starting image transcoding job...'
8
+ p 'fetching image from the cat api...'
9
+
10
+ open('http://thecatapi.com/api/images/get') do |f|
11
+ p 'starting transcoding job...'
12
+ image_transcoder = ImageTranscoder.new
13
+ response = image_transcoder.transcode!(f)
14
+
15
+ # if you are using rails one thing you can do would be to start an ActiveJob process that recursively
16
+ # checks on the status of the assembly until it is finished
17
+ while !response.finished?
18
+ sleep 1
19
+ p 'checking job status...'
20
+ response = image_transcoder.get_status!(response[:assembly_id])
21
+ end
22
+ p response[:message]
23
+ p response[:results]['image'][0]['url']
24
+ end
25
+
26
+ p 'starting audio transcoding job...'
27
+ p 'fetching soundbite from nasa...'
28
+ p "\n"
29
+
30
+ open('http://www.nasa.gov/640379main_Computers_are_in_Control.m4r') do |f|
31
+ p 'starting transcoding job...'
32
+ audio_transcoder = AudioTranscoder.new
33
+ response = audio_transcoder.transcode!(f)
34
+
35
+ # if you are using rails one thing you can do would be to start an ActiveJob process that recursively
36
+ # checks on the status of the assembly until it is finished
37
+ while !response.finished?
38
+ sleep 1
39
+ p 'checking job status...'
40
+ response = audio_transcoder.get_status!(response[:assembly_id])
41
+ end
42
+ p response[:message]
43
+ p response[:results]['mp3'][0]['url']
44
+ p "\n"
45
+ end
46
+
47
+ p 'starting audio concat transcoding job...'
48
+ p 'fetching 3 soundbites from nasa...'
49
+
50
+ files = [
51
+ 'http://www.nasa.gov/mp3/640148main_APU%20Shutdown.mp3',
52
+ 'http://www.nasa.gov/mp3/640164main_Go%20for%20Deploy.mp3',
53
+ 'http://www.nasa.gov/mp3/640165main_Lookin%20At%20It.mp3'
54
+ ]
55
+
56
+ p 'starting transcoding job...'
57
+ audio_concat_transcoder = AudioConcatTranscoder.new
58
+ response = audio_concat_transcoder.transcode!(files)
59
+
60
+ # if you are using rails one thing you can do would be to start an ActiveJob process that recursively
61
+ # checks on the status of the assembly until it is finished
62
+ while !response.finished?
63
+ sleep 1
64
+ p 'checking job status...'
65
+ response = audio_concat_transcoder.get_status!(response[:assembly_id])
66
+ end
67
+ p response[:message]
68
+ p response[:results]['concat'][0]['url']
69
+ p "\n"
@@ -0,0 +1,13 @@
1
+ class MediaTranscoder
2
+ def transloadit_client
3
+ @transloadit ||= Transloadit.new({
4
+ key: ENV.fetch('TRANSLOADIT_KEY'),
5
+ secret: ENV.fetch('TRANSLOADIT_SECRET')
6
+ })
7
+ end
8
+
9
+ def get_status!(assembly_id)
10
+ req = Transloadit::Request.new('/assemblies/' + assembly_id.to_s, ENV.fetch('TRANSLOADIT_SECRET'))
11
+ req.get.extend!(Transloadit::Response::Assembly)
12
+ end
13
+ end
@@ -1,14 +1,18 @@
1
1
  require 'multi_json'
2
+ require 'date'
2
3
 
3
4
  #
4
5
  # Implements the Transloadit REST API in Ruby. Check the {file:README.md README}
5
6
  # for usage instructions.
6
7
  #
7
8
  class Transloadit
9
+ autoload :ApiModel, 'transloadit/api_model'
8
10
  autoload :Assembly, 'transloadit/assembly'
11
+ autoload :Exception, 'transloadit/exception'
9
12
  autoload :Request, 'transloadit/request'
10
13
  autoload :Response, 'transloadit/response'
11
14
  autoload :Step, 'transloadit/step'
15
+ autoload :Template, 'transloadit/template'
12
16
  autoload :VERSION, 'transloadit/version'
13
17
 
14
18
  # @return [String] your Transloadit auth key
@@ -75,6 +79,33 @@ class Transloadit
75
79
  Transloadit::Assembly.new(self, options)
76
80
  end
77
81
 
82
+ #
83
+ # Creates a Transloadit::Template instance ready to interact with its corresponding REST API.
84
+ #
85
+ # See the Transloadit {documentation}[https://transloadit.com/docs/api-docs/#template-api]
86
+ # for futher information on Templates and available endpoints.
87
+ #
88
+ def template(options = {})
89
+ Transloadit::Template.new(self, options)
90
+ end
91
+
92
+ #
93
+ # Gets user billing reports for specified month and year.
94
+ # Defaults to current month or year if corresponding param is not specified.
95
+ #
96
+ # @param [Integer] month the month for which billing reports should be retrieved.
97
+ # defaults to current month if not specified.
98
+ # @param [Integer] year the year for which billing reports should be retrieved.
99
+ # defaults to current year if not specified.
100
+ #
101
+ def bill(month = Date.today.month, year = Date.today.year)
102
+ # convert month to 2 digit format
103
+ month = format '%02d', month
104
+ path = "bill/#{year}-#{month}"
105
+
106
+ Transloadit::Request.new(path, self.secret).get({ :auth => self.to_hash })
107
+ end
108
+
78
109
  #
79
110
  # @return [String] a human-readable version of the Transloadit.
80
111
  #
@@ -0,0 +1,73 @@
1
+ require 'transloadit'
2
+
3
+ #
4
+ # Represents an API class that more Transloadit specific API classes
5
+ # would inherit from.
6
+ #
7
+ class Transloadit::ApiModel
8
+ # @return [Transloadit] the associated Transloadit instance
9
+ attr_reader :transloadit
10
+
11
+ # @return [Hash] the options describing the Assembly
12
+ attr_accessor :options
13
+
14
+ #
15
+ # Creates a new API instance authenticated using the given +transloadit+
16
+ # instance.
17
+ #
18
+ # @param [Transloadit] transloadit the associated Transloadit instance
19
+ # @param [Hash] options the configuration for the API;
20
+ #
21
+ def initialize(transloadit, options = {})
22
+ self.transloadit = transloadit
23
+ self.options = options
24
+ end
25
+
26
+ #
27
+ # @return [String] a human-readable version of the API
28
+ #
29
+ def inspect
30
+ self.to_hash.inspect
31
+ end
32
+
33
+ #
34
+ # @return [Hash] a Transloadit-compatible Hash of the API's contents
35
+ #
36
+ def to_hash
37
+ self.options.merge(
38
+ :auth => self.transloadit.to_hash,
39
+ ).delete_if {|_,v| v.nil?}
40
+ end
41
+
42
+ #
43
+ # @return [String] JSON-encoded String containing the API's contents
44
+ #
45
+ def to_json
46
+ MultiJson.dump(self.to_hash)
47
+ end
48
+
49
+ protected
50
+
51
+ attr_writer :transloadit
52
+
53
+ private
54
+
55
+ #
56
+ # Performs http request in favour of it's caller
57
+ #
58
+ # @param [String] path url path to which request is made
59
+ # @param [Hash] params POST/GET data to submit with the request
60
+ # @param [String] method http request method. This could be 'post' or 'get'
61
+ # @param [Hash] extra_params additional POST/GET data to submit with the request
62
+ #
63
+ # @return [Transloadit::Response] the response
64
+ #
65
+ def _do_request(path, params = nil, method = 'get', extra_params = nil)
66
+ if !params.nil?
67
+ params = self.to_hash.update(params)
68
+ params = { :params => params } if ['post', 'put', 'delete'].include? method
69
+ params.merge!(extra_params) if !extra_params.nil?
70
+ end
71
+ Transloadit::Request.new(path, self.transloadit.secret).public_send(method, params)
72
+ end
73
+ end
@@ -1,34 +1,13 @@
1
1
  require 'transloadit'
2
2
 
3
3
  #
4
- # Represents a Assembly ready to be sent to the REST API for processing. An
5
- # Assembly can contain one or more Steps for processing or point to a
6
- # server-side template. It's submitted along with a list of files to process,
7
- # at which point Transloadit will process and store the files according to the
8
- # rules in the Assembly.
4
+ # Represents an Assembly API ready to make calls to the REST API endpoints.
9
5
  #
10
- # See the Transloadit {documentation}[http://transloadit.com/docs/building-assembly-instructions]
11
- # for futher information on Assemblies and their parameters.
6
+ # See the Transloadit {documentation}[https://transloadit.com/docs/api-docs/#assembly-api]
7
+ # for futher information on Assemblies and available endpoints.
12
8
  #
13
- class Transloadit::Assembly
14
- # @return [Transloadit] the associated Transloadit instance
15
- attr_reader :transloadit
16
-
17
- # @return [Hash] the options describing the Assembly
18
- attr_accessor :options
19
-
20
- #
21
- # Creates a new Assembly authenticated using the given +transloadit+
22
- # instance.
23
- #
24
- # @param [Transloadit] transloadit the associated Transloadit instance
25
- # @param [Hash] options the configuration for the Assembly;
26
- # see {Transloadit#assembly}
27
- #
28
- def initialize(transloadit, options = {})
29
- self.transloadit = transloadit
30
- self.options = options
31
- end
9
+ class Transloadit::Assembly < Transloadit::ApiModel
10
+ DEFAULT_TRIES = 3
32
11
 
33
12
  #
34
13
  # @return [Hash] the processing steps, formatted for sending to Transloadit
@@ -38,41 +17,106 @@ class Transloadit::Assembly
38
17
  end
39
18
 
40
19
  #
41
- # Submits the assembly for processing. Accepts as many IO objects as you
42
- # wish to process in the assembly. The last argument is an optional Hash
20
+ # Creates a Transloadit::Assembly and sends to the REST API. An
21
+ # Assembly can contain one or more Steps for processing or point to a
22
+ # server-side template. It's submitted along with a list of files to process,
23
+ # at which point Transloadit will process and store the files according to the
24
+ # rules in the Assembly.
25
+ # See the Transloadit {documentation}[http://transloadit.com/docs/building-assembly-instructions]
26
+ # for futher information on Assemblies and their parameters.
27
+ #
28
+ # Accepts as many IO objects as you wish to process in the assembly.
29
+ # The last argument is an optional Hash
43
30
  # of parameters to send along with the request.
44
31
  #
45
- # @overload submit!(*ios)
32
+ # @overload create!(*ios)
46
33
  # @param [Array<IO>] *ios the files for the assembly to process
47
34
  #
48
- # @overload submit!(*ios, params = {})
35
+ # @overload create!(*ios, params = {})
49
36
  # @param [Array<IO>] *ios the files for the assembly to process
50
- # @param [Hash] params additional POST data to submit with the request
37
+ # @param [Hash] params additional POST data to submit with the request;
38
+ # for a full list of parameters, see the official documentation
39
+ # on {templates}[http://transloadit.com/docs/templates].
40
+ # @option params [Step, Array<Step>] :steps the steps to perform in this
41
+ # assembly
42
+ # @option params [String] :notify_url A URL to be POSTed when the assembly
43
+ # has finished processing
44
+ # @option params [String] :template_id the ID of a
45
+ # {template}[https://transloadit.com/templates] to use instead of
46
+ # specifying params here directly
47
+ #
48
+ def create!(*ios, **params)
49
+ params[:steps] = _wrap_steps_in_hash(params[:steps]) if !params[:steps].nil?
50
+
51
+ extra_params = {}
52
+ extra_params.merge!(self.options[:fields]) if self.options[:fields]
53
+
54
+ trials = self.options[:tries] || DEFAULT_TRIES
55
+ (1..trials).each do |trial|
56
+ # update the payload with file entries
57
+ ios.each_with_index {|f, i| extra_params.update :"file_#{i}" => f }
58
+
59
+ response = _do_request(
60
+ '/assemblies',params,'post', extra_params
61
+ ).extend!(Transloadit::Response::Assembly)
62
+
63
+ return response unless response.rate_limit?
64
+
65
+ _handle_rate_limit!(response, ios, trial < trials)
66
+ end
67
+ end
68
+
69
+ #
70
+ # alias for create!
71
+ # keeping this method for backward compatibility
51
72
  #
52
73
  def submit!(*ios)
53
- params = _extract_options!(ios)
54
- payload = { :params => self.to_hash.update(params) }
55
- payload.merge!(self.options[:fields]) if self.options[:fields]
74
+ warn "#{caller(1)[0]}: warning: Transloadit::Assembly#submit!"\
75
+ " is deprecated. use Transloadit::Assembly#create! instead"
76
+ self.create!(*ios)
77
+ end
56
78
 
57
- # update the payload with file entries
58
- ios.each_with_index {|f, i| payload.update :"file_#{i}" => f }
79
+ #
80
+ # Returns a list of all assemblies
81
+ # @param [Hash] additional GET data to submit with the request
82
+ #
83
+ def list(params = {})
84
+ _do_request('/assemblies', params)
85
+ end
59
86
 
60
- # find a bored instance
61
- Transloadit::Request.bored!
87
+ #
88
+ # Returns a single assembly object specified by the assembly id
89
+ # @param [String] id id of the desired assembly
90
+ #
91
+ def get(id)
92
+ _do_request("/assemblies/#{id}").extend!(Transloadit::Response::Assembly)
93
+ end
62
94
 
63
- # create the request
64
- request = Transloadit::Request.new '/assemblies',
65
- self.transloadit.secret
95
+ #
96
+ # Replays an assembly specified by the id
97
+ # @param [String] id id of the desired assembly
98
+ # @param [Hash] params additional POST data to submit with the request
99
+ #
100
+ def replay(id, params = {})
101
+ params.merge!({ :wait => false })
102
+ _do_request("/assemblies/#{id}/replay", params, 'post').extend!(Transloadit::Response::Assembly)
103
+ end
66
104
 
67
- # post the request, extend it with the Assembly extensions
68
- request.post(payload).extend!(Transloadit::Response::Assembly)
105
+ #
106
+ # Returns all assembly notifications
107
+ # @param [Hash] params additional GET data to submit with the request
108
+ #
109
+ def get_notifications(params = {})
110
+ _do_request "/assembly_notifications", params
69
111
  end
70
112
 
71
113
  #
72
- # @return [String] a human-readable version of the Assembly
114
+ # Replays an assembly notification by the id
115
+ # @param [String] id id of the desired assembly
116
+ # @param [Hash] params additional POST data to submit with the request
73
117
  #
74
- def inspect
75
- self.to_hash.inspect
118
+ def replay_notification(id, params = {})
119
+ _do_request("/assembly_notifications/#{id}/replay", params, 'post')
76
120
  end
77
121
 
78
122
  #
@@ -85,17 +129,6 @@ class Transloadit::Assembly
85
129
  ).delete_if {|k,v| v.nil?}
86
130
  end
87
131
 
88
- #
89
- # @return [String] JSON-encoded String containing the Assembly's contents
90
- #
91
- def to_json
92
- MultiJson.dump(self.to_hash)
93
- end
94
-
95
- protected
96
-
97
- attr_writer :transloadit
98
-
99
132
  private
100
133
 
101
134
  #
@@ -116,13 +149,20 @@ class Transloadit::Assembly
116
149
  end
117
150
 
118
151
  #
119
- # Extracts the last argument from a set of arguments if it's a hash.
120
- # Otherwise, returns an empty hash.
152
+ # Stays idle for certain time and then reopens assembly files for reprocessing.
153
+ # Should be called when assembly rate limit is reached.
121
154
  #
122
- # @param *args the arguments to search for an options hash
123
- # @return [Hash] the options passed, otherwise an empty hash
155
+ # @param [Response] response assembly response that comes with a rate limit
156
+ # @param [Array<IO>] ios the files sent for the assembly to process.
124
157
  #
125
- def _extract_options!(args)
126
- args.last.is_a?(Hash) ? args.pop : {}
158
+ def _handle_rate_limit!(response, ios, is_retrying)
159
+ if is_retrying
160
+ warn "Rate limit reached. Waiting for #{response.wait_time} seconds before retrying."
161
+ sleep response.wait_time
162
+ # RestClient closes file streams at the end of a request.
163
+ ios.collect! {|file| open file.path }
164
+ else
165
+ raise Transloadit::Exception::RateLimitReached.new(response)
166
+ end
127
167
  end
128
168
  end