shrine-transloadit 0.5.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,474 +1,671 @@
1
1
  # Shrine::Plugins::Transloadit
2
2
 
3
- Provides [Transloadit] integration for [Shrine].
3
+ Provides [Transloadit] integration for [Shrine], using its [Ruby SDK].
4
4
 
5
- Transloadit is a service that helps you handles file uploads, resize, crop and
5
+ Transloadit is a service that helps you handle file uploads, resize, crop and
6
6
  watermark your images, make GIFs, transcode your videos, extract thumbnails,
7
- generate audio waveforms, and so much more. In short, Transloadit is the Swiss
8
- Army Knife for your files.
9
-
10
- ## Setup
11
-
12
- While Transloadit is able to export processed files to [many storage services],
13
- this plugin currently supports only Amazon S3 (just because there are no Shrine
14
- integrations written for other services on that list yet). You can just add
15
- shrine-transloadit to your current setup:
7
+ generate audio waveforms and more.
8
+
9
+ ## Contents
10
+
11
+ * [Installation](#installation)
12
+ * [Setup](#setup)
13
+ * [Usage](#usage)
14
+ * [Notifications](#notifications)
15
+ * [Direct uploads](#direct-uploads)
16
+ * [Promotion](#promotion)
17
+ * [Skipping exports](#skipping-exports)
18
+ * [API](#api)
19
+ - [Processor](#processor)
20
+ - [Saver](#saver)
21
+ - [Step](#step)
22
+ - [Import step](#import-step)
23
+ - [Export step](#export-step)
24
+ - [File](#file)
25
+ * [Instrumentation](#instrumentation)
26
+
27
+ ## Installation
28
+
29
+ Put the gem in your Gemfile:
16
30
 
17
31
  ```rb
18
- gem "shrine"
19
- gem "aws-sdk-s3", "~> 1.2" # for Amazon S3
20
- gem "shrine-transloadit"
32
+ # Gemfile
33
+ gem "shrine-transloadit", "~> 1.0"
21
34
  ```
22
35
 
23
- ```rb
24
- require "shrine"
25
- require "shrine/storage/s3"
36
+ ## Setup
26
37
 
27
- s3_options = {
28
- bucket: "my-bucket",
29
- region: "my-region",
30
- access_key_id: "abc",
31
- secret_access_key: "xyz",
32
- }
38
+ You'll first need to create [credentials] for the storage service you want to
39
+ import from and export to. Let's assume you're using S3 and have named the
40
+ credentials `s3_store`. Now you can load the `transloadit` plugin, providing
41
+ Transloadit key & secret, and mapping credentials to Shrine storages:
33
42
 
43
+ ```rb
44
+ # example storage configuration
34
45
  Shrine.storages = {
35
- cache: Shrine::Storage::S3.new(prefix: "cache", **s3_options),
36
- store: Shrine::Storage::S3.new(prefix: "store", **s3_options),
46
+ cache: Shrine::Storage::S3.new(prefix: "cache", **options),
47
+ store: Shrine::Storage::S3.new(**options),
37
48
  }
38
49
 
39
- class TransloaditUploader < Shrine
40
- plugin :transloadit,
41
- auth_key: "your transloadit key",
42
- auth_secret: "your transloadit secret"
43
- end
50
+ # transloadit plugin configuration
51
+ Shrine.plugin :transloadit,
52
+ auth: {
53
+ key: "YOUR_TRANSLOADIT_KEY",
54
+ secret: "YOUR_TRANSLOADIT_SECRET",
55
+ },
56
+ credentials: {
57
+ cache: :s3_store, # use "s3_store" credentials for :cache storage
58
+ store: :s3_store, # use "s3_store" credentials for :store storage
59
+ }
60
+
61
+ # for storing processed files
62
+ Shrine.plugin :derivatives
44
63
  ```
45
64
 
46
- This setup assumes you're doing direct S3 uploads, but you can also do [direct
47
- uploads to Transloadit], or just use any other `:cache` storage which provides
48
- URLs for uploaded files.
65
+ ## Usage
66
+
67
+ The `transloadit` plugin provides helper methods for creating [import][import
68
+ robots] and [export][export robots] steps, as well as for parsing out exported
69
+ files from results.
70
+
71
+ Here is a basic example where we kick off transcoding and thumbnail extraction
72
+ from an attached video, wait for assembly to complete, then save processed
73
+ files as derivatives:
49
74
 
50
- ## How it works
75
+ ```rb
76
+ class VideoUploader < Shrine
77
+ Attacher.transloadit_processor do
78
+ import = file.transloadit_import_step
79
+ encode = transloadit_step "encode", "/video/encode", use: import
80
+ thumbs = transloadit_step "thumbs", "/video/thumbs", use: import
81
+ export = store.transloadit_export_step use: [encode, thumbs]
82
+
83
+ assembly = transloadit.assembly(steps: [import, encode, thumbs, export])
84
+ assembly.create!
85
+ end
51
86
 
52
- Transloadit works in a way that you create an "assembly", which contains all
53
- information about how the file(s) should be processed, from import to export.
54
- Processing itself happens asynchronously, and you can give Transloadit a URL
55
- which it will POST results to when processing finishes.
87
+ Attacher.transloadit_saver do |results|
88
+ transcoded = store.transloadit_file(results["encode"])
89
+ thumbnails = store.transloadit_files(results["thumbs"])
56
90
 
57
- This plugin allows you to easily implement this webhook flow. You can intercept
58
- promoting, and submit a Transloadit assembly using the cached file, along with
59
- a URL to the route in your app where you'd like Transloadit to POST the results
60
- of processing. Then you can call the plugin again in the route to save the
61
- results to your attachment column.
91
+ merge_derivatives(transcoded: transcoded, thumbnails: thumbnails)
92
+ end
93
+ end
94
+ ```
95
+ ```rb
96
+ response = attacher.transloadit_process
97
+ response.reload_until_finished!
62
98
 
63
- The **[demo app]** shows a complete implementation of this flow, and can serve
64
- as a good baseline for your own implementation.
99
+ if response.error?
100
+ # handle error
101
+ end
65
102
 
66
- ## Usage
103
+ attacher.transloadit_save(response["results"])
104
+ attacher.derivatives #=>
105
+ # {
106
+ # transcoded: #<Shrine::UploadedFile storage_key=:store ...>,
107
+ # thumbnails: [
108
+ # #<Shrine::UploadedFile storage_key=:store ...>,
109
+ # #<Shrine::UploadedFile storage_key=:store ...>,
110
+ # ...
111
+ # ]
112
+ # }
113
+ ```
67
114
 
68
- We loaded the `transloadit` plugin in a `TransloaditUploader` base uploader
69
- class, so that any uploaders that you want to do Transloadit processing with
70
- can just inherit from that class.
115
+ ### Backgrounding
71
116
 
72
- Transloadit assemblies are built inside `#transloadit_process` method in your
73
- uploader, and you can use some convenient helper methods which the plugin
74
- provides.
117
+ When using [backgrounding], it's probably best to create the assembly after
118
+ promotion:
75
119
 
76
120
  ```rb
77
- class ImageUploader < TransloaditUploader # inherit from our Transloadit base uploader class
78
- def transloadit_process(io, context)
79
- resized = transloadit_file(io)
80
- .add_step("resize", "/image/resize", width: 800)
121
+ Shrine.plugin :backgrounding
122
+ Shrine::Attacher.promote_block do
123
+ PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
124
+ end
125
+ ```
126
+ ```rb
127
+ class PromoteJob
128
+ include Sidekiq::Worker
129
+
130
+ def perform(attacher_class, record_class, record_id, name, file_data)
131
+ attacher_class = Object.const_get(attacher_class)
132
+ record = Object.const_get(record_class).find(record_id) # if using Active Record
81
133
 
82
- transloadit_assembly(resized, context: context)
134
+ attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
135
+ attacher.atomic_promote
136
+ attacher.transloadit_process
137
+ # ...
138
+ rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
83
139
  end
84
140
  end
85
141
  ```
86
142
 
87
- These helper methods just provide a higher-level interface over the
88
- [transloadit gem], which you might want look at to get a better understanding
89
- of how building assemblies works.
143
+ ## Notifications
90
144
 
91
- In short, in Transloadit every action, be it import, processing, or export, is
92
- a "step". Each step is defined by its [robot and arguments], and needs to have
93
- a *unique name*. Transloadit allows you to define the entire processing flow
94
- (which can result in multiple files) as a collection of steps, which is called
95
- an "assembly". Once the assembly is built it can be submitted to Transloadit.
145
+ When using [assembly notifications], the attacher data can be sent to the
146
+ webhook via `:fields`:
96
147
 
97
- ### Versions
148
+ ```rb
149
+ Attacher.transloadit_processor do
150
+ # ...
151
+ assembly = transloadit.assembly(
152
+ steps: [ ... ],
153
+ notify_url: "https://example.com/webhooks/transloadit",
154
+ fields: {
155
+ attacher: {
156
+ record_class: record.class,
157
+ record_id: record.id,
158
+ name: name,
159
+ data: file_data,
160
+ }
161
+ }
162
+ )
163
+ assembly.create!
164
+ end
165
+ ```
98
166
 
99
- With Transloadit you can create multiple files in a single assembly, and this
100
- plugin allows you to leverage that in form of a hash of versions.
167
+ Then in the webhook handler we can load the attacher and [atomically
168
+ persist][atomic_helpers] assembly results. If during processing the attachment
169
+ has changed or record was deleted, we make sure we delete processed files.
101
170
 
102
171
  ```rb
103
- class ImageUploader < TransloaditUploader
104
- plugin :versions
172
+ post "/transloadit/video" do
173
+ Shrine.transloadit_verify!(params) # verify transloadit signature
174
+
175
+ response = JSON.parse(params["transloadit"])
105
176
 
106
- def transloadit_process(io, context)
107
- original = transloadit_file(io)
108
- medium = original.add_step("resize_500", "/image/resize", width: 500)
109
- small = original.add_step("resize_300", "/image/resize", width: 300)
177
+ record_class, record_id, name, file_data = response["fields"]["attacher"].values
178
+ record_class = Object.const_get(record_class)
110
179
 
111
- files = { original: original, medium: medium, small: small }
180
+ attacher = record_class.send(:"#{name}_attacher")
181
+ derivatives = attacher.transloadit_save(response["results"])
112
182
 
113
- transloadit_assembly(files, context: context)
183
+ begin
184
+ record = record_class.find(record_id)
185
+ attacher = Shrine::Attacher.retrieve(model: record, name: name, file: file_data)
186
+
187
+ attacher.merge_derivatives(derivatives)
188
+ attacher.atomic_persist
189
+ rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
190
+ attacher.destroy_attached # delete orphaned processed files
114
191
  end
192
+
193
+ # return successful response for Transloadit
194
+ status 200
115
195
  end
116
196
  ```
117
197
 
118
- ### Multiple files
198
+ Note that if you have CSRF protection, make sure that you skip verifying the
199
+ CSRF token for this route.
200
+
201
+ ## Direct uploads
202
+
203
+ Transloadit supports client side uploads via [Robodog], an [Uppy]-based
204
+ JavaScript library.
119
205
 
120
- Some Transloadit robots might produce multiple files, e.g. `/video/adaptive` or
121
- `/document/thumbs`. By default, shrine-transloadit will raise an error when a
122
- step returned more than one file, but it's possible to specify the result
123
- format in which shrine-transloadit should save the processed files.
206
+ If you have an HTML form, you can use Robodog's [Form API][Robodog Form] to add
207
+ Transloadit's encoding capabilities to it:
124
208
 
125
- #### `list`
209
+ ```js
210
+ window.Robodog.form('form#myform', {
211
+ params: {
212
+ auth: { key: 'YOUR_TRANSLOADIT_KEY' },
213
+ template_id: 'YOUR_TEMPLATE_ID',
214
+ },
215
+ waitForEncoding: true,
216
+ // ...
217
+ })
218
+ ```
126
219
 
127
- The `list` format means that the processed files will be saved as a flat list.
220
+ With the above setup, Robodog will send the assembly results to your controller
221
+ in the `transloadit` param, which we can parse out and save to our record. See
222
+ the [demo app] for an example of doing this.
223
+
224
+ ## Promotion
225
+
226
+ If you want Transloadit to also upload your cached original file to permanent
227
+ storage, you can skip promotion on the Shrine side:
128
228
 
129
229
  ```rb
130
- class PdfUploader < TransloaditUploader
131
- def transloadit_process(io, context)
132
- thumbs = transloadit_file(io)
133
- .add_step("thumbs", "/document/thumbs", ...)
134
- .multiple(:list) # marks that the result of this pipeline should be saved as a list
230
+ class VideoUploader < Shrine
231
+ Attacher.transloadit_processor do
232
+ import = file.transloadit_import_step
233
+ encode = transloadit_step "encode", "/video/encode", use: import
234
+ thumbs = transloadit_step "thumbs", "/video/thumbs", use: import
235
+ export = store.transloadit_export_step use: [import, encode, thumbs] # include original
236
+
237
+ assembly = transloadit.assembly(steps: [import, encode, thumbs, export])
238
+ assembly.create!
239
+ end
135
240
 
136
- transloadit_assembly(thumbs)
241
+ Attacher.transloadit_saver do |results|
242
+ stored = store.transloadit_file(results["import"])
243
+ transcoded = store.transloadit_file(results["encode"])
244
+ thumbnails = store.transloadit_files(results["thumbs"])
245
+
246
+ set(stored) # set promoted file
247
+ merge_derivatives(transcoded: transcoded, thumbnails: thumbnails)
137
248
  end
138
249
  end
139
250
  ```
140
-
141
- This will make the processing results save as an array of files:
142
-
143
251
  ```rb
144
- pdf.file #=>
145
- # [
146
- # #<Shrine::UploadedFile ...>
147
- # #<Shrine::UploadedFile ...>
148
- # ...
149
- # ]
252
+ class PromoteJob
253
+ include Sidekiq::Worker
150
254
 
151
- pdf.file[0] # page 1
152
- ```
255
+ def perform(attacher_class, record_class, record_id, name, file_data)
256
+ attacher_class = Object.const_get(attacher_class)
257
+ record = Object.const_get(record_class).find(record_id) # if using Active Record
153
258
 
154
- ### Webhooks
259
+ attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
155
260
 
156
- Transloadit performs its processing asynchronously, and you can provide a URL
157
- where you want Transloadit to POST results of processing once it's finished.
261
+ response = attacher.transloadit_process
262
+ response.reload_until_finished!
158
263
 
159
- ```rb
160
- class ImageUploader < TransloaditUploader
161
- def transloadit_process(io, context)
162
- # ...
163
- transloadit_assembly(files, notify_url: "http://myapp.com/webhooks/transloadit")
264
+ if response.error?
265
+ # handle error
266
+ end
267
+
268
+ attacher.transloadit_save(response["results"])
269
+ attacher.atomic_persist attacher.uploaded_file(file_data)
270
+ rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
271
+ attacher&.destroy_attached # delete orphaned processed files
164
272
  end
165
273
  end
166
274
  ```
167
275
 
168
- Then in your `POST /webhooks/transloadit` route you can call the plugin to
169
- automatically save the results to the attachment column in Shrine's format.
276
+ ## Skipping exports
277
+
278
+ If you want to use Transloadit only for processing, and prefer to store results
279
+ to yourself, you can do so with help of the [shrine-url] gem.
170
280
 
171
281
  ```rb
172
- post "/webhooks/transloadit" do
173
- TransloaditUploader::Attacher.transloadit_save(params)
174
- # return 200 status
175
- end
282
+ # Gemfile
283
+ gem "shrine-url"
176
284
  ```
285
+ ```rb
286
+ # ...
287
+ require "shrine/storage/url"
177
288
 
178
- Note that if you have CSRF protection, make sure that you skip verifying the
179
- CSRF token for this route.
289
+ Shrine.storages = {
290
+ # ...
291
+ url: Shrine::Storage::Url.new,
292
+ }
293
+ ```
180
294
 
181
- ### Direct uploads
295
+ If you don't specify an export step, Transloadit will return processed files
296
+ uploaded to Transloadit's temporary storage. You can load these results using
297
+ the `:url` storage, and then upload them to your permanent storage:
182
298
 
183
- Transloadit supports direct uploads, allowing you to do some processing on the
184
- file before it's submitted to the app. It's recommended that you use [Uppy] for
185
- client side uploads, take a look its [Transloadit plugin][uppy transloadit] for
186
- more details.
299
+ ```rb
300
+ class VideoUploader < Shrine
301
+ Attacher.transloadit_processor do
302
+ import = file.transloadit_import_step
303
+ encode = transloadit_step "encode", "/video/encode", use: import
304
+ thumbs = transloadit_step "thumbs", "/video/thumbs", use: import
305
+ # no export step
306
+
307
+ assembly = transloadit.assembly(steps: [import, encode, thumbs])
308
+ assembly.create!
309
+ end
187
310
 
188
- ```js
189
- // https://uppy.io/docs/transloadit/
190
- var uppy = Uppy.Core({})
191
- .use(Uppy.FileInput, {
192
- target: fileInput.parentNode,
193
- allowMultipleFiles: fileInput.multiple
194
- })
195
- .use(Uppy.Tus, {})
196
- .use(Uppy.Transloadit, {
197
- waitForEncoding: true,
198
- params: {
199
- auth: { key: 'YOUR_TRANSLOADIT_KEY' },
200
- steps: {
201
- // ...
202
- }
203
- }
204
- })
311
+ Attacher.transloadit_saver do |results|
312
+ url = shrine_class.new(:url)
313
+ transcoded = url.transloadit_file(results["encode"])
314
+ thumbnails = url.transloadit_files(results["thumbs"])
205
315
 
206
- uppy.run()
207
- ```
316
+ # results are uploaded to Transloadit's temporary storage
317
+ transcoded #=> #<Shrine::UploadedFile @storage_key=:url @id="https://tmp.transloadit.com/..." ...>
318
+ thumbnails #=> [#<Shrine::UploadedFile @storage_key=:url @id="https://tmp.transloadit.com/..." ...>, ...]
208
319
 
209
- When direct upload finishes Transloadit will return processing results, which
210
- you can use to construct the Shrine uploaded file data and send it as the
211
- Shrine attachment. Let's assume that we didn't provide and export step, in
212
- which case Transloadit will return temporary URL to the processed files, which
213
- we can use as the uploaded file identifier:
320
+ # upload results to permanent storage
321
+ add_derivatives(transcoded: transcoded, thumbnails: thumbnails)
322
+ end
323
+ end
324
+ ```
325
+ ```rb
326
+ response = attacher.transloadit_process
327
+ response.reload_until_finished!
214
328
 
215
- ```js
216
- uppy.on('transloadit:result', function (stepName, result) {
217
- var uploadedFileData = JSON.stringify({
218
- id: result['ssl_url'],
219
- storage: 'cache',
220
- metadata: {
221
- size: result['size'],
222
- filename: result['name'],
223
- mime_type: result['mime'],
224
- width: result['meta'] && result['meta']['width'],
225
- height: result['meta'] && result['meta']['height'],
226
- transloadit: result['meta'],
227
- }
228
- })
329
+ if response.error?
330
+ # handle error
331
+ end
229
332
 
230
- // send `uploadedFileData` as the Shrine attachment
231
- })
333
+ attacher.transloadit_save(response["results"])
334
+ attacher.derivatives #=>
335
+ # {
336
+ # transcoded: #<Shrine::UploadedFile storage_key=:store ...>,
337
+ # thumbnails: [
338
+ # #<Shrine::UploadedFile storage_key=:store ...>,
339
+ # #<Shrine::UploadedFile storage_key=:store ...>,
340
+ # ...
341
+ # ]
342
+ # }
232
343
  ```
233
344
 
234
- In order for using an URL as the uploaded file identifier to work, you'll need
235
- to set [shrine-url] as your temporary storage:
345
+ ## API
346
+
347
+ ### Processor
348
+
349
+ The processor is just a block registered under an identifier, which is expected
350
+ to create a Transloadit assembly:
236
351
 
237
352
  ```rb
238
- gem "shrine-url"
353
+ class VideoUploader < Shrine
354
+ Attacher.transloadit_processor :video do
355
+ # ...
356
+ end
357
+ end
239
358
  ```
240
359
 
360
+ It is executed when `Attacher#transloadit_process` is called:
361
+
241
362
  ```rb
242
- require "shrine/storage/url"
243
- Shrine.storages[:cache] = Shrine::Storage::Url.new
363
+ attacher.transloadit_process(:video) # calls :video processor
244
364
  ```
245
365
 
246
- See the **[demo app]** for a complete example of direct uploads.
247
-
248
- ### Templates
366
+ Any arguments passed to the processor will be given to the block:
249
367
 
250
- Transloadit recommends using [templates], since they allow you to replay failed
251
- assemblies, and also allow you not to expose credentials in your HTML.
368
+ ```rb
369
+ attacher.transloadit_process(:video, foo: "bar")
370
+ ```
371
+ ```rb
372
+ class VideoUploader < Shrine
373
+ Attacher.transloadit_processor :video do |options|
374
+ options #=> { :foo => "bar" }
375
+ end
376
+ end
377
+ ```
252
378
 
253
- Here is an example where the whole processing is defined inside a template,
254
- and we just set the location of the imported file.
379
+ The processor block is executed in context of a `Shrine::Attacher` instance:
255
380
 
256
381
  ```rb
257
- # Your Transloadit template saved as "my_template"
258
- {
259
- steps: {
260
- resize: {
261
- robot: "/image/resize",
262
- use: "import", # the "import" step will be passed in
263
- width: 800
264
- },
265
- export: {
266
- robot: "/s3/store",
267
- use: "resize",
268
- bucket: "YOUR_AWS_BUCKET",
269
- key: "YOUR_AWS_KEY",
270
- secret: "YOUR_AWS_SECRET",
271
- bucket_region: "YOUR_AWS_REGION",
272
- path: "videos/${unique_prefix}/${file.url_name}"
273
- }
274
- }
275
- }
382
+ class VideoUploader < Shrine
383
+ Attacher.transloadit_processor :video do
384
+ self #=> #<Shrine::Attacher>
385
+
386
+ record #=> #<Video>
387
+ name #=> :file
388
+ file #=> #<Shrine::UploadedFile>
389
+ end
390
+ end
276
391
  ```
392
+
393
+ ### Saver
394
+
395
+ The saver is just a block registered under an identifier, which is expected to
396
+ save given Transloadit results into the attacher:
397
+
277
398
  ```rb
278
- class ImageUplaoder < TransloaditUploader
279
- def transloadit_process(io, context)
280
- import = transloadit_import_step("import", io)
281
- transloadit_assembly("my_template", steps: [import])
399
+ class VideoUploader < Shrine
400
+ Attacher.transloadit_saver :video do |results|
401
+ # ...
282
402
  end
283
403
  end
284
404
  ```
285
405
 
286
- ### Backgrounding
287
-
288
- Even though submitting a Transloadit assembly doesn't require any uploading, it
289
- still does two HTTP requests, so you might want to put them into a background
290
- job. You can configure that in the `TransloaditUploader` base uploader class:
406
+ It is executed when `Attacher#transloadit_save` is called:
291
407
 
292
408
  ```rb
293
- class TransloaditUploader < Shrine
294
- plugin :transloadit,
295
- auth_key: "your transloadit key",
296
- auth_secret: "your transloadit secret"
409
+ attacher.transloadit_save(:video, results) # calls :video saver
410
+ ```
297
411
 
298
- Attacher.promote { |data| TransloaditJob.perform_async(data) }
412
+ Any arguments passed to the saver will be given to the block:
413
+
414
+ ```rb
415
+ attacher.transloadit_save(:video, results, foo: "bar")
416
+ ```
417
+ ```rb
418
+ class VideoUploader < Shrine
419
+ Attacher.transloadit_saver :video do |results, options|
420
+ options #=> { :foo => "bar" }
421
+ end
299
422
  end
300
423
  ```
424
+
425
+ The saver block is executed in context of a `Shrine::Attacher` instance:
426
+
301
427
  ```rb
302
- class TransloaditJob
303
- include Sidekiq::Worker
428
+ class VideoUploader < Shrine
429
+ Attacher.transloadit_saver :video do |results|
430
+ self #=> #<Shrine::Attacher>
304
431
 
305
- def perform(data)
306
- TransloaditUploader::Attacher.transloadit_process(data)
432
+ record #=> #<Video>
433
+ name #=> :file
434
+ file #=> #<Shrine::UploadedFile>
307
435
  end
308
436
  end
309
437
  ```
310
438
 
311
- ### Tracking progress
439
+ ### Step
312
440
 
313
- When an assembly is submitted, Transloadit returns a lot of useful information
314
- about the status of that assembly, which the plugin saves to the cached
315
- attachment's metadata.
441
+ You can generate `Transloadit::Step` objects with `Shrine.transloadit_step`:
316
442
 
317
443
  ```rb
318
- response = photo.image.transloadit_response
319
- response.body #=>
320
- # {
321
- # "ok" => "ASSEMBLY_EXECUTING",
322
- # "message" => "The assembly is currently being executed.",
323
- # "assembly_id" => "83d07d10414011e68cc8c5df79919836",
324
- # "assembly_url" => "http://api2.janani.transloadit.com/assemblies/83d07d10414011e68cc8c5df79919836",
325
- # "execution_start" => "2016/07/03 17:06:42 GMT",
326
- # "execution_duration" => 2.113,
327
- # "params" => "{\"steps\":{...}}",
328
- # ...
329
- # }
444
+ Shrine.transloadit_step "my_name", "/my/robot", **options
445
+ #=> #<Transloadit::Step name="my_name", robot="/my/robot", options={...}>
330
446
  ```
331
447
 
332
- At an point during the execution of the assembly you can refresh this
333
- information:
448
+ This method adds the ability to pass another `Transloadit::Step` object as the
449
+ `:use` parameter:
334
450
 
335
451
  ```rb
336
- response.finished? #=> false
337
- response.reload!
338
- response.finished? #=> true
452
+ step_one = Shrine.transloadit_step "one", "/robot/one"
453
+ step_two = Shrine.transloadit_step "two", "/robot/two", use: step_one
454
+ step_two.options[:use] #=> ["one"]
339
455
  ```
340
456
 
341
- ### Metadata
457
+ ### Import step
458
+
459
+ The `Shrine::UploadedFile#transloadit_import_step` method generates an import
460
+ step for the uploaded file:
461
+
462
+ ```rb
463
+ file = Shrine.upload(io, :store)
464
+ file.storage #=> #<Shrine::Storage::S3>
465
+ file.id #=> "foo"
342
466
 
343
- For each processed file Transloadit also extracts a great deal of useful
344
- metadata. When the Transloadit processing is finished and the results are saved
345
- as a Shrine attachment, this metadata will be automatically used to populate
346
- the attachment's metadata.
467
+ step = file.transloadit_import_step
468
+
469
+ step #=> #<Transloadit::Step ...>
470
+ step.name #=> "import"
471
+ step.robot #=> "/s3/import"
472
+
473
+ step.options[:path] #=> "foo"
474
+ step.options[:credentials] #=> :s3_store (inferred from the plugin setting)
475
+ ```
347
476
 
348
- Additionally the Transloadit's metadata hash will be saved in an additional
349
- metadata key, so that you can access any other values:
477
+ You can change the default step name:
350
478
 
351
479
  ```rb
352
- photo = Photo.create(image: image_file)
353
- photo.image.metadata["transloadit"] #=>
354
- # {
355
- # "date_recorded" => "2013/09/04 08:03:39",
356
- # "date_file_created" => "2013/09/04 12:03:39 GMT",
357
- # "date_file_modified" => "2016/07/11 02:27:11 GMT",
358
- # "aspect_ratio" => "1.504",
359
- # "city" => "Decatur",
360
- # "state" => "Georgia",
361
- # "country" => "United States",
362
- # "latitude" => 33.77519301,
363
- # "longitude" => -84.295608,
364
- # "orientation" => "Horizontal (normal)",
365
- # "colorspace" => "RGB",
366
- # "average_color" => "#8b8688",
367
- # ...
368
- # }
480
+ step = file.transloadit_import_step("my_import")
481
+ step.name #=> "my_import"
482
+ ```
483
+
484
+ You can also pass step options:
485
+
486
+ ```rb
487
+ step = file.transloadit_import_step(ignore_errors: ["meta"])
488
+ step.options[:ignore_errors] #=> ["meta"]
369
489
  ```
370
490
 
371
- ### Import & Export
491
+ The following import robots are currently supported:
372
492
 
373
- Every `TransloaditFile` needs to have an import and an export step. This plugin
374
- automatically generates those steps for you:
493
+ | Robot | Description |
494
+ | :----------- | :---------- |
495
+ | `/s3/import` | activated for `Shrine::Storage::S3` |
496
+ | `/http/import` | activated for any other storage which returns HTTP(S) URLs |
497
+ | `/ftp/import` | activated for any other storage which returns FTP URLs |
498
+
499
+ ### Export step
500
+
501
+ The `Shrine#transloadit_export_step` method generates an export step for the underlying
502
+ storage:
375
503
 
376
504
  ```rb
377
- transloadit_file(io)
505
+ uploader = Shrine.new(:store)
506
+ uploader.storage #=> #<Shrine::Storage::S3>
507
+
508
+ step = uploader.transloadit_export_step
378
509
 
379
- # is equivalent to
510
+ step #=> #<Transloadit::Step ...>
511
+ step.name #=> "export"
512
+ step.robot #=> "/s3/store"
380
513
 
381
- file = transloadit_file
382
- file.add_step(transloadit_import_step("import", io))
514
+ step.options[:credentials] #=> :s3_store (inferred from the plugin setting)
383
515
  ```
384
516
 
517
+ You can change the default step name:
518
+
385
519
  ```rb
386
- transloadit_assembly({ original: original, thumb: thumb })
520
+ step = uploader.transloadit_export_step("my_export")
521
+ step.name #=> "my_export"
522
+ ```
387
523
 
388
- # is equivalent to
524
+ You can also pass step options:
389
525
 
390
- transloadit_assembly({
391
- original: original.add_step(transloadit_export_step("export_original")),
392
- thumb: thumb.add_step(transloadit_export_step("export_thumb")),
393
- })
526
+ ```rb
527
+ step = file.transloadit_export_step(acl: "public-read")
528
+ step.options[:acl] #=> "public-read"
394
529
  ```
395
530
 
396
- If you want/need to generate these steps yourself, you can just use the
397
- expanded forms.
531
+ The following export robots are currently supported:
532
+
533
+ | Robot | Description |
534
+ | :---- | :---------- |
535
+ | `/s3/store` | activated for `Shrine::Storage::S3` |
536
+ | `/google/store` | activated for [`Shrine::Storage::GoogleCloudStorage`][shrine-gcs] |
537
+ | `/youtube/store` | activated for [`Shrine::Storage::YouTube`][shrine-youtube] |
398
538
 
399
- ### Errors
539
+ ### File
400
540
 
401
- Any errors that happen on assembly submission or during processing will be
402
- propagated as a `Shrine::Plugins::Transloadit::ResponseError`, which
403
- additionally carries the Transloadit response in the `#response` attribute.
541
+ The `Shrine#transloadit_file` method will convert a Transloadit result hash
542
+ into a `Shrine::UploadedFile` object:
404
543
 
405
544
  ```rb
406
- post "/webhooks/transloadit" do
407
- begin
408
- TransloaditUploader::Attacher.transloadit_save(params)
409
- rescue Shrine::Plugins::Transloadit::ResponseError => exception
410
- exception.response # include this response in your exception notification
411
- end
545
+ uploader = Shrine.new(:store)
546
+ uploader.storage #=> #<Shrine::Storage::S3>
547
+
548
+ file = uploader.transloadit_file(
549
+ "url" => "https://my-bucket.s3.amazonaws.com/foo",
412
550
  # ...
413
- end
551
+ )
552
+
553
+ file #=> #<Shrine::UploadedFile @id="foo" storage_key=:store ...>
554
+
555
+ file.storage #=> #<Shrine::Storage::S3>
556
+ file.id #=> "foo"
414
557
  ```
415
558
 
416
- ### Testing
559
+ You can use the plural `Shrine#transloadit_files` to convert an array of
560
+ results:
561
+
562
+ ```rb
563
+ files = uploader.transloadit_files [
564
+ { "url" => "https://my-bucket.s3.amazonaws.com/foo", ... },
565
+ { "url" => "https://my-bucket.s3.amazonaws.com/bar", ... },
566
+ { "url" => "https://my-bucket.s3.amazonaws.com/baz", ... },
567
+ ]
417
568
 
418
- In development or test environment you cannot use webhooks, because Transloadit
419
- as an external service cannot access your localhost. In this case you can just
420
- do polling:
569
+ files #=>
570
+ # [
571
+ # #<Shrine::UploadedFile @id="foo" @storage_key=:store ...>,
572
+ # #<Shrine::UploadedFile @id="bar" @storage_key=:store ...>,
573
+ # #<Shrine::UploadedFile @id="baz" @storage_key=:store ...>,
574
+ # ]
575
+ ```
576
+
577
+ It will include basic metadata:
421
578
 
422
579
  ```rb
423
- class MyUploader < TransloaditUploader
424
- def transloadit_process(io, context)
425
- # ...
580
+ file = uploader.transloadit_file(
581
+ # ...
582
+ "name" => "matrix.mp4",
583
+ "size" => 44198,
584
+ "mime" => "video/mp4",
585
+ )
586
+
587
+ file.original_filename #=> "matrix.mp4"
588
+ file.size #=> 44198
589
+ file.mime_type #=> "video/mp4"
590
+ ```
426
591
 
427
- if ENV["RACK_ENV"] == "production"
428
- notify_url = "https://myapp.com/webhooks/transloadit"
429
- else
430
- # In development we cannot receive webhooks, because Transloadit as an
431
- # external service cannot reach our localhost.
432
- end
592
+ It will also merge any custom metadata:
433
593
 
434
- transloadit_assembly(files, context: context, notify_url: notify_url)
435
- end
436
- end
594
+ ```rb
595
+ file = uploader.transloadit_file(
596
+ # ...
597
+ "meta" => { "duration" => 9000, ... },
598
+ )
599
+
600
+ file["duration"] #=> 9000
437
601
  ```
438
602
 
603
+ Currently only `Shrine::Stroage::S3` is supported. However, you can still
604
+ handle other remote files using [`Shrine::Storage::Url`][shrine-url]:
605
+
439
606
  ```rb
440
- class TransloaditJob
441
- include Sidekiq::Worker
607
+ Shrine.storages => {
608
+ # ...
609
+ url: Shrine::Storage::Url.new,
610
+ }
611
+ ```
612
+ ```rb
613
+ uploader = Shrine.new(:url)
614
+ uploader #=> #<Shrine::Storage::Url>
442
615
 
443
- def perform(data)
444
- attacher = TransloaditUploader::Attacher.transloadit_process(data)
616
+ file = uploader.transloadit_file(
617
+ "url" => "https://example.com/foo",
618
+ # ...
619
+ )
445
620
 
446
- # Webhooks won't work in development, so we can just use polling.
447
- unless ENV["RACK_ENV"] == "production"
448
- response = attacher.get.transloadit_response
449
- response.reload_until_finished!
450
- attacher.transloadit_save(response.body)
451
- end
452
- end
453
- end
621
+ file.id #=> "https://example.com/foo"
454
622
  ```
455
623
 
456
- ## Contributing
624
+ ## Instrumentation
457
625
 
458
- Before you can run tests, you need to first create an `.env` file in the
459
- project root containing your Transloadit and Amazon S3 credentials:
626
+ If the `instrumentation` plugin has been loaded, the `transloadit` plugin adds
627
+ instrumentation around triggering processing.
460
628
 
461
- ```sh
462
- # .env
463
- TRANSLOADIT_KEY="..."
464
- TRANSLOADIT_SECRET="..."
465
- S3_BUCKET="..."
466
- S3_REGION="..."
467
- S3_ACCESS_KEY_ID="..."
468
- S3_SECRET_ACCESS_KEY="..."
629
+ ```rb
630
+ # instrumentation plugin needs to be loaded *before* transloadit
631
+ plugin :instrumentation
632
+ plugin :transloadit
469
633
  ```
470
634
 
471
- Afterwards you can run the tests:
635
+ Calling the processor will trigger a `transloadit.shrine` event with the
636
+ following payload:
637
+
638
+ | Key | Description |
639
+ | :---- | :---------- |
640
+ | `:processor` | Name of the processor |
641
+ | `:uploader` | The uploader class that sent the event |
642
+
643
+ A default log subscriber is added as well which logs these events:
644
+
645
+ ```
646
+ Transloadit (1238ms) – {:processor=>:video, :uploader=>VideoUploader}
647
+ ```
648
+
649
+ You can also use your own log subscriber:
650
+
651
+ ```rb
652
+ plugin :transloadit, log_subscriber: -> (event) {
653
+ Shrine.logger.info JSON.generate(name: event.name, duration: event.duration, **event.payload)
654
+ }
655
+ ```
656
+ ```
657
+ {"name":"transloadit","duration":1238,"processor":"video","uploader":"VideoUploader"}
658
+ ```
659
+
660
+ Or disable logging altogether:
661
+
662
+ ```rb
663
+ plugin :transloadit, log_subscriber: nil
664
+ ```
665
+
666
+ ## Contributing
667
+
668
+ Tests are run with:
472
669
 
473
670
  ```sh
474
671
  $ bundle exec rake test
@@ -480,12 +677,18 @@ $ bundle exec rake test
480
677
 
481
678
  [Shrine]: https://github.com/shrinerb/shrine
482
679
  [Transloadit]: https://transloadit.com/
483
- [many storage services]: https://transloadit.com/docs/conversion-robots/#file-export-robots
484
- [transloadit gem]: https://github.com/transloadit/ruby-sdk
485
- [robot and arguments]: https://transloadit.com/docs/conversion-robots/
486
- [templates]: https://transloadit.com/docs/#templates
487
- [Uppy]: https://uppy.io
488
- [uppy transloadit]: https://uppy.io/docs/transloadit/
489
- [demo app]: /demo
490
- [direct uploads to Transloadit]: #direct-uploads
680
+ [Ruby SDK]: https://github.com/transloadit/ruby-sdk
681
+ [credentials]: https://transloadit.com/docs/#16-template-credentials
682
+ [import robots]: https://transloadit.com/docs/transcoding/#overview-service-file-importing
683
+ [export robots]: https://transloadit.com/docs/transcoding/#overview-service-file-exporting
684
+ [derivatives]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/derivatives.md#readme
685
+ [assembly notifications]: https://transloadit.com/docs/#24-assembly-notifications
686
+ [backgrounding]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/backgrounding.md#readme
687
+ [shrine-url]: https://github.com/shrinerb/shrine-url
688
+ [Robodog]: https://uppy.io/docs/robodog/
689
+ [Robodog Form]: https://uppy.io/docs/robodog/form/
690
+ [Uppy]: https://uppy.io/
691
+ [atomic_helpers]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/atomic_helpers.md#readme
692
+ [shrine-gcs]: https://github.com/renchap/shrine-google_cloud_storage
693
+ [shrine-youtube]: https://github.com/thedyrt/shrine-storage-you_tube
491
694
  [shrine-url]: https://github.com/shrinerb/shrine-url