dynamic_image 3.1.1 → 3.1.2
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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/dynamic_image/backfill.rb +54 -20
- data/lib/dynamic_image/controller.rb +1 -1
- data/lib/dynamic_image/engine.rb +0 -1
- data/lib/dynamic_image/format.rb +0 -7
- data/lib/dynamic_image/format_negotiator.rb +1 -1
- data/lib/dynamic_image/model.rb +7 -3
- data/lib/dynamic_image/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cce4ab2b6835bbc3e5299f0c9513ada8a30a2db2b46865b975d20492febb8656
|
|
4
|
+
data.tar.gz: 172bf1404ef850c29b15d2f3cb88bc41fd4bb7274d0a10e1bfc41d0d6f1b8c55
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 917691711e8027af32a176f95d2ad9ccc1621cf8b3141c0e734f086357ace2d6cd2ea0e5217c07ef7ada018a5baeddad6f509efd8b0f4919eb6d2c8e1c4ceafb
|
|
7
|
+
data.tar.gz: ab5d71c3d0508b4819e93489dfaee8ad5772177b3c1cf76174ac16ebfd4d459070a0b1c8fe2b1c2bc3d20f3f9d648c48ecf07c4e3920ce690dc492b721692d9e
|
data/README.md
CHANGED
|
@@ -285,7 +285,7 @@ media queries.
|
|
|
285
285
|
## Formats
|
|
286
286
|
|
|
287
287
|
Supported formats are JPEG, PNG, GIF, WebP, JPEG XL and TIFF.
|
|
288
|
-
|
|
288
|
+
HEIC and AVIF can also be uploaded, but aren't supported for output.
|
|
289
289
|
|
|
290
290
|
The preferred format lists are configurable, sorted most preferred format
|
|
291
291
|
first:
|
|
@@ -4,7 +4,7 @@ module DynamicImage
|
|
|
4
4
|
# = DynamicImage Backfill
|
|
5
5
|
#
|
|
6
6
|
# Fills in the metadata columns for records stored before those columns existed. The values are read back from
|
|
7
|
-
# the stored file.
|
|
7
|
+
# the stored file, except where the format guarantees them.
|
|
8
8
|
#
|
|
9
9
|
# Records are written with +update_columns+, so no callbacks run and +updated_at+ is left alone.
|
|
10
10
|
# {DynamicImage::Model#to_param} fingerprints on +updated_at+, and touching it would invalidate every image URL
|
|
@@ -17,20 +17,24 @@ module DynamicImage
|
|
|
17
17
|
# The columns this fills in.
|
|
18
18
|
COLUMNS = %i[frame_count alpha].freeze
|
|
19
19
|
|
|
20
|
-
# @return [Class] the model being backfilled
|
|
21
|
-
attr_reader :model
|
|
22
|
-
|
|
23
20
|
# @return [Integer] records written
|
|
24
21
|
attr_reader :updated
|
|
25
22
|
|
|
26
23
|
# @return [Integer] records left alone, unreadable or missing
|
|
27
24
|
attr_reader :skipped
|
|
28
25
|
|
|
26
|
+
# @return [Integer] records filled in from the format, without a read
|
|
27
|
+
attr_reader :inferred
|
|
28
|
+
|
|
29
29
|
# @param model [Class] a model including {DynamicImage::Model}
|
|
30
|
-
|
|
30
|
+
# @param concurrency [Integer] how many files to read at a time. Reads are network bound; the metadata itself
|
|
31
|
+
# comes off the header. Set to 1 to read serially.
|
|
32
|
+
def initialize(model, concurrency: 4)
|
|
31
33
|
@model = model
|
|
32
34
|
@updated = 0
|
|
33
35
|
@skipped = 0
|
|
36
|
+
@inferred = 0
|
|
37
|
+
@concurrency = [concurrency.to_i, 1].max
|
|
34
38
|
end
|
|
35
39
|
|
|
36
40
|
# The records with a column still unset.
|
|
@@ -40,22 +44,42 @@ module DynamicImage
|
|
|
40
44
|
COLUMNS.map { |column| model.where(column => nil) }.reduce(:or)
|
|
41
45
|
end
|
|
42
46
|
|
|
43
|
-
#
|
|
47
|
+
# Fills in what the format settles, then reads the rest.
|
|
44
48
|
#
|
|
45
|
-
# @yieldparam record [DynamicImage::Model] each record, after it has been
|
|
49
|
+
# @yieldparam record [DynamicImage::Model] each record, after it has been read
|
|
46
50
|
# @return [self]
|
|
47
51
|
# @raise [ArgumentError] if the table doesn't have the columns yet
|
|
48
52
|
def run
|
|
49
53
|
ensure_columns
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
infer_from_format
|
|
55
|
+
pending.find_in_batches(batch_size: concurrency) do |batch|
|
|
56
|
+
read(batch).each do |record, values|
|
|
57
|
+
write(record, values)
|
|
58
|
+
yield(record) if block_given?
|
|
59
|
+
end
|
|
53
60
|
end
|
|
54
61
|
self
|
|
55
62
|
end
|
|
56
63
|
|
|
57
64
|
private
|
|
58
65
|
|
|
66
|
+
attr_reader :model, :concurrency
|
|
67
|
+
|
|
68
|
+
# The formats whose values follow from the format itself. A format that holds neither animation nor an alpha
|
|
69
|
+
# channel has one frame and no transparency, whatever the file contains.
|
|
70
|
+
def inferable_formats
|
|
71
|
+
DynamicImage::Format.formats.reject { |f| f.animated? || f.alpha? }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Written with update_all, so no files are read, no callbacks run and updated_at is left alone.
|
|
75
|
+
def infer_from_format
|
|
76
|
+
inferable_formats.each do |format|
|
|
77
|
+
@inferred += pending.where(content_type: format.content_types)
|
|
78
|
+
.update_all(frame_count: 1, alpha: false)
|
|
79
|
+
end
|
|
80
|
+
@inferred
|
|
81
|
+
end
|
|
82
|
+
|
|
59
83
|
def ensure_columns
|
|
60
84
|
missing = COLUMNS.reject { |c| model.column_names.include?(c.to_s) }
|
|
61
85
|
return if missing.empty?
|
|
@@ -65,20 +89,30 @@ module DynamicImage
|
|
|
65
89
|
"Run bin/rails generate dynamic_image:upgrade #{model.name}"
|
|
66
90
|
end
|
|
67
91
|
|
|
68
|
-
|
|
69
|
-
|
|
92
|
+
# Reads run in threads and touch no database connection. The writes happen on the calling thread, so the
|
|
93
|
+
# counters stay consistent.
|
|
94
|
+
def read(records)
|
|
95
|
+
return records.map { |record| [record, values_for(record)] } if concurrency == 1
|
|
96
|
+
|
|
97
|
+
records.map { |record| Thread.new { [record, values_for(record)] } }
|
|
98
|
+
.map(&:value)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Metadata reads lazily, so the values have to be resolved before the file goes out of scope.
|
|
102
|
+
def values_for(record)
|
|
103
|
+
record.with_data_file do |path|
|
|
104
|
+
metadata = DynamicImage::Metadata.new(path)
|
|
105
|
+
[metadata.frame_count, metadata.alpha?] if metadata.valid?
|
|
106
|
+
end
|
|
70
107
|
rescue Dis::Errors::NotFoundError
|
|
71
|
-
|
|
108
|
+
nil
|
|
72
109
|
end
|
|
73
110
|
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
metadata = DynamicImage::Metadata.new(path)
|
|
78
|
-
return @skipped += 1 unless metadata.valid?
|
|
111
|
+
# Written with update_columns so that no callbacks run.
|
|
112
|
+
def write(record, values)
|
|
113
|
+
return @skipped += 1 if values.nil?
|
|
79
114
|
|
|
80
|
-
record.update_columns(frame_count:
|
|
81
|
-
alpha: metadata.alpha?)
|
|
115
|
+
record.update_columns(frame_count: values.first, alpha: values.last)
|
|
82
116
|
@updated += 1
|
|
83
117
|
end
|
|
84
118
|
end
|
|
@@ -32,7 +32,7 @@ module DynamicImage
|
|
|
32
32
|
PROCESSED_FORMATS = %i[gif jpeg jpg jxl png tiff webp].freeze
|
|
33
33
|
|
|
34
34
|
# Additional formats +original+ and +download+ will serve.
|
|
35
|
-
STORED_FORMATS = (PROCESSED_FORMATS + %i[avif
|
|
35
|
+
STORED_FORMATS = (PROCESSED_FORMATS + %i[avif heic]).freeze
|
|
36
36
|
|
|
37
37
|
included do
|
|
38
38
|
before_action :verify_signed_params
|
data/lib/dynamic_image/engine.rb
CHANGED
|
@@ -19,7 +19,6 @@ module DynamicImage
|
|
|
19
19
|
|
|
20
20
|
initializer "dynamic_image.mime_types" do
|
|
21
21
|
Mime::Type.register "image/avif", :avif
|
|
22
|
-
Mime::Type.register "image/bmp", :bmp
|
|
23
22
|
Mime::Type.register "image/gif", :gif
|
|
24
23
|
Mime::Type.register "image/heic", :heic, %w[image/heif]
|
|
25
24
|
Mime::Type.register "image/jpeg", :jpg
|
data/lib/dynamic_image/format.rb
CHANGED
|
@@ -191,13 +191,6 @@ module DynamicImage
|
|
|
191
191
|
signature: ->(bytes) { iso_brands(bytes).intersect?(%w[avif avis]) }
|
|
192
192
|
)
|
|
193
193
|
|
|
194
|
-
register(
|
|
195
|
-
"BMP",
|
|
196
|
-
content_type: %w[image/bmp],
|
|
197
|
-
extension: %w[.bmp],
|
|
198
|
-
magic_bytes: ["\x42\x4d"]
|
|
199
|
-
)
|
|
200
|
-
|
|
201
194
|
register(
|
|
202
195
|
"GIF",
|
|
203
196
|
animated: true,
|
data/lib/dynamic_image/model.rb
CHANGED
|
@@ -71,13 +71,17 @@ module DynamicImage
|
|
|
71
71
|
before_validation :read_image_metadata, if: :data_changed?
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
# Returns true if the image holds more than one frame.
|
|
74
|
+
# Returns true if the image holds more than one frame in a format that renders them.
|
|
75
75
|
#
|
|
76
|
-
# Images stored before +frame_count+ existed have none, and are taken to be still.
|
|
76
|
+
# Images stored before +frame_count+ existed have none, and are taken to be still. A multi-page document in a
|
|
77
|
+
# format that isn't animated, such as TIFF, is also still: {DynamicImage::ImageReader} loads only its first
|
|
78
|
+
# page.
|
|
77
79
|
#
|
|
78
80
|
# @return [Boolean]
|
|
79
81
|
def animated?
|
|
80
|
-
has_attribute?(:frame_count) && frame_count.to_i > 1
|
|
82
|
+
return false unless has_attribute?(:frame_count) && frame_count.to_i > 1
|
|
83
|
+
|
|
84
|
+
DynamicImage::Format.content_type(content_type)&.animated? || false
|
|
81
85
|
end
|
|
82
86
|
|
|
83
87
|
# Returns the alt text for the image, or nil if none has been set.
|