activestorage 6.1.3.2 → 7.0.0.alpha2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activestorage might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +132 -182
- data/MIT-LICENSE +1 -1
- data/README.md +25 -11
- data/app/assets/javascripts/activestorage.esm.js +844 -0
- data/app/assets/javascripts/activestorage.js +257 -376
- data/app/controllers/active_storage/base_controller.rb +1 -10
- data/app/controllers/active_storage/blobs/proxy_controller.rb +14 -4
- data/app/controllers/active_storage/blobs/redirect_controller.rb +6 -4
- data/app/controllers/active_storage/representations/base_controller.rb +18 -0
- data/app/controllers/active_storage/representations/proxy_controller.rb +7 -11
- data/app/controllers/active_storage/representations/redirect_controller.rb +7 -7
- data/app/controllers/concerns/active_storage/set_blob.rb +6 -2
- data/app/controllers/concerns/active_storage/set_current.rb +3 -3
- data/app/controllers/concerns/active_storage/streaming.rb +65 -0
- data/app/javascript/activestorage/ujs.js +1 -1
- data/app/models/active_storage/attachment.rb +36 -3
- data/app/models/active_storage/blob/representable.rb +8 -6
- data/app/models/active_storage/blob.rb +27 -28
- data/app/models/active_storage/current.rb +12 -2
- data/app/models/active_storage/preview.rb +6 -4
- data/app/models/active_storage/record.rb +1 -1
- data/app/models/active_storage/variant.rb +4 -7
- data/app/models/active_storage/variant_record.rb +2 -0
- data/app/models/active_storage/variant_with_record.rb +10 -6
- data/app/models/active_storage/variation.rb +2 -2
- data/config/routes.rb +10 -10
- data/db/migrate/20170806125915_create_active_storage_tables.rb +29 -8
- data/db/update_migrate/20191206030411_create_active_storage_variant_records.rb +15 -2
- data/lib/active_storage/analyzer/audio_analyzer.rb +65 -0
- data/lib/active_storage/analyzer/image_analyzer/image_magick.rb +39 -0
- data/lib/active_storage/analyzer/image_analyzer/vips.rb +49 -0
- data/lib/active_storage/analyzer/image_analyzer.rb +2 -30
- data/lib/active_storage/analyzer/video_analyzer.rb +26 -11
- data/lib/active_storage/analyzer.rb +8 -4
- data/lib/active_storage/attached/changes/create_many.rb +7 -3
- data/lib/active_storage/attached/changes/create_one.rb +1 -1
- data/lib/active_storage/attached/changes/create_one_of_many.rb +1 -1
- data/lib/active_storage/attached/changes/delete_many.rb +1 -1
- data/lib/active_storage/attached/changes/delete_one.rb +1 -1
- data/lib/active_storage/attached/changes/detach_many.rb +18 -0
- data/lib/active_storage/attached/changes/detach_one.rb +24 -0
- data/lib/active_storage/attached/changes/purge_many.rb +27 -0
- data/lib/active_storage/attached/changes/purge_one.rb +27 -0
- data/lib/active_storage/attached/changes.rb +7 -1
- data/lib/active_storage/attached/many.rb +27 -15
- data/lib/active_storage/attached/model.rb +31 -5
- data/lib/active_storage/attached/one.rb +32 -27
- data/lib/active_storage/downloader.rb +2 -2
- data/lib/active_storage/engine.rb +29 -1
- data/lib/active_storage/errors.rb +3 -0
- data/lib/active_storage/fixture_set.rb +76 -0
- data/lib/active_storage/gem_version.rb +4 -4
- data/lib/active_storage/previewer/video_previewer.rb +1 -1
- data/lib/active_storage/previewer.rb +14 -5
- data/lib/active_storage/reflection.rb +12 -2
- data/lib/active_storage/service/azure_storage_service.rb +1 -1
- data/lib/active_storage/service/configurator.rb +1 -1
- data/lib/active_storage/service/disk_service.rb +13 -18
- data/lib/active_storage/service/gcs_service.rb +91 -7
- data/lib/active_storage/service/mirror_service.rb +1 -1
- data/lib/active_storage/service/registry.rb +1 -1
- data/lib/active_storage/service/s3_service.rb +4 -4
- data/lib/active_storage/service.rb +3 -3
- data/lib/active_storage/transformers/image_processing_transformer.rb +1 -1
- data/lib/active_storage/transformers/transformer.rb +1 -1
- data/lib/active_storage.rb +5 -1
- data/lib/tasks/activestorage.rake +5 -1
- metadata +35 -25
- data/app/controllers/concerns/active_storage/set_headers.rb +0 -12
@@ -1,5 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class ActiveStorage::Current < ActiveSupport::CurrentAttributes
|
4
|
-
attribute :
|
3
|
+
class ActiveStorage::Current < ActiveSupport::CurrentAttributes # :nodoc:
|
4
|
+
attribute :url_options
|
5
|
+
|
6
|
+
def host=(host)
|
7
|
+
ActiveSupport::Deprecation.warn("ActiveStorage::Current.host= is deprecated, instead use ActiveStorage::Current.url_options=")
|
8
|
+
self.url_options = { host: host }
|
9
|
+
end
|
10
|
+
|
11
|
+
def host
|
12
|
+
ActiveSupport::Deprecation.warn("ActiveStorage::Current.host is deprecated, instead use ActiveStorage::Current.url_options")
|
13
|
+
self.url_options&.dig(:host)
|
14
|
+
end
|
5
15
|
end
|
@@ -66,9 +66,6 @@ class ActiveStorage::Preview
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
-
alias_method :service_url, :url
|
70
|
-
deprecate service_url: :url
|
71
|
-
|
72
69
|
# Returns a combination key of the blob and the variation that together identifies a specific variant.
|
73
70
|
def key
|
74
71
|
if processed?
|
@@ -78,6 +75,11 @@ class ActiveStorage::Preview
|
|
78
75
|
end
|
79
76
|
end
|
80
77
|
|
78
|
+
# Downloads the file associated with this preview's variant. If no block is
|
79
|
+
# given, the entire file is read into memory and returned. That'll use a lot
|
80
|
+
# of RAM for very large files. If a block is given, then the download is
|
81
|
+
# streamed and yielded in chunks. Raises ActiveStorage::Preview::UnprocessedError
|
82
|
+
# if the preview has not been processed yet.
|
81
83
|
def download(&block)
|
82
84
|
if processed?
|
83
85
|
variant.download(&block)
|
@@ -93,7 +95,7 @@ class ActiveStorage::Preview
|
|
93
95
|
|
94
96
|
def process
|
95
97
|
previewer.preview(service_name: blob.service_name) do |attachable|
|
96
|
-
ActiveRecord::Base.connected_to(role: ActiveRecord
|
98
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role) do
|
97
99
|
image.attach(attachable)
|
98
100
|
end
|
99
101
|
end
|
@@ -42,7 +42,7 @@
|
|
42
42
|
# You can combine any number of ImageMagick/libvips operations into a variant, as well as any macros provided by the
|
43
43
|
# ImageProcessing gem (such as +resize_to_limit+):
|
44
44
|
#
|
45
|
-
# avatar.variant(resize_to_limit: [800, 800],
|
45
|
+
# avatar.variant(resize_to_limit: [800, 800], colourspace: "b-w", rotate: "-90")
|
46
46
|
#
|
47
47
|
# Visit the following links for a list of available ImageProcessing commands and ImageMagick/libvips operations:
|
48
48
|
#
|
@@ -67,7 +67,7 @@ class ActiveStorage::Variant
|
|
67
67
|
|
68
68
|
# Returns a combination key of the blob and the variation that together identifies a specific variant.
|
69
69
|
def key
|
70
|
-
"variants/#{blob.key}/#{Digest::SHA256.hexdigest(variation.key)}"
|
70
|
+
"variants/#{blob.key}/#{OpenSSL::Digest::SHA256.hexdigest(variation.key)}"
|
71
71
|
end
|
72
72
|
|
73
73
|
# Returns the URL of the blob variant on the service. See {ActiveStorage::Blob#url} for details.
|
@@ -79,9 +79,6 @@ class ActiveStorage::Variant
|
|
79
79
|
service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type
|
80
80
|
end
|
81
81
|
|
82
|
-
alias_method :service_url, :url
|
83
|
-
deprecate service_url: :url
|
84
|
-
|
85
82
|
# Downloads the file associated with this variant. If no block is given, the entire file is read into memory and returned.
|
86
83
|
# That'll use a lot of RAM for very large files. If a block is given, then the download is streamed and yielded in chunks.
|
87
84
|
def download(&block)
|
@@ -89,12 +86,12 @@ class ActiveStorage::Variant
|
|
89
86
|
end
|
90
87
|
|
91
88
|
def filename
|
92
|
-
ActiveStorage::Filename.new "#{blob.filename.base}.#{variation.format}"
|
89
|
+
ActiveStorage::Filename.new "#{blob.filename.base}.#{variation.format.downcase}"
|
93
90
|
end
|
94
91
|
|
95
92
|
alias_method :content_type_for_serving, :content_type
|
96
93
|
|
97
|
-
def forced_disposition_for_serving
|
94
|
+
def forced_disposition_for_serving # :nodoc:
|
98
95
|
nil
|
99
96
|
end
|
100
97
|
|
@@ -1,7 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Like an ActiveStorage::Variant, but keeps detail about the variant in the database as an
|
4
|
+
# ActiveStorage::VariantRecord. This is only used if `ActiveStorage.track_variants` is enabled.
|
3
5
|
class ActiveStorage::VariantWithRecord
|
4
6
|
attr_reader :blob, :variation
|
7
|
+
delegate :service, to: :blob
|
5
8
|
|
6
9
|
def initialize(blob, variation)
|
7
10
|
@blob, @variation = blob, ActiveStorage::Variation.wrap(variation)
|
@@ -26,14 +29,11 @@ class ActiveStorage::VariantWithRecord
|
|
26
29
|
|
27
30
|
delegate :key, :url, :download, to: :image, allow_nil: true
|
28
31
|
|
29
|
-
alias_method :service_url, :url
|
30
|
-
deprecate service_url: :url
|
31
|
-
|
32
32
|
private
|
33
33
|
def transform_blob
|
34
34
|
blob.open do |input|
|
35
35
|
variation.transform(input) do |output|
|
36
|
-
yield io: output, filename: "#{blob.filename.base}.#{variation.format}",
|
36
|
+
yield io: output, filename: "#{blob.filename.base}.#{variation.format.downcase}",
|
37
37
|
content_type: variation.content_type, service_name: blob.service.name
|
38
38
|
end
|
39
39
|
end
|
@@ -41,7 +41,7 @@ class ActiveStorage::VariantWithRecord
|
|
41
41
|
|
42
42
|
def create_or_find_record(image:)
|
43
43
|
@record =
|
44
|
-
ActiveRecord::Base.connected_to(role: ActiveRecord
|
44
|
+
ActiveRecord::Base.connected_to(role: ActiveRecord.writing_role) do
|
45
45
|
blob.variant_records.create_or_find_by!(variation_digest: variation.digest) do |record|
|
46
46
|
record.image.attach(image)
|
47
47
|
end
|
@@ -49,6 +49,10 @@ class ActiveStorage::VariantWithRecord
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def record
|
52
|
-
@record ||= blob.variant_records.
|
52
|
+
@record ||= if blob.variant_records.loaded?
|
53
|
+
blob.variant_records.find { |v| v.variation_digest == variation.digest }
|
54
|
+
else
|
55
|
+
blob.variant_records.find_by(variation_digest: variation.digest)
|
56
|
+
end
|
53
57
|
end
|
54
58
|
end
|
@@ -8,7 +8,7 @@ require "mini_mime"
|
|
8
8
|
# In case you do need to use this directly, it's instantiated using a hash of transformations where
|
9
9
|
# the key is the command and the value is the arguments. Example:
|
10
10
|
#
|
11
|
-
# ActiveStorage::Variation.new(resize_to_limit: [100, 100],
|
11
|
+
# ActiveStorage::Variation.new(resize_to_limit: [100, 100], colourspace: "b-w", rotate: "-90", saver: { trim: true })
|
12
12
|
#
|
13
13
|
# The options map directly to {ImageProcessing}[https://github.com/janko-m/image_processing] commands.
|
14
14
|
class ActiveStorage::Variation
|
@@ -75,7 +75,7 @@ class ActiveStorage::Variation
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def digest
|
78
|
-
Digest::SHA1.base64digest Marshal.dump(transformations)
|
78
|
+
OpenSSL::Digest::SHA1.base64digest Marshal.dump(transformations)
|
79
79
|
end
|
80
80
|
|
81
81
|
private
|
data/config/routes.rb
CHANGED
@@ -16,11 +16,7 @@ Rails.application.routes.draw do
|
|
16
16
|
end
|
17
17
|
|
18
18
|
direct :rails_representation do |representation, options|
|
19
|
-
|
20
|
-
variation_key = representation.variation.key
|
21
|
-
filename = representation.blob.filename
|
22
|
-
|
23
|
-
route_for(:rails_blob_representation, signed_blob_id, variation_key, filename, options)
|
19
|
+
route_for(ActiveStorage.resolve_model_to_route, representation, options)
|
24
20
|
end
|
25
21
|
|
26
22
|
resolve("ActiveStorage::Variant") { |variant, options| route_for(ActiveStorage.resolve_model_to_route, variant, options) }
|
@@ -28,22 +24,24 @@ Rails.application.routes.draw do
|
|
28
24
|
resolve("ActiveStorage::Preview") { |preview, options| route_for(ActiveStorage.resolve_model_to_route, preview, options) }
|
29
25
|
|
30
26
|
direct :rails_blob do |blob, options|
|
31
|
-
route_for(
|
27
|
+
route_for(ActiveStorage.resolve_model_to_route, blob, options)
|
32
28
|
end
|
33
29
|
|
34
30
|
resolve("ActiveStorage::Blob") { |blob, options| route_for(ActiveStorage.resolve_model_to_route, blob, options) }
|
35
31
|
resolve("ActiveStorage::Attachment") { |attachment, options| route_for(ActiveStorage.resolve_model_to_route, attachment.blob, options) }
|
36
32
|
|
37
33
|
direct :rails_storage_proxy do |model, options|
|
34
|
+
expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in }
|
35
|
+
|
38
36
|
if model.respond_to?(:signed_id)
|
39
37
|
route_for(
|
40
38
|
:rails_service_blob_proxy,
|
41
|
-
model.signed_id,
|
39
|
+
model.signed_id(expires_in: expires_in),
|
42
40
|
model.filename,
|
43
41
|
options
|
44
42
|
)
|
45
43
|
else
|
46
|
-
signed_blob_id = model.blob.signed_id
|
44
|
+
signed_blob_id = model.blob.signed_id(expires_in: expires_in)
|
47
45
|
variation_key = model.variation.key
|
48
46
|
filename = model.blob.filename
|
49
47
|
|
@@ -58,15 +56,17 @@ Rails.application.routes.draw do
|
|
58
56
|
end
|
59
57
|
|
60
58
|
direct :rails_storage_redirect do |model, options|
|
59
|
+
expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in }
|
60
|
+
|
61
61
|
if model.respond_to?(:signed_id)
|
62
62
|
route_for(
|
63
63
|
:rails_service_blob,
|
64
|
-
model.signed_id,
|
64
|
+
model.signed_id(expires_in: expires_in),
|
65
65
|
model.filename,
|
66
66
|
options
|
67
67
|
)
|
68
68
|
else
|
69
|
-
signed_blob_id = model.blob.signed_id
|
69
|
+
signed_blob_id = model.blob.signed_id(expires_in: expires_in)
|
70
70
|
variation_key = model.variation.key
|
71
71
|
filename = model.blob.filename
|
72
72
|
|
@@ -1,6 +1,9 @@
|
|
1
1
|
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
|
2
2
|
def change
|
3
|
-
|
3
|
+
# Use Active Record's configured type for primary and foreign keys
|
4
|
+
primary_key_type, foreign_key_type = primary_and_foreign_key_types
|
5
|
+
|
6
|
+
create_table :active_storage_blobs, id: primary_key_type do |t|
|
4
7
|
t.string :key, null: false
|
5
8
|
t.string :filename, null: false
|
6
9
|
t.string :content_type
|
@@ -8,28 +11,46 @@ class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
|
|
8
11
|
t.string :service_name, null: false
|
9
12
|
t.bigint :byte_size, null: false
|
10
13
|
t.string :checksum, null: false
|
11
|
-
|
14
|
+
|
15
|
+
if connection.supports_datetime_with_precision?
|
16
|
+
t.datetime :created_at, precision: 6, null: false
|
17
|
+
else
|
18
|
+
t.datetime :created_at, null: false
|
19
|
+
end
|
12
20
|
|
13
21
|
t.index [ :key ], unique: true
|
14
22
|
end
|
15
23
|
|
16
|
-
create_table :active_storage_attachments do |t|
|
24
|
+
create_table :active_storage_attachments, id: primary_key_type do |t|
|
17
25
|
t.string :name, null: false
|
18
|
-
t.references :record, null: false, polymorphic: true, index: false
|
19
|
-
t.references :blob, null: false
|
26
|
+
t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type
|
27
|
+
t.references :blob, null: false, type: foreign_key_type
|
20
28
|
|
21
|
-
|
29
|
+
if connection.supports_datetime_with_precision?
|
30
|
+
t.datetime :created_at, precision: 6, null: false
|
31
|
+
else
|
32
|
+
t.datetime :created_at, null: false
|
33
|
+
end
|
22
34
|
|
23
35
|
t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
|
24
36
|
t.foreign_key :active_storage_blobs, column: :blob_id
|
25
37
|
end
|
26
38
|
|
27
|
-
create_table :active_storage_variant_records do |t|
|
28
|
-
t.belongs_to :blob, null: false, index: false
|
39
|
+
create_table :active_storage_variant_records, id: primary_key_type do |t|
|
40
|
+
t.belongs_to :blob, null: false, index: false, type: foreign_key_type
|
29
41
|
t.string :variation_digest, null: false
|
30
42
|
|
31
43
|
t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
|
32
44
|
t.foreign_key :active_storage_blobs, column: :blob_id
|
33
45
|
end
|
34
46
|
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def primary_and_foreign_key_types
|
50
|
+
config = Rails.configuration.generators
|
51
|
+
setting = config.options[config.orm][:primary_key_type]
|
52
|
+
primary_key_type = setting || :primary_key
|
53
|
+
foreign_key_type = setting || :bigint
|
54
|
+
[primary_key_type, foreign_key_type]
|
55
|
+
end
|
35
56
|
end
|
@@ -1,11 +1,24 @@
|
|
1
1
|
class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0]
|
2
2
|
def change
|
3
|
-
|
4
|
-
|
3
|
+
# Use Active Record's configured type for primary key
|
4
|
+
create_table :active_storage_variant_records, id: primary_key_type do |t|
|
5
|
+
t.belongs_to :blob, null: false, index: false, type: blobs_primary_key_type
|
5
6
|
t.string :variation_digest, null: false
|
6
7
|
|
7
8
|
t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true
|
8
9
|
t.foreign_key :active_storage_blobs, column: :blob_id
|
9
10
|
end
|
10
11
|
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def primary_key_type
|
15
|
+
config = Rails.configuration.generators
|
16
|
+
config.options[config.orm][:primary_key_type] || :primary_key
|
17
|
+
end
|
18
|
+
|
19
|
+
def blobs_primary_key_type
|
20
|
+
pkey_name = connection.primary_key(:active_storage_blobs)
|
21
|
+
pkey_column = connection.columns(:active_storage_blobs).find { |c| c.name == pkey_name }
|
22
|
+
pkey_column.bigint? ? :bigint : pkey_column.type
|
23
|
+
end
|
11
24
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
# Extracts duration (seconds) and bit_rate (bits/s) from an audio blob.
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
#
|
8
|
+
# ActiveStorage::Analyzer::AudioAnalyzer.new(blob).metadata
|
9
|
+
# # => { duration: 5.0, bit_rate: 320340 }
|
10
|
+
#
|
11
|
+
# This analyzer requires the {FFmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails.
|
12
|
+
class Analyzer::AudioAnalyzer < Analyzer
|
13
|
+
def self.accept?(blob)
|
14
|
+
blob.audio?
|
15
|
+
end
|
16
|
+
|
17
|
+
def metadata
|
18
|
+
{ duration: duration, bit_rate: bit_rate }.compact
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def duration
|
23
|
+
duration = audio_stream["duration"]
|
24
|
+
Float(duration) if duration
|
25
|
+
end
|
26
|
+
|
27
|
+
def bit_rate
|
28
|
+
bit_rate = audio_stream["bit_rate"]
|
29
|
+
Integer(bit_rate) if bit_rate
|
30
|
+
end
|
31
|
+
|
32
|
+
def audio_stream
|
33
|
+
@audio_stream ||= streams.detect { |stream| stream["codec_type"] == "audio" } || {}
|
34
|
+
end
|
35
|
+
|
36
|
+
def streams
|
37
|
+
probe["streams"] || []
|
38
|
+
end
|
39
|
+
|
40
|
+
def probe
|
41
|
+
@probe ||= download_blob_to_tempfile { |file| probe_from(file) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def probe_from(file)
|
45
|
+
instrument(File.basename(ffprobe_path)) do
|
46
|
+
IO.popen([ ffprobe_path,
|
47
|
+
"-print_format", "json",
|
48
|
+
"-show_streams",
|
49
|
+
"-show_format",
|
50
|
+
"-v", "error",
|
51
|
+
file.path
|
52
|
+
]) do |output|
|
53
|
+
JSON.parse(output.read)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
rescue Errno::ENOENT
|
57
|
+
logger.info "Skipping audio analysis because FFmpeg isn't installed"
|
58
|
+
{}
|
59
|
+
end
|
60
|
+
|
61
|
+
def ffprobe_path
|
62
|
+
ActiveStorage.paths[:ffprobe] || "ffprobe"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
# This analyzer relies on the third-party {MiniMagick}[https://github.com/minimagick/minimagick] gem. MiniMagick requires
|
5
|
+
# the {ImageMagick}[http://www.imagemagick.org] system library.
|
6
|
+
class Analyzer::ImageAnalyzer::ImageMagick < Analyzer::ImageAnalyzer
|
7
|
+
def self.accept?(blob)
|
8
|
+
super && ActiveStorage.variant_processor == :mini_magick
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
def read_image
|
13
|
+
download_blob_to_tempfile do |file|
|
14
|
+
require "mini_magick"
|
15
|
+
|
16
|
+
image = instrument("mini_magick") do
|
17
|
+
MiniMagick::Image.new(file.path)
|
18
|
+
end
|
19
|
+
|
20
|
+
if image.valid?
|
21
|
+
yield image
|
22
|
+
else
|
23
|
+
logger.info "Skipping image analysis because ImageMagick doesn't support the file"
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
rescue LoadError
|
28
|
+
logger.info "Skipping image analysis because the mini_magick gem isn't installed"
|
29
|
+
{}
|
30
|
+
rescue MiniMagick::Error => error
|
31
|
+
logger.error "Skipping image analysis due to an ImageMagick error: #{error.message}"
|
32
|
+
{}
|
33
|
+
end
|
34
|
+
|
35
|
+
def rotated_image?(image)
|
36
|
+
%w[ RightTop LeftBottom TopRight BottomLeft ].include?(image["%[orientation]"])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorage
|
4
|
+
# This analyzer relies on the third-party {ruby-vips}[https://github.com/libvips/ruby-vips] gem. Ruby-vips requires
|
5
|
+
# the {libvips}[https://libvips.github.io/libvips/] system library.
|
6
|
+
class Analyzer::ImageAnalyzer::Vips < Analyzer::ImageAnalyzer
|
7
|
+
def self.accept?(blob)
|
8
|
+
super && ActiveStorage.variant_processor == :vips
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
def read_image
|
13
|
+
download_blob_to_tempfile do |file|
|
14
|
+
require "ruby-vips"
|
15
|
+
|
16
|
+
image = instrument("vips") do
|
17
|
+
::Vips::Image.new_from_file(file.path, access: :sequential)
|
18
|
+
end
|
19
|
+
|
20
|
+
if valid_image?(image)
|
21
|
+
yield image
|
22
|
+
else
|
23
|
+
logger.info "Skipping image analysis because Vips doesn't support the file"
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
rescue LoadError
|
28
|
+
logger.info "Skipping image analysis because the ruby-vips gem isn't installed"
|
29
|
+
{}
|
30
|
+
rescue ::Vips::Error => error
|
31
|
+
logger.error "Skipping image analysis due to an Vips error: #{error.message}"
|
32
|
+
{}
|
33
|
+
end
|
34
|
+
|
35
|
+
ROTATIONS = /Right-top|Left-bottom|Top-right|Bottom-left/
|
36
|
+
def rotated_image?(image)
|
37
|
+
ROTATIONS === image.get("exif-ifd0-Orientation")
|
38
|
+
rescue ::Vips::Error
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def valid_image?(image)
|
43
|
+
image.avg
|
44
|
+
true
|
45
|
+
rescue ::Vips::Error
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|