shrine 2.14.0 → 2.15.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of shrine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +384 -374
- data/README.md +132 -63
- data/doc/advantages.md +191 -109
- data/doc/attacher.md +1 -1
- data/doc/carrierwave.md +4 -4
- data/doc/creating_storages.md +2 -2
- data/doc/design.md +2 -2
- data/doc/direct_s3.md +3 -3
- data/doc/metadata.md +1 -1
- data/doc/multiple_files.md +2 -2
- data/doc/paperclip.md +3 -3
- data/doc/plugins/activerecord.md +92 -0
- data/doc/plugins/add_metadata.md +93 -0
- data/doc/plugins/backgrounding.md +148 -0
- data/doc/plugins/backup.md +29 -0
- data/doc/plugins/cached_attachment_data.md +23 -0
- data/doc/plugins/copy.md +22 -0
- data/doc/plugins/data_uri.md +92 -0
- data/doc/plugins/default_storage.md +16 -0
- data/doc/plugins/default_url.md +33 -0
- data/doc/plugins/default_url_options.md +22 -0
- data/doc/plugins/delete_promoted.md +10 -0
- data/doc/plugins/delete_raw.md +16 -0
- data/doc/plugins/derivation_endpoint.md +747 -0
- data/doc/plugins/determine_mime_type.md +64 -0
- data/doc/plugins/direct_upload.md +170 -0
- data/doc/plugins/download_endpoint.md +83 -0
- data/doc/plugins/dynamic_storage.md +20 -0
- data/doc/plugins/hooks.md +56 -0
- data/doc/plugins/included.md +15 -0
- data/doc/plugins/infer_extension.md +57 -0
- data/doc/plugins/keep_files.md +20 -0
- data/doc/plugins/logging.md +39 -0
- data/doc/plugins/metadata_attribues.md +43 -0
- data/doc/plugins/migration_helpers.md +58 -0
- data/doc/plugins/module_include.md +40 -0
- data/doc/plugins/moving.md +17 -0
- data/doc/plugins/multi_delete.md +18 -0
- data/doc/plugins/parallelize.md +14 -0
- data/doc/plugins/parsed_json.md +9 -0
- data/doc/plugins/presign_endpoint.md +133 -0
- data/doc/plugins/pretty_location.md +29 -0
- data/doc/plugins/processing.md +68 -0
- data/doc/plugins/rack_file.md +49 -0
- data/doc/plugins/rack_response.md +96 -0
- data/doc/plugins/recache.md +27 -0
- data/doc/plugins/refresh_metadata.md +31 -0
- data/doc/plugins/remote_url.md +104 -0
- data/doc/plugins/remove_attachment.md +16 -0
- data/doc/plugins/remove_invalid.md +9 -0
- data/doc/plugins/restore_cached_data.md +14 -0
- data/doc/plugins/sequel.md +64 -0
- data/doc/plugins/signature.md +49 -0
- data/doc/plugins/store_dimensions.md +68 -0
- data/doc/plugins/tempfile.md +40 -0
- data/doc/plugins/upload_endpoint.md +123 -0
- data/doc/plugins/upload_options.md +28 -0
- data/doc/plugins/validation_helpers.md +129 -0
- data/doc/plugins/versions.md +179 -0
- data/doc/processing.md +217 -247
- data/doc/refile.md +3 -3
- data/doc/release_notes/1.0.0.md +143 -0
- data/doc/release_notes/1.1.0.md +184 -0
- data/doc/release_notes/1.2.0.md +37 -0
- data/doc/release_notes/1.3.0.md +90 -0
- data/doc/release_notes/1.4.0.md +167 -0
- data/doc/release_notes/1.4.1.md +9 -0
- data/doc/release_notes/1.4.2.md +20 -0
- data/doc/release_notes/2.0.0.md +173 -0
- data/doc/release_notes/2.0.1.md +12 -0
- data/doc/release_notes/2.1.0.md +59 -0
- data/doc/release_notes/2.1.1.md +8 -0
- data/doc/release_notes/2.10.0.md +52 -0
- data/doc/release_notes/2.10.1.md +6 -0
- data/doc/release_notes/2.11.0.md +69 -0
- data/doc/release_notes/2.12.0.md +65 -0
- data/doc/release_notes/2.13.0.md +146 -0
- data/doc/release_notes/2.14.0.md +278 -0
- data/doc/release_notes/2.15.0.md +82 -0
- data/doc/release_notes/2.2.0.md +98 -0
- data/doc/release_notes/2.3.0.md +50 -0
- data/doc/release_notes/2.3.1.md +10 -0
- data/doc/release_notes/2.4.0.md +87 -0
- data/doc/release_notes/2.4.1.md +29 -0
- data/doc/release_notes/2.5.0.md +130 -0
- data/doc/release_notes/2.6.0.md +254 -0
- data/doc/release_notes/2.6.1.md +14 -0
- data/doc/release_notes/2.7.0.md +180 -0
- data/doc/release_notes/2.8.0.md +95 -0
- data/doc/release_notes/2.9.0.md +82 -0
- data/doc/retrieving_uploads.md +1 -1
- data/doc/storage/file_system.md +96 -0
- data/doc/storage/s3.md +293 -0
- data/doc/validation.md +1 -1
- data/lib/shrine/plugins/_urlsafe_serialization.rb +33 -125
- data/lib/shrine/plugins/activerecord.rb +0 -78
- data/lib/shrine/plugins/add_metadata.rb +0 -80
- data/lib/shrine/plugins/backgrounding.rb +0 -134
- data/lib/shrine/plugins/backup.rb +0 -22
- data/lib/shrine/plugins/cached_attachment_data.rb +0 -15
- data/lib/shrine/plugins/copy.rb +0 -14
- data/lib/shrine/plugins/data_uri.rb +0 -73
- data/lib/shrine/plugins/default_storage.rb +0 -11
- data/lib/shrine/plugins/default_url.rb +0 -25
- data/lib/shrine/plugins/default_url_options.rb +0 -16
- data/lib/shrine/plugins/delete_promoted.rb +0 -6
- data/lib/shrine/plugins/delete_raw.rb +0 -10
- data/lib/shrine/plugins/derivation_endpoint.rb +652 -0
- data/lib/shrine/plugins/determine_mime_type.rb +1 -85
- data/lib/shrine/plugins/direct_upload.rb +0 -155
- data/lib/shrine/plugins/download_endpoint.rb +11 -73
- data/lib/shrine/plugins/dynamic_storage.rb +0 -17
- data/lib/shrine/plugins/hooks.rb +0 -48
- data/lib/shrine/plugins/included.rb +0 -12
- data/lib/shrine/plugins/infer_extension.rb +0 -49
- data/lib/shrine/plugins/keep_files.rb +0 -19
- data/lib/shrine/plugins/logging.rb +0 -39
- data/lib/shrine/plugins/metadata_attributes.rb +0 -35
- data/lib/shrine/plugins/migration_helpers.rb +0 -50
- data/lib/shrine/plugins/module_include.rb +0 -32
- data/lib/shrine/plugins/moving.rb +0 -12
- data/lib/shrine/plugins/multi_delete.rb +0 -13
- data/lib/shrine/plugins/parallelize.rb +0 -8
- data/lib/shrine/plugins/parsed_json.rb +0 -5
- data/lib/shrine/plugins/presign_endpoint.rb +2 -117
- data/lib/shrine/plugins/pretty_location.rb +0 -22
- data/lib/shrine/plugins/processing.rb +0 -55
- data/lib/shrine/plugins/rack_file.rb +0 -39
- data/lib/shrine/plugins/rack_response.rb +0 -81
- data/lib/shrine/plugins/recache.rb +0 -21
- data/lib/shrine/plugins/refresh_metadata.rb +0 -24
- data/lib/shrine/plugins/remote_url.rb +0 -85
- data/lib/shrine/plugins/remove_attachment.rb +0 -10
- data/lib/shrine/plugins/remove_invalid.rb +0 -6
- data/lib/shrine/plugins/restore_cached_data.rb +0 -10
- data/lib/shrine/plugins/sequel.rb +0 -54
- data/lib/shrine/plugins/signature.rb +0 -37
- data/lib/shrine/plugins/store_dimensions.rb +0 -63
- data/lib/shrine/plugins/tempfile.rb +4 -35
- data/lib/shrine/plugins/upload_endpoint.rb +2 -109
- data/lib/shrine/plugins/upload_options.rb +0 -20
- data/lib/shrine/plugins/validation_helpers.rb +0 -36
- data/lib/shrine/plugins/versions.rb +0 -156
- data/lib/shrine/storage/file_system.rb +0 -77
- data/lib/shrine/storage/s3.rb +0 -249
- data/lib/shrine/version.rb +1 -1
- data/shrine.gemspec +2 -2
- metadata +86 -6
data/doc/processing.md
CHANGED
@@ -1,154 +1,98 @@
|
|
1
1
|
# File Processing
|
2
2
|
|
3
|
-
Shrine allows you to process files
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
Shrine allows you to process files in two ways. One is processing "[on
|
4
|
+
upload](#processing-on-upload)", where the processing gets triggered when the file is
|
5
|
+
attached to a record. The other is "[on-the-fly](#on-the-fly-processing)"
|
6
|
+
processing, where the processing is performed lazily at the moment the file is
|
7
|
+
requested.
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
storage).
|
9
|
+
With both ways you need to define some kind of processing block, which accepts
|
10
|
+
a source file and is expected to return the processed result file.
|
12
11
|
|
13
12
|
```rb
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
process(:store) do |io, context|
|
18
|
-
io #=> #<Shrine::UploadedFile ...>
|
19
|
-
context #=> {:record=>#<Photo...>,:name=>:image,...}
|
20
|
-
end
|
13
|
+
some_process_block do |source_file|
|
14
|
+
# process source file and return the result
|
21
15
|
end
|
22
16
|
```
|
23
17
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
Shrine treats processing as a functional transformation; you are given the
|
30
|
-
original file, and how you're going to perform processing is entirely up to
|
31
|
-
you, you only need to return the processed files at the end of the block that
|
32
|
-
you want to save. Then Shrine will continue to upload those files to the
|
33
|
-
storage. Note that **it's recommended to always keep the original file**, just
|
34
|
-
in case you'll ever need to reprocess it.
|
35
|
-
|
36
|
-
It's a good idea to also load the `delete_raw` plugin to automatically delete
|
37
|
-
processed files after they're uploaded.
|
18
|
+
How you're going to implement processing is entirely up to you. For images it's
|
19
|
+
recommended to use the **[ImageProcessing]** gem, which provides wrappers for
|
20
|
+
processing with [ImageMagick]/[GraphicsMagick] (using the [MiniMagick] gem) or
|
21
|
+
[libvips] (using the [ruby-vips] gem; see the [libvips section](#libvips)).
|
22
|
+
Here is an example of generating a thumbnail with ImageProcessing:
|
38
23
|
|
39
|
-
```
|
40
|
-
|
41
|
-
plugin :processing
|
42
|
-
plugin :delete_raw # automatically delete processed files after uploading
|
43
|
-
|
44
|
-
# ...
|
45
|
-
end
|
24
|
+
```sh
|
25
|
+
$ brew install imagemagick
|
46
26
|
```
|
47
27
|
|
48
|
-
## Single file
|
49
|
-
|
50
|
-
Let's say that you have an image that you want to optimize before it's saved
|
51
|
-
to permanent storage. This is how you might do it with the [image_optim] gem:
|
52
|
-
|
53
28
|
```rb
|
54
29
|
# Gemfile
|
55
|
-
gem "
|
56
|
-
gem "image_optim_pack" # precompiled binaries
|
30
|
+
gem "image_processing", "~> 1.0"
|
57
31
|
```
|
58
32
|
|
59
33
|
```rb
|
60
|
-
require "
|
61
|
-
|
62
|
-
class ImageUploader < Shrine
|
63
|
-
plugin :processing
|
64
|
-
plugin :delete_raw
|
65
|
-
|
66
|
-
process(:store) do |io, context|
|
67
|
-
original = io.download
|
68
|
-
|
69
|
-
image_optim = ImageOptim.new
|
70
|
-
optimized_path = image_optim.optimize_image(original.path)
|
34
|
+
require "image_processing/mini_magick"
|
71
35
|
|
72
|
-
|
36
|
+
thumbnail = ImageProcessing::MiniMagick
|
37
|
+
.source(image)
|
38
|
+
.resize_to_limit!(600, 400)
|
73
39
|
|
74
|
-
|
75
|
-
end
|
76
|
-
end
|
40
|
+
thumbnail #=> #<Tempfile:...> (a 600x400 thumbnail of the source image)
|
77
41
|
```
|
78
42
|
|
79
|
-
|
80
|
-
download the cached file from temporary storage before optimizing it.
|
81
|
-
Afterwards we also close and delete it using `Tempfile#close!`.
|
82
|
-
|
83
|
-
## Versions
|
43
|
+
## Processing on upload
|
84
44
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
Since we'll be storing multiple derivates of the original file, we'll need to
|
92
|
-
also load the `versions` plugin, which allows us to return a Hash of processed
|
93
|
-
files. For processing we'll be using the `ImageProcessing::MiniMagick` backend,
|
94
|
-
which performs processing with [ImageMagick] or [GraphicsMagick].
|
45
|
+
Shrine allows you to process files before they're uploaded to a storage. It's
|
46
|
+
generally best to process cached files when they're being promoted to permanent
|
47
|
+
storage, because (a) at that point the file has already been successfully
|
48
|
+
[validated][validation], (b) the parent record has been saved and the database
|
49
|
+
transaction has been committed, and (c) this can be delayed into a [background
|
50
|
+
job][backgrounding].
|
95
51
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
```rb
|
100
|
-
# Gemfile
|
101
|
-
gem "image_processing", "~> 1.0"
|
102
|
-
```
|
52
|
+
You can define processing using the `processing` plugin, which we'll use to
|
53
|
+
hook into the `:store` phase (when cached file is uploaded to permanent
|
54
|
+
storage).
|
103
55
|
|
104
56
|
```rb
|
105
|
-
require "image_processing/mini_magick"
|
106
|
-
|
107
57
|
class ImageUploader < Shrine
|
108
58
|
plugin :processing
|
109
|
-
plugin :versions
|
110
|
-
plugin :delete_raw
|
111
59
|
|
112
60
|
process(:store) do |io, context|
|
113
|
-
|
114
|
-
|
115
|
-
io.download do |original|
|
116
|
-
pipeline = ImageProcessing::MiniMagick.source(original)
|
117
|
-
|
118
|
-
versions[:large] = pipeline.resize_to_limit!(800, 800)
|
119
|
-
versions[:medium] = pipeline.resize_to_limit!(500, 500)
|
120
|
-
versions[:small] = pipeline.resize_to_limit!(300, 300)
|
121
|
-
end
|
61
|
+
io #=> #<Shrine::UploadedFile ...>
|
62
|
+
context #=> {:record=>#<Photo...>,:name=>:image,...}
|
122
63
|
|
123
|
-
|
64
|
+
# ...
|
124
65
|
end
|
125
66
|
end
|
126
67
|
```
|
127
68
|
|
128
|
-
|
69
|
+
The processing block yields two arguments: a [`Shrine::UploadedFile`] object
|
70
|
+
representing the file uploaded to temporary storage, and a Hash containing
|
71
|
+
additional data such as the model instance and attachment name. The block
|
72
|
+
result should be file(s) that will be uploaded to permanent storage.
|
129
73
|
|
130
|
-
|
131
|
-
to be multiple times faster than ImageMagick, with lower memory usage on top of
|
132
|
-
that (see [Why is libvips quick]). Using libvips is as easy as installing libvips
|
133
|
-
and switching to the `ImageProcessing::Vips` backend.
|
74
|
+
### Versions
|
134
75
|
|
135
|
-
|
136
|
-
|
137
|
-
|
76
|
+
Let's say we're handling images, and want to generate thumbnails of various
|
77
|
+
dimensions. In this case we can use the ImageProcessing gem to generate the
|
78
|
+
thumbnails, and return a hash of processed files at the end of the block. We'll
|
79
|
+
need to load the `versions` plugin which extends Shrine with the ability to
|
80
|
+
handle collections of files inside the same attachment.
|
138
81
|
|
139
82
|
```rb
|
140
|
-
require "image_processing/
|
83
|
+
require "image_processing/mini_magick"
|
141
84
|
|
142
85
|
class ImageUploader < Shrine
|
143
|
-
plugin :processing
|
144
|
-
plugin :versions
|
145
|
-
plugin :delete_raw
|
86
|
+
plugin :processing # allows hooking into promoting
|
87
|
+
plugin :versions # enable Shrine to handle a hash of files
|
88
|
+
plugin :delete_raw # delete processed files after uploading
|
146
89
|
|
147
90
|
process(:store) do |io, context|
|
148
91
|
versions = { original: io } # retain original
|
149
92
|
|
93
|
+
# download the uploaded file from the temporary storage
|
150
94
|
io.download do |original|
|
151
|
-
pipeline = ImageProcessing::
|
95
|
+
pipeline = ImageProcessing::MiniMagick.source(original)
|
152
96
|
|
153
97
|
versions[:large] = pipeline.resize_to_limit!(800, 800)
|
154
98
|
versions[:medium] = pipeline.resize_to_limit!(500, 500)
|
@@ -160,89 +104,43 @@ class ImageUploader < Shrine
|
|
160
104
|
end
|
161
105
|
```
|
162
106
|
|
163
|
-
|
164
|
-
|
165
|
-
Since processing is so dynamic, you're not limited to using the ImageProcessing
|
166
|
-
gem, you can also use a 3rd-party service to generate thumbnails for you. Here
|
167
|
-
is the same example as above, but this time using [ImageOptim.com] to do the
|
168
|
-
processing (not to be confused with the [image_optim] gem):
|
169
|
-
|
170
|
-
```rb
|
171
|
-
# Gemfile
|
172
|
-
gem "down", "~> 4.4"
|
173
|
-
gem "http", "~> 3.2"
|
174
|
-
```
|
175
|
-
|
176
|
-
```rb
|
177
|
-
require "down/http"
|
178
|
-
|
179
|
-
class ImageUploader < Shrine
|
180
|
-
plugin :processing
|
181
|
-
plugin :versions
|
182
|
-
plugin :delete_raw
|
183
|
-
|
184
|
-
IMAGE_OPTIM_URL = "https://im2.io/<USERNAME>"
|
185
|
-
|
186
|
-
process(:store) do |io, context|
|
187
|
-
down = Down::Http.new(method: :post)
|
188
|
-
|
189
|
-
size_800 = down.download("#{IMAGE_OPTIM_URL}/800x800/#{io.url}")
|
190
|
-
size_500 = down.download("#{IMAGE_OPTIM_URL}/500x500/#{io.url}")
|
191
|
-
size_300 = down.download("#{IMAGE_OPTIM_URL}/300x300/#{io.url}")
|
192
|
-
|
193
|
-
{ original: io, large: size_800, medium: size_500, small: size_300 }
|
194
|
-
end
|
195
|
-
end
|
196
|
-
```
|
197
|
-
|
198
|
-
We used the [Down] gem to download response bodies into tempfiles, specifically
|
199
|
-
its [HTTP.rb] backend, as it supports changing the request method and uses an
|
200
|
-
order of magnitude less memory than the default backend. Notice that we didn't
|
201
|
-
have to download the original file from temporary storage as ImageOptim.com
|
202
|
-
allows us to provide a URL.
|
107
|
+
**NOTE: It's recommended to always keep the original file, just in case you'll
|
108
|
+
ever need to reprocess it.**
|
203
109
|
|
204
|
-
|
110
|
+
### Conditional processing
|
205
111
|
|
206
|
-
|
207
|
-
|
208
|
-
|
112
|
+
The process block yields the attached file uploaded to temporary storage, so we
|
113
|
+
have information like file extension and MIME type available. Together with
|
114
|
+
ImageProcessing's chainable API, it's easy to do conditional proccessing.
|
209
115
|
|
210
116
|
For example, let's say we want our thumbnails to be either JPEGs or PNGs, and
|
211
117
|
we also want to save JPEGs as progressive (interlaced). Here's how the code for
|
212
118
|
this might look like:
|
213
119
|
|
214
120
|
```rb
|
215
|
-
|
216
|
-
|
217
|
-
class ImageUploader < Shrine
|
218
|
-
plugin :processing
|
219
|
-
plugin :versions
|
220
|
-
plugin :delete_raw
|
221
|
-
|
222
|
-
process(:store) do |io, context|
|
223
|
-
versions = { original: io } # retain original
|
224
|
-
|
225
|
-
io.download do |original|
|
226
|
-
pipeline = ImageProcessing::Vips.source(original)
|
121
|
+
process(:store) do |io, context|
|
122
|
+
versions = { original: io }
|
227
123
|
|
228
|
-
|
229
|
-
|
230
|
-
pipeline = pipeline
|
231
|
-
.convert("jpeg")
|
232
|
-
.saver(interlace: true)
|
233
|
-
end
|
124
|
+
io.download do |original|
|
125
|
+
pipeline = ImageProcessing::Vips.source(original)
|
234
126
|
|
235
|
-
|
236
|
-
|
237
|
-
|
127
|
+
# Shrine::UploadedFile object contains information about the MIME type
|
128
|
+
unless io.mime_type == "image/png"
|
129
|
+
pipeline = pipeline
|
130
|
+
.convert("jpeg")
|
131
|
+
.saver(interlace: true)
|
238
132
|
end
|
239
133
|
|
240
|
-
versions
|
134
|
+
versions[:large] = pipeline.resize_to_limit!(800, 800)
|
135
|
+
versions[:medium] = pipeline.resize_to_limit!(500, 500)
|
136
|
+
versions[:small] = pipeline.resize_to_limit!(300, 300)
|
241
137
|
end
|
138
|
+
|
139
|
+
versions
|
242
140
|
end
|
243
141
|
```
|
244
142
|
|
245
|
-
|
143
|
+
### Processing other file types
|
246
144
|
|
247
145
|
So far we've only been talking about processing images. However, there is
|
248
146
|
nothing image-specific in Shrine's processing API, you can just as well process
|
@@ -255,6 +153,7 @@ To demonstrate, here is an example of transcoding videos using
|
|
255
153
|
|
256
154
|
```rb
|
257
155
|
require "streamio-ffmpeg"
|
156
|
+
require "tempfile"
|
258
157
|
|
259
158
|
class VideoUploader < Shrine
|
260
159
|
plugin :processing
|
@@ -262,18 +161,22 @@ class VideoUploader < Shrine
|
|
262
161
|
plugin :delete_raw
|
263
162
|
|
264
163
|
process(:store) do |io, context|
|
265
|
-
|
266
|
-
transcoded = Tempfile.new(["transcoded", ".mp4"], binmode: true)
|
267
|
-
screenshot = Tempfile.new(["screenshot", ".jpg"], binmode: true)
|
164
|
+
versions = { original: io }
|
268
165
|
|
269
|
-
|
270
|
-
|
271
|
-
|
166
|
+
io.download do |original|
|
167
|
+
transcoded = Tempfile.new(["transcoded", ".mp4"], binmode: true)
|
168
|
+
screenshot = Tempfile.new(["screenshot", ".jpg"], binmode: true)
|
169
|
+
|
170
|
+
movie = FFMPEG::Movie.new(original.path)
|
171
|
+
movie.transcode(transcoded.path)
|
172
|
+
movie.screenshot(screenshot.path)
|
173
|
+
|
174
|
+
[transcoded, screenshot].each(&:open) # refresh file descriptors
|
272
175
|
|
273
|
-
|
274
|
-
|
176
|
+
versions.merge!(transcoded: transcoded, screenshot: screenshot)
|
177
|
+
end
|
275
178
|
|
276
|
-
|
179
|
+
versions
|
277
180
|
end
|
278
181
|
end
|
279
182
|
```
|
@@ -282,81 +185,152 @@ end
|
|
282
185
|
|
283
186
|
Generating image thumbnails on upload can be a pain to maintain, because
|
284
187
|
whenever you need to add a new version or change an existing one, you need to
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
As an alternative, it's very common to generate thumbnails dynamically, when
|
289
|
-
their URL is first requested, and then cache the processing result for future
|
290
|
-
requests. This strategy is known as "on-the-fly processing", and it's suitable
|
291
|
-
for smaller files such as images.
|
188
|
+
retroactively apply it to all existing uploads (see the [Reprocessing Versions]
|
189
|
+
guide for more details).
|
292
190
|
|
293
|
-
|
294
|
-
|
295
|
-
|
191
|
+
As an alternative, it's very common to instead generate thumbnails dynamically
|
192
|
+
as they're requested, and then cache them for future requests. This strategy is
|
193
|
+
known as "on-the-fly processing", and it's suitable for generating thumbnails
|
194
|
+
or document previews.
|
296
195
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
* [thumbor]
|
301
|
-
* [flyimg]
|
302
|
-
* ...
|
196
|
+
Shrine provides on-the-fly processing functionality via the
|
197
|
+
[`derivation_endpoint`][derivation_endpoint] plugin. The basic setup is the
|
198
|
+
following:
|
303
199
|
|
304
|
-
|
305
|
-
|
306
|
-
|
200
|
+
1. load the plugin with a secret key and a path prefix for the endpoint
|
201
|
+
2. mount the endpoint into your main app's router
|
202
|
+
3. define a processing block for the type files you want to generate
|
307
203
|
|
308
|
-
|
204
|
+
Together it might look something like this:
|
309
205
|
|
310
|
-
|
311
|
-
|
312
|
-
as an alternative to Shrine, but Dragonfly's app that performs on-the-fly
|
313
|
-
processing can actually be used standalone.
|
206
|
+
```rb
|
207
|
+
require "image_processing/mini_magick"
|
314
208
|
|
315
|
-
|
316
|
-
|
209
|
+
class ImageUploader < Shrine
|
210
|
+
plugin :derivation_endpoint,
|
211
|
+
secret_key: "<YOUR SECRET KEY>",
|
212
|
+
prefix: "derivations/image"
|
213
|
+
|
214
|
+
derivation :thumbnail do |file, width, height|
|
215
|
+
ImageProcessing::MiniMagick
|
216
|
+
.source(file)
|
217
|
+
.resize_to_limit!(width.to_i, height.to_i)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
```
|
317
221
|
|
318
222
|
```rb
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
plugin :imagemagick
|
223
|
+
# config/routes.rb (Rails)
|
224
|
+
Rails.application.routes.draw do
|
225
|
+
mount ImageUploader.derivation_endpoint => "derivations/image"
|
323
226
|
end
|
227
|
+
```
|
324
228
|
|
325
|
-
|
229
|
+
Now you can generate thumbnail URLs from attached files, and the actual
|
230
|
+
thumbnail will be generated when the URL is requested:
|
231
|
+
|
232
|
+
```rb
|
233
|
+
photo.image.derivation_url(:thumbnail, "600", "400")
|
234
|
+
#=> "/derivations/image/thumbnail/600/400/eyJpZCI6ImZvbyIsInN0b3JhZ2UiOiJzdG9yZSJ9?signature=..."
|
326
235
|
```
|
327
236
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
237
|
+
The plugin is highly customizable, be sure to check out the
|
238
|
+
[documentation][derivation_endpoint], especially the [performance
|
239
|
+
section][derivation_endpoint performance].
|
240
|
+
|
241
|
+
## Extras
|
242
|
+
|
243
|
+
### libvips
|
332
244
|
|
333
|
-
|
334
|
-
|
335
|
-
|
245
|
+
As mentioned, ImageProcessing gem also has an alternative backend for
|
246
|
+
processing images with **[libvips]**. libvips is a full-featured image
|
247
|
+
processing library like ImageMagick, with impressive performance
|
248
|
+
characteristics – it's often **multiple times faster** than ImageMagick and has
|
249
|
+
low memory usage (see [Why is libvips quick]).
|
250
|
+
|
251
|
+
Using libvips is as easy as installing it and switching to the
|
252
|
+
`ImageProcessing::Vips` backend:
|
253
|
+
|
254
|
+
```sh
|
255
|
+
$ brew install vips
|
256
|
+
```
|
336
257
|
|
337
258
|
```rb
|
338
|
-
|
339
|
-
|
340
|
-
Shrine.plugin :default_url_options, cache: { public: true }, store: { public: true }
|
259
|
+
# Gemfile
|
260
|
+
gem "image_processing", "~> 1.0"
|
341
261
|
```
|
342
262
|
|
343
|
-
|
263
|
+
```rb
|
264
|
+
require "image_processing/vips"
|
265
|
+
|
266
|
+
# all we did was replace `ImageProcessing::MiniMagick` with `ImageProcessing::Vips`
|
267
|
+
thumbnail = ImageProcessing::Vips
|
268
|
+
.source(image)
|
269
|
+
.resize_to_limit!(600, 400)
|
270
|
+
|
271
|
+
thumbnail #=> #<Tempfile:...> (a 600x400 thumbnail of the source image)
|
272
|
+
```
|
273
|
+
|
274
|
+
### Optimizing thumbnails
|
275
|
+
|
276
|
+
If you're generating image thumbnails, you can additionally use the
|
277
|
+
[image_optim] gem to further reduce their filesize:
|
344
278
|
|
345
279
|
```rb
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
.url
|
351
|
-
end
|
280
|
+
# Gemfile
|
281
|
+
gem "image_processing", "~> 1.0"
|
282
|
+
gem "image_optim"
|
283
|
+
gem "image_optim_pack" # precompiled binaries
|
352
284
|
```
|
285
|
+
|
353
286
|
```rb
|
354
|
-
|
287
|
+
require "image_processing/mini_magick"
|
288
|
+
|
289
|
+
thumbnail = ImageProcessing::MiniMagick
|
290
|
+
.source(image)
|
291
|
+
.resize_to_limit!(600, 400)
|
292
|
+
|
293
|
+
image_optim = ImageOptim.new
|
294
|
+
image_optim.optimize_image!(thumbnail.path)
|
295
|
+
|
296
|
+
thumbnail.open # refresh file descriptor
|
297
|
+
thumbnail
|
298
|
+
```
|
299
|
+
|
300
|
+
### External processing
|
301
|
+
|
302
|
+
Since processing is so dynamic, you're not limited to using the ImageProcessing
|
303
|
+
gem, you can also use a 3rd-party service to generate thumbnails for you. Here
|
304
|
+
is an example of generating thumbnails on-the-fly using [ImageOptim.com] (not
|
305
|
+
to be confused with the [image_optim] gem):
|
306
|
+
|
307
|
+
```rb
|
308
|
+
# Gemfile
|
309
|
+
gem "down", "~> 4.4"
|
310
|
+
gem "http", "~> 4.0"
|
311
|
+
```
|
312
|
+
|
313
|
+
```rb
|
314
|
+
require "down/http"
|
315
|
+
|
316
|
+
class ImageUploader < Shrine
|
317
|
+
plugin :derivation_endpoint,
|
318
|
+
secret_key: "secret",
|
319
|
+
prefix: "derivations/image",
|
320
|
+
download: false
|
321
|
+
|
322
|
+
derivation :thumbnail do |source, width, height|
|
323
|
+
# generate thumbnails using ImageOptim.com
|
324
|
+
down = Down::Http.new(method: :post)
|
325
|
+
down.download("https://im2.io/<USERNAME>/#{width}x#{height}/#{source.url}")
|
326
|
+
end
|
327
|
+
end
|
355
328
|
```
|
356
329
|
|
357
330
|
### Cloudinary
|
358
331
|
|
359
|
-
[Cloudinary] is a
|
332
|
+
[Cloudinary] is a popular commercial service for on-the-fly image processing,
|
333
|
+
so it's a good alternative to the `derivation_endpoint` plugin. The
|
360
334
|
[shrine-cloudinary] gem provides a Shrine storage that we can set for our
|
361
335
|
temporary and permanent storage:
|
362
336
|
|
@@ -389,25 +363,21 @@ photo.image.url(width: 100, height: 100, crop: :fit)
|
|
389
363
|
#=> "http://res.cloudinary.com/myapp/image/upload/w_100,h_100,c_fit/nature.jpg"
|
390
364
|
```
|
391
365
|
|
392
|
-
[`Shrine::UploadedFile`]: http://shrinerb.com/rdoc/classes/Shrine/
|
393
|
-
[
|
394
|
-
[ImageProcessing]: https://github.com/janko-m/image_processing
|
395
|
-
[`ImageProcessing::MiniMagick`]: https://github.com/janko-m/image_processing/blob/master/doc/minimagick.md
|
366
|
+
[`Shrine::UploadedFile`]: http://shrinerb.com/rdoc/classes/Shrine/UploadedFile/InstanceMethods.html
|
367
|
+
[ImageProcessing]: https://github.com/janko/image_processing
|
396
368
|
[ImageMagick]: https://www.imagemagick.org
|
397
369
|
[GraphicsMagick]: http://www.graphicsmagick.org
|
398
370
|
[libvips]: http://libvips.github.io/libvips/
|
399
371
|
[Why is libvips quick]: https://github.com/libvips/libvips/wiki/Why-is-libvips-quick
|
372
|
+
[image_optim]: https://github.com/toy/image_optim
|
400
373
|
[ImageOptim.com]: https://imageoptim.com/api
|
401
|
-
[Down]: https://github.com/janko-m/down
|
402
|
-
[HTTP.rb]: https://github.com/httprb/http
|
403
374
|
[streamio-ffmpeg]: https://github.com/streamio/streamio-ffmpeg
|
404
|
-
[
|
405
|
-
[Dragonfly]: http://markevans.github.io/dragonfly/
|
406
|
-
[imgproxy]: https://github.com/DarthSim/imgproxy
|
407
|
-
[imaginary]: https://github.com/h2non/imaginary
|
408
|
-
[thumbor]: http://thumbor.org
|
409
|
-
[flyimg]: http://flyimg.io
|
375
|
+
[Reprocessing Versions]: /doc/regenerating_versions.md#readme
|
410
376
|
[Cloudinary]: https://cloudinary.com
|
411
|
-
[Dragonfly configuration]: http://markevans.github.io/dragonfly/configuration
|
412
|
-
[fog-aws]: https://github.com/fog/fog-aws
|
413
377
|
[shrine-cloudinary]: https://github.com/shrinerb/shrine-cloudinary
|
378
|
+
[backgrounding]: /doc/plugins/backgrounding.md#readme
|
379
|
+
[validation]: /doc/validation.md#readme
|
380
|
+
[ruby-vips]: https://github.com/libvips/ruby-vips
|
381
|
+
[MiniMagick]: https://github.com/minimagick/minimagick
|
382
|
+
[derivation_endpoint]: /doc/plugins/derivation_endpoint.md#readme
|
383
|
+
[derivation_endpoint performance]: /doc/plugins/derivation_endpoint.md#performance
|