shrine-transloadit 0.5.0 → 1.0.1

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