shrine-transloadit 0.5.1 → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 72d852ce420b15983f981ac19cd6bd8f3b5d5f5d5750e9843a6ee88f8c6d4a4b
4
- data.tar.gz: e3dbef98ddbf4b8b6897590371020a214576abfb4a091ed29ff2c92d6d2908e3
3
+ metadata.gz: 379b39a12a74f5b4e3e0c9336c1cc560eaa5ce78596fc55aa8990ea017d632bf
4
+ data.tar.gz: '04930268d5f2c682dac35cb635fd2e0be617fc6ce1c3bd55b0ee572f771b300f'
5
5
  SHA512:
6
- metadata.gz: 640d10e9ee8d7ddd884166dd1d8d0f26c0bf16ee3b22e25bf2c605e1677230c4c75a123aefdf84799350fc786136cf886f820bec9b318e1a73c5707122ac792e
7
- data.tar.gz: dbf511fd3c4382c4ba11cacc38988e7e1542df5254f9d5c0842d64fc44799ef78d3ff132db766ec1b88a1e4abe1dc8dab6d74425fa81e9b33f29b8e3ef8149c7
6
+ metadata.gz: 0bc371cbec27b5cf8a642eedea74c372e5b1bbef48b82956635a28dd76b02eb1436870a5ba3b18671f26bc897199c77c995df16070704688fecb3d42d11b8f74
7
+ data.tar.gz: 3da98b2895dd0af05e9f6aac2cc835105b754ef91e0ae0cd528b6cd7ace93a9ac82e8b7dc1ae5dc22b928d24caff89ebb3203b9a97735ac69349e97249ec89b4
data/README.md CHANGED
@@ -1,474 +1,608 @@
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.
7
+ generate audio waveforms and more.
8
+
9
+ ## Contents
10
+
11
+ * [Installation](#installation)
12
+ * [Setup](#setup)
13
+ - [Credentials](#credentials)
14
+ * [Usage](#usge)
15
+ - [Backgrounding](#backgrounding)
16
+ * [Notifications](#notifications)
17
+ * [Direct uploads](#direct-uploads)
18
+ * [Promotion](#promotion)
19
+ * [Skipping exports](#skipping-exports)
20
+ * [API](#api)
21
+ - [Processing & Saving](#processing-saving)
22
+ - [Generating steps](#generating-steps)
23
+ - [Parsing files](#parsing-files)
24
+ - [Verifying signature](#verifying-signature)
25
+ - [Transloadit instance](#transloadit-instance)
26
+
27
+ ## Installation
28
+
29
+ Put the gem in your Gemfile:
30
+
31
+ ```rb
32
+ # Gemfile
33
+ gem "shrine-transloadit", "~> 1.0"
34
+ ```
9
35
 
10
36
  ## Setup
11
37
 
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:
38
+ Load the `transloadit` plugin and configure your Transloadit key and secret:
16
39
 
17
40
  ```rb
18
- gem "shrine"
19
- gem "aws-sdk-s3", "~> 1.2" # for Amazon S3
20
- gem "shrine-transloadit"
41
+ Shrine.plugin :transloadit, auth: {
42
+ key: "YOUR_TRANSLOADIT_KEY",
43
+ secret: "YOUR_TRANSLOADIT_SECRET",
44
+ }
21
45
  ```
22
46
 
23
- ```rb
24
- require "shrine"
25
- require "shrine/storage/s3"
47
+ ### Credentials
26
48
 
27
- s3_options = {
28
- bucket: "my-bucket",
29
- region: "my-region",
30
- access_key_id: "abc",
31
- secret_access_key: "xyz",
32
- }
49
+ You'll need to create [credentials] for the storage services you want to import
50
+ from and export to. Then you need to map these credentials to Shrine storages:
33
51
 
52
+ ```rb
34
53
  Shrine.storages = {
35
- cache: Shrine::Storage::S3.new(prefix: "cache", **s3_options),
36
- store: Shrine::Storage::S3.new(prefix: "store", **s3_options),
54
+ cache: Shrine::Storage::S3.new(prefix: "cache", **options),
55
+ store: Shrine::Storage::S3.new(**options),
37
56
  }
38
57
 
39
- class TransloaditUploader < Shrine
40
- plugin :transloadit,
41
- auth_key: "your transloadit key",
42
- auth_secret: "your transloadit secret"
43
- end
58
+ Shrine.plugin :transloadit, auth: { ... },
59
+ credentials: {
60
+ cache: :s3_store, # use "s3_store" credentials for :cache storage
61
+ store: :s3_store, # use "s3_store" credentials for :store storage
62
+ }
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.
49
-
50
- ## How it works
51
-
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.
65
+ ### Derivatives
56
66
 
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.
67
+ The examples will assume you have the [`derivatives`][derivatives] plugin
68
+ loaded:
62
69
 
63
- The **[demo app]** shows a complete implementation of this flow, and can serve
64
- as a good baseline for your own implementation.
70
+ ```rb
71
+ Shrine.plugin :derivatives
72
+ ```
65
73
 
66
74
  ## Usage
67
75
 
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.
76
+ The `transloadit` plugin provides helper methods for creating [import][import
77
+ robots] and [export][export robots] steps, as well as for parsing out exported
78
+ files from results.
71
79
 
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.
80
+ Here is a basic example where we kick off transcoding and thumbnail extraction
81
+ from an attached video, wait for assembly to complete, then save processed
82
+ files as derivatives:
75
83
 
76
84
  ```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)
85
+ class VideoUploader < Shrine
86
+ Attacher.transloadit_processor :video do
87
+ import = file.transloadit_import_step
88
+ encode = transloadit_step "encode", "/video/encode", use: import
89
+ thumbs = transloadit_step "thumbs", "/video/thumbs", use: import
90
+ export = store.transloadit_export_step use: [encode, thumbs]
91
+
92
+ assembly = transloadit.assembly(steps: [import, encode, thumbs, export])
93
+ assembly.create!
94
+ end
81
95
 
82
- transloadit_assembly(resized, context: context)
96
+ Attacher.transloadit_saver :video do |response|
97
+ transcoded = store.transloadit_file(response["results"]["encode"])
98
+ thumbnails = store.transloadit_files(response["results"]["thumbs"])
99
+
100
+ merge_derivatives(transcoded: transcoded, thumbnails: thumbnails)
83
101
  end
84
102
  end
85
103
  ```
104
+ ```rb
105
+ response = attacher.transloadit_process(:video)
106
+ response.reload_until_finished!
86
107
 
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.
108
+ if response.error?
109
+ # handle error
110
+ end
90
111
 
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.
112
+ attacher.transloadit_save(:video, response)
113
+ attacher.derivatives #=>
114
+ # {
115
+ # transcoded: #<Shrine::UploadedFile storage_key=:store ...>,
116
+ # thumbnails: [
117
+ # #<Shrine::UploadedFile storage_key=:store ...>,
118
+ # #<Shrine::UploadedFile storage_key=:store ...>,
119
+ # ...
120
+ # ]
121
+ # }
122
+ ```
96
123
 
97
- ### Versions
124
+ ### Backgrounding
98
125
 
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.
126
+ When using [backgrounding], it's probably best to create the assembly after
127
+ promotion:
101
128
 
102
129
  ```rb
103
- class ImageUploader < TransloaditUploader
104
- plugin :versions
130
+ class PromoteJob
131
+ def perform(record, name, file_data)
132
+ attacher = Shrine::Attacher.retrieve(model: record, name: name, file: file_data)
133
+ attacher.atomic_promote
134
+ attacher.transloadit_process(:video)
135
+ # ...
136
+ rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
137
+ end
138
+ end
139
+ ```
105
140
 
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)
141
+ ## Notifications
110
142
 
111
- files = { original: original, medium: medium, small: small }
143
+ When using [assembly notifications], the attacher data can be sent to the
144
+ webhook via `:fields`:
112
145
 
113
- transloadit_assembly(files, context: context)
114
- end
146
+ ```rb
147
+ Attacher.transloadit_processor :video do
148
+ # ...
149
+ assembly = transloadit.assembly(
150
+ steps: [ ... ],
151
+ notify_url: "https://example.com/webhooks/transloadit",
152
+ fields: {
153
+ attacher: {
154
+ record_class: record.class,
155
+ record_id: record.id,
156
+ name: name,
157
+ data: file_data,
158
+ }
159
+ }
160
+ )
161
+ assembly.create!
115
162
  end
116
163
  ```
117
164
 
118
- ### Multiple files
165
+ Then in the webhook handler we can load the attacher and [atomically
166
+ persist][atomic_helpers] assembly results. If during processing the attachment
167
+ has changed or record was deleted, we make sure we delete processed files.
119
168
 
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.
169
+ ```rb
170
+ post "/transloadit/video" do
171
+ Shrine.transloadit_verify!(params) # verify transloadit signature
124
172
 
125
- #### `list`
173
+ response = JSON.parse(params["transloadit"])
126
174
 
127
- The `list` format means that the processed files will be saved as a flat list.
175
+ record_class, record_id, name, file_data = response["fields"]["attacher"].values
176
+ record_class = Object.const_get(record_class)
128
177
 
129
- ```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
178
+ begin
179
+ record = record_class.find(record_id)
180
+ attacher = Shrine::Attacher.retrieve(model: record, name: name, file: file_data)
135
181
 
136
- transloadit_assembly(thumbs)
182
+ attacher.transloadit_save(:video, response)
183
+
184
+ attacher.atomic_persist
185
+ rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
186
+ unless attacher
187
+ attacher = record_class.send(:"#{name}_attacher")
188
+ attacher.transloadit_save(:video, response)
189
+ end
190
+
191
+ attacher.destroy(background: true) # delete orphaned files
137
192
  end
193
+
194
+ # return successful response for Transloadit
195
+ status 200
138
196
  end
139
197
  ```
140
198
 
141
- This will make the processing results save as an array of files:
199
+ Note that if you have CSRF protection, make sure that you skip verifying the
200
+ CSRF token for this route.
201
+
202
+ ## Direct uploads
142
203
 
143
- ```rb
144
- pdf.file #=>
145
- # [
146
- # #<Shrine::UploadedFile ...>
147
- # #<Shrine::UploadedFile ...>
148
- # ...
149
- # ]
204
+ Transloadit supports client side uploads via [Robodog], an [Uppy]-based
205
+ JavaScript library.
150
206
 
151
- pdf.file[0] # page 1
207
+ If you have an HTML form, you can use Robodog's [Form API][Robodog Form] to add
208
+ Transloadit's encoding capabilities to it:
209
+
210
+ ```js
211
+ window.Robodog.form('form#myform', {
212
+ params: {
213
+ auth: { key: 'YOUR_TRANSLOADIT_KEY' },
214
+ template_id: 'YOUR_TEMPLATE_ID',
215
+ },
216
+ waitForEncoding: true,
217
+ // ...
218
+ })
152
219
  ```
153
220
 
154
- ### Webhooks
221
+ With the above setup, Robodog will send the assembly results to your controller
222
+ in the `transloadit` param, which we can parse out and save to our record. See
223
+ the [demo app] for an example of doing this.
224
+
225
+ ## Promotion
155
226
 
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.
227
+ If you want Transloadit to also upload your cached original file to permanent
228
+ storage, you can skip promotion on the Shrine side:
158
229
 
159
230
  ```rb
160
- class ImageUploader < TransloaditUploader
161
- def transloadit_process(io, context)
162
- # ...
163
- transloadit_assembly(files, notify_url: "http://myapp.com/webhooks/transloadit")
231
+ class VideoUploader < Shrine
232
+ Attacher.transloadit_processor :video do
233
+ import = file.transloadit_import_step
234
+ encode = transloadit_step "encode", "/video/encode", use: import
235
+ thumbs = transloadit_step "thumbs", "/video/thumbs", use: import
236
+ export = store.transloadit_export_step use: [import, encode, thumbs] # include original
237
+
238
+ assembly = transloadit.assembly(use: [import, encode, thumbs, export])
239
+ assembly.create!
240
+ end
241
+
242
+ Attacher.transloadit_saver :video do |response|
243
+ stored = store.transloadit_file(response["results"]["import"])
244
+ transcoded = store.transloadit_file(response["results"]["encode"])
245
+ thumbnails = store.transloadit_files(response["results"]["thumbs"])
246
+
247
+ set(stored) # set promoted file
248
+ merge_derivatives(transcoded: transcoded, thumbnails: thumbnails)
164
249
  end
165
250
  end
166
251
  ```
252
+ ```rb
253
+ class PromoteJob
254
+ def perform(record, name, file_data)
255
+ attacher = Shrine::Attacher.retrieve(model: record, name: name, file: file_data)
167
256
 
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.
257
+ response = attacher.transloadit_process(:video)
258
+ response.reload_until_finished!
170
259
 
171
- ```rb
172
- post "/webhooks/transloadit" do
173
- TransloaditUploader::Attacher.transloadit_save(params)
174
- # return 200 status
260
+ if response.error?
261
+ # handle error
262
+ end
263
+
264
+ original_file = attacher.file
265
+ attacher.transloadit_save(:video, response)
266
+
267
+ attacher.atomic_persist(original_file)
268
+ rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
269
+ # delete orphaned processed files (backgrounding plugin is recommended)
270
+ attacher.destroy(background: true) if attacher
271
+ end
175
272
  end
176
273
  ```
177
274
 
178
- Note that if you have CSRF protection, make sure that you skip verifying the
179
- CSRF token for this route.
180
-
181
- ### Direct uploads
275
+ ## Skipping exports
182
276
 
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.
277
+ If you want to use Transloadit only for processing, and prefer to store results
278
+ to yourself, you can do so with help of the [shrine-url] gem.
187
279
 
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
- })
280
+ ```rb
281
+ # Gemfile
282
+ gem "shrine-url"
283
+ ```
284
+ ```rb
285
+ # ...
286
+ require "shrine/storage/url"
205
287
 
206
- uppy.run()
288
+ Shrine.storages = {
289
+ # ...
290
+ url: Shrine::Storage::Url.new,
291
+ }
207
292
  ```
208
293
 
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:
294
+ If you don't specify an export step, Transloadit will return processed files
295
+ uploaded to Transloadit's temporary storage. You can load these results using
296
+ the `:url` storage, and then upload them to your permanent storage:
214
297
 
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
- })
298
+ ```rb
299
+ class VideoUploader < Shrine
300
+ Attacher.transloadit_processor :video do
301
+ import = file.transloadit_import_step
302
+ encode = transloadit_step "encode", "/video/encode", use: import
303
+ thumbs = transloadit_step "thumbs", "/video/thumbs", use: import
304
+ # no export step
305
+
306
+ assembly = transloadit.assembly(steps: [import, encode, thumbs])
307
+ assembly.create!
308
+ end
229
309
 
230
- // send `uploadedFileData` as the Shrine attachment
231
- })
232
- ```
310
+ Attacher.transloadit_saver :video do |response|
311
+ url = shrine_class.new(:url)
312
+ transcoded = url.transloadit_file(response["results"]["encode"])
313
+ thumbnails = url.transloadit_files(response["results"]["thumbs"])
233
314
 
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:
315
+ # results are uploaded to Transloadit's temporary storage
316
+ transcoded #=> #<Shrine::UploadedFile @storage_key=:url @id="https://tmp.transloadit.com/..." ...>
317
+ thumbnails #=> [#<Shrine::UploadedFile @storage_key=:url @id="https://tmp.transloadit.com/..." ...>, ...]
236
318
 
319
+ # upload results to permanent storage
320
+ add_derivatives(transcoded: transcoded, thumbnails: thumbnails)
321
+ end
322
+ end
323
+ ```
237
324
  ```rb
238
- gem "shrine-url"
325
+ response = attacher.transloadit_process(:video)
326
+ response.reload_until_finished!
327
+
328
+ if response.error?
329
+ # handle error
330
+ end
331
+
332
+ attacher.transloadit_save(:video, response)
333
+ attacher.derivatives #=>
334
+ # {
335
+ # transcoded: #<Shrine::UploadedFile storage_key=:store ...>,
336
+ # thumbnails: [
337
+ # #<Shrine::UploadedFile storage_key=:store ...>,
338
+ # #<Shrine::UploadedFile storage_key=:store ...>,
339
+ # ...
340
+ # ]
341
+ # }
239
342
  ```
240
343
 
344
+ ## API
345
+
346
+ ### Processor
347
+
348
+ The processor is just a block registered under an identifier, which is expected
349
+ to create a Transloadit assembly:
350
+
241
351
  ```rb
242
- require "shrine/storage/url"
243
- Shrine.storages[:cache] = Shrine::Storage::Url.new
352
+ class VideoUploader < Shrine
353
+ Attacher.transloadit_processor :video do
354
+ # ...
355
+ end
356
+ end
244
357
  ```
245
358
 
246
- See the **[demo app]** for a complete example of direct uploads.
247
-
248
- ### Templates
359
+ It is executed when `Attacher#transloadit_process` is called:
249
360
 
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.
361
+ ```rb
362
+ attacher.transloadit_process(:video) # calls :video processor
363
+ ```
252
364
 
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.
365
+ Any arguments passed to the processor will be given to the block:
255
366
 
256
367
  ```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
- }
368
+ attacher.transloadit_process(:video, foo: "bar")
276
369
  ```
277
370
  ```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])
371
+ class VideoUploader < Shrine
372
+ Attacher.transloadit_processor :video do |options|
373
+ options #=> { :foo => "bar" }
282
374
  end
283
375
  end
284
376
  ```
285
377
 
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:
378
+ The processor block is executed in context of a `Shrine::Attacher` instance:
291
379
 
292
380
  ```rb
293
- class TransloaditUploader < Shrine
294
- plugin :transloadit,
295
- auth_key: "your transloadit key",
296
- auth_secret: "your transloadit secret"
381
+ class VideoUploader < Shrine
382
+ Attacher.transloadit_processor :video do
383
+ self #=> #<Shrine::Attacher>
297
384
 
298
- Attacher.promote { |data| TransloaditJob.perform_async(data) }
385
+ record #=> #<Video>
386
+ name #=> :file
387
+ file #=> #<Shrine::UploadedFile>
388
+ end
299
389
  end
300
390
  ```
301
- ```rb
302
- class TransloaditJob
303
- include Sidekiq::Worker
304
391
 
305
- def perform(data)
306
- TransloaditUploader::Attacher.transloadit_process(data)
392
+ ### Saver
393
+
394
+ The saver is just a block registered under an identifier, which is expected to
395
+ save given Transloadit results into the attacher:
396
+
397
+ ```rb
398
+ class VideoUploader < Shrine
399
+ Attacher.transloadit_saver :video do |results|
400
+ # ...
307
401
  end
308
402
  end
309
403
  ```
310
404
 
311
- ### Tracking progress
405
+ It is executed when `Attacher#transloadit_save` is called:
406
+
407
+ ```rb
408
+ attacher.transloadit_save(:video, results) # calls :video saver
409
+ ```
312
410
 
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.
411
+ Any arguments passed to the saver will be given to the block:
316
412
 
317
413
  ```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
- # }
414
+ attacher.transloadit_save(:video, results, foo: "bar")
415
+ ```
416
+ ```rb
417
+ class VideoUploader < Shrine
418
+ Attacher.transloadit_saver :video do |results, options|
419
+ options #=> { :foo => "bar" }
420
+ end
421
+ end
330
422
  ```
331
423
 
332
- At an point during the execution of the assembly you can refresh this
333
- information:
424
+ The saver block is executed in context of a `Shrine::Attacher` instance:
334
425
 
335
426
  ```rb
336
- response.finished? #=> false
337
- response.reload!
338
- response.finished? #=> true
427
+ class VideoUploader < Shrine
428
+ Attacher.transloadit_saver :video do |results|
429
+ self #=> #<Shrine::Attacher>
430
+
431
+ record #=> #<Video>
432
+ name #=> :file
433
+ file #=> #<Shrine::UploadedFile>
434
+ end
435
+ end
339
436
  ```
340
437
 
341
- ### Metadata
438
+ ### Step
342
439
 
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.
440
+ You can generate `Transloadit::Step` objects with `Shrine.transloadit_step`:
347
441
 
348
- Additionally the Transloadit's metadata hash will be saved in an additional
349
- metadata key, so that you can access any other values:
442
+ ```rb
443
+ Shrine.transloadit_step "my_name", "/my/robot", **options
444
+ #=> #<Transloadit::Step name="my_name", robot="/my/robot", options={...}>
445
+ ```
446
+
447
+ This method adds the ability to pass another `Transloadit::Step` object as the
448
+ `:use` parameter:
350
449
 
351
450
  ```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
- # }
451
+ step_one = Shrine.transloadit_step "one", "/robot/one"
452
+ step_two = Shrine.transloadit_step "two", "/robot/two", use: step_one
453
+ step_two.options[:use] #=> ["one"]
369
454
  ```
370
455
 
371
- ### Import & Export
456
+ ### Import step
372
457
 
373
- Every `TransloaditFile` needs to have an import and an export step. This plugin
374
- automatically generates those steps for you:
458
+ The `Shrine::UploadedFile#transloadit_import_step` method generates an import
459
+ step for the uploaded file:
375
460
 
376
461
  ```rb
377
- transloadit_file(io)
462
+ file = Shrine.upload(io, :store)
463
+ file.storage #=> #<Shrine::Storage::S3>
464
+ file.id #=> "foo"
378
465
 
379
- # is equivalent to
466
+ step = file.transloadit_import_step
380
467
 
381
- file = transloadit_file
382
- file.add_step(transloadit_import_step("import", io))
468
+ step #=> #<Transloadit::Step ...>
469
+ step.name #=> "import"
470
+ step.robot #=> "/s3/import"
471
+
472
+ step.options[:path] #=> "foo"
473
+ step.options[:credentials] #=> :s3_store (inferred from the plugin setting)
383
474
  ```
384
475
 
476
+ You can change the default step name:
477
+
385
478
  ```rb
386
- transloadit_assembly({ original: original, thumb: thumb })
479
+ step = file.transloadit_import_step("my_import")
480
+ step.name #=> "my_import"
481
+ ```
387
482
 
388
- # is equivalent to
483
+ You can also pass step options:
389
484
 
390
- transloadit_assembly({
391
- original: original.add_step(transloadit_export_step("export_original")),
392
- thumb: thumb.add_step(transloadit_export_step("export_thumb")),
393
- })
485
+ ```rb
486
+ step = file.transloadit_import_step(ignore_errors: ["meta"])
487
+ step.options[:ignore_errors] #=> ["meta"]
394
488
  ```
395
489
 
396
- If you want/need to generate these steps yourself, you can just use the
397
- expanded forms.
490
+ The following import robots are currently supported:
398
491
 
399
- ### Errors
492
+ | Robot | Description |
493
+ | :----------- | :---------- |
494
+ | `/s3/import` | activated for `Shrine::Storage::S3` |
495
+ | `/http/import` | activated for any other storage which returns HTTP(S) URLs |
496
+ | `/ftp/import` | activated for any other storage which returns FTP URLs |
400
497
 
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.
498
+ ### Export step
499
+
500
+ The `Shrine#transloadit_export_step` method generates an export step for the underlying
501
+ storage:
404
502
 
405
503
  ```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
412
- # ...
413
- end
504
+ uploader = Shrine.new(:store)
505
+ uploader.storage #=> #<Shrine::Storage::S3>
506
+
507
+ step = uploader.transloadit_export_step
508
+
509
+ step #=> #<Transloadit::Step ...>
510
+ step.name #=> "export"
511
+ step.robot #=> "/s3/store"
512
+
513
+ step.options[:credentials] #=> :s3_store (inferred from the plugin setting)
414
514
  ```
415
515
 
416
- ### Testing
516
+ You can change the default step name:
417
517
 
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:
518
+ ```rb
519
+ step = uploader.transloadit_export_step("my_export")
520
+ step.name #=> "my_export"
521
+ ```
522
+
523
+ You can also pass step options:
421
524
 
422
525
  ```rb
423
- class MyUploader < TransloaditUploader
424
- def transloadit_process(io, context)
425
- # ...
526
+ step = file.transloadit_export_step(acl: "public-read")
527
+ step.options[:acl] #=> "public-read"
528
+ ```
426
529
 
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
530
+ The following export robots are currently supported:
433
531
 
434
- transloadit_assembly(files, context: context, notify_url: notify_url)
435
- end
436
- end
532
+ | Robot | Description |
533
+ | :---- | :---------- |
534
+ | `/s3/store` | activated for `Shrine::Storage::S3` |
535
+ | `/google/store` | activated for [`Shrine::Storage::GoogleCloudStorage`][shrine-gcs] |
536
+ | `/youtube/store` | activated for [`Shrine::Storage::YouTube`][shrine-youtube] |
537
+
538
+ ### File
539
+
540
+ The `Shrine#transloadit_file` method will convert a Transloadit result hash
541
+ into a `Shrine::UploadedFile` object:
542
+
543
+ ```rb
544
+ uploader = Shrine.new(:store)
545
+ uploader.storage #=> #<Shrine::Storage::S3>
546
+
547
+ file = uploader.transloadit_file(
548
+ "url" => "https://my-bucket.s3.amazonaws.com/foo",
549
+ # ...
550
+ )
551
+
552
+ file.storage #=> #<Shrine::Storage::S3>
553
+ file.id #=> "foo"
437
554
  ```
438
555
 
556
+ It will include basic metadata:
557
+
439
558
  ```rb
440
- class TransloaditJob
441
- include Sidekiq::Worker
559
+ file = uploader.transloadit_file(
560
+ # ...
561
+ "name" => "matrix.mp4",
562
+ "size" => 44198,
563
+ "mime" => "video/mp4",
564
+ )
565
+
566
+ file.original_filename #=> "matrix.mp4"
567
+ file.size #=> 44198
568
+ file.mime_type #=> "video/mp4"
569
+ ```
442
570
 
443
- def perform(data)
444
- attacher = TransloaditUploader::Attacher.transloadit_process(data)
571
+ It will also merge any custom metadata:
445
572
 
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
573
+ ```rb
574
+ file = uploader.transloadit_file(
575
+ # ...
576
+ "meta" => { "duration" => 9000, ... },
577
+ )
578
+
579
+ file["duration"] #=> 9000
454
580
  ```
455
581
 
456
- ## Contributing
582
+ Currently only `Shrine::Stroage::S3` is supported. However, you can still
583
+ handle other remote files using [`Shrine::Storage::Url`][shrine-url]:
457
584
 
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:
585
+ ```rb
586
+ Shrine.storages => {
587
+ # ...
588
+ url: Shrine::Storage::Url.new,
589
+ }
590
+ ```
591
+ ```rb
592
+ uploader = Shrine.new(:url)
593
+ uploader #=> #<Shrine::Storage::Url>
460
594
 
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="..."
595
+ file = uploader.transloadit_file(
596
+ "url" => "https://example.com/foo",
597
+ # ...
598
+ )
599
+
600
+ file.id #=> "https://example.com/foo"
469
601
  ```
470
602
 
471
- Afterwards you can run the tests:
603
+ ## Contributing
604
+
605
+ Tests are run with:
472
606
 
473
607
  ```sh
474
608
  $ bundle exec rake test
@@ -480,12 +614,18 @@ $ bundle exec rake test
480
614
 
481
615
  [Shrine]: https://github.com/shrinerb/shrine
482
616
  [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
617
+ [Ruby SDK]: https://github.com/transloadit/ruby-sdk
618
+ [credentials]: https://transloadit.com/docs/#16-template-credentials
619
+ [import robots]: https://transloadit.com/docs/transcoding/#overview-service-file-importing
620
+ [export robots]: https://transloadit.com/docs/transcoding/#overview-service-file-exporting
621
+ [derivatives]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/derivatives.md#readme
622
+ [assembly notifications]: https://transloadit.com/docs/#24-assembly-notifications
623
+ [backgrounding]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/backgrounding.md#readme
624
+ [shrine-url]: https://github.com/shrinerb/shrine-url
625
+ [Robodog]: https://uppy.io/docs/robodog/
626
+ [Robodog Form]: https://uppy.io/docs/robodog/form/
627
+ [Uppy]: https://uppy.io/
628
+ [atomic_helpers]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/atomic_helpers.md#readme
629
+ [shrine-gcs]: https://github.com/renchap/shrine-google_cloud_storage
630
+ [shrine-youtube]: https://github.com/thedyrt/shrine-storage-you_tube
491
631
  [shrine-url]: https://github.com/shrinerb/shrine-url