transloadit 1.2.0 → 2.0.0

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