oximg 0.18.0-aarch64-linux-gnu → 0.18.1-aarch64-linux-gnu

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb8e10a4f8d26016ab0a30feea53bd15ae14f8c40c101af4af254faa385b2431
4
- data.tar.gz: 86643933723d7deb8f050df9b090baea89b70a30263eee9df848f13fa2831b5a
3
+ metadata.gz: d4d3cb7265d61a0d89d9851f6e0938951ca773603a02dcdc19ad5b8abf3d68de
4
+ data.tar.gz: 7cfa66df5ffac827bc245ddd387cc321054dabd6f97c97a13bf3e6b3e8ad50e5
5
5
  SHA512:
6
- metadata.gz: ec4435ce5ef51e46423e3cac7316f44dd2976c287481d4f67feeb074eefb234c6855ab0e766d30632da625a7e2c4f1f331eef6c15bc6a60959b201df85c48db0
7
- data.tar.gz: 8edf8c025ad4fd2a143a7e430d5e730a80577344b6a358d3406e20e2ed9057ee3fa8b98a8fafea0e2ec0cce6b484479405facbb6f36e6046cd449986bff743d0
6
+ metadata.gz: 1be6c2171b1eb682a4d495d3130eca32b7e856db393a5319aba109ada1f5d5cdd1e636ebf8072243894b58d6801849aa0cee30cb34d91adee4f0ff4593bfeb53
7
+ data.tar.gz: 5c2cf3b537d675e748cd62e3787a1008099ab2e16103ecdfee11d80a8e4b1535f81a5ebc3e3a7b5124410948042b230269f7ffae0dec5a75ea5f91770bfa6dbc
data/README.md CHANGED
@@ -31,7 +31,8 @@ the name does not say, and `:gif` is not a `format:` you can ask for.
31
31
 
32
32
  ```ruby
33
33
  Oximg.resize(source, destination, width: 0, height: 0,
34
- quality: nil, format: nil, preset: nil)
34
+ quality: nil, format: nil, preset: nil,
35
+ icc: nil, auto_rotate: nil)
35
36
  ```
36
37
 
37
38
  The source is fitted within `width` x `height` and **never enlarged**. A
@@ -50,6 +51,12 @@ Oximg.resize("photo.jpg", "smaller.jpg", quality: 70)
50
51
  | `quality` | 1–100, default 80. JPEG quality |
51
52
  | `format` | `:jpg`, `:jpeg`, `:png`, `:webp`, `:avif`. Defaults to the destination's extension, else the source's own format |
52
53
  | `preset` | `:jpegli` (default — maximum quality per byte), `:fast`, `:small` |
54
+ | `icc` | `:keep` or `:strip` the source's colour profile. Default: `OXIMG_ICC`, else keep |
55
+ | `auto_rotate` | `false` keeps the stored orientation, `true` applies the EXIF one. Default: `OXIMG_AUTO_ROTATE`, else true |
56
+
57
+ `icc` and `auto_rotate` apply to that call only, and win over the
58
+ environment, so a threaded job runner can treat one image differently
59
+ without touching `ENV`.
53
60
 
54
61
  `resize` returns the destination path and raises `Oximg::ProcessingError`
55
62
  on failure, carrying the binary's own message and exit status. Paths are
@@ -59,7 +66,7 @@ cannot be read as a flag.
59
66
  ## Stripping metadata
60
67
 
61
68
  ```ruby
62
- Oximg.strip(source, destination, icc: nil)
69
+ Oximg.strip(source, destination, icc: nil, env: nil)
63
70
  ```
64
71
 
65
72
  Removes EXIF (GPS, camera, timestamps), XMP, IPTC and comments from a
@@ -71,10 +78,26 @@ still displays upright. `icc: :strip` also drops the colour profile.
71
78
  The default is `:keep`, or `OXIMG_ICC`. Dropping a CMYK JPEG's profile
72
79
  raises `Oximg::ProcessingError`, as do GIF and AVIF sources.
73
80
 
81
+ ## Knobs, per process or per call
82
+
74
83
  Everything the server tunes through `OXIMG_*` environment variables
75
84
  applies here too, and is validated the same fail-closed way: a typo'd
76
85
  knob is a startup error, never a silent default.
77
86
 
87
+ To set one for a single call, pass `env:` to `resize` or `strip`:
88
+
89
+ ```ruby
90
+ Oximg.resize("in.jpg", "out.jpg", width: 750, env: {"OXIMG_JPEG_PROGRESSIVE" => "0"})
91
+ ```
92
+
93
+ Each call spawns its own process, and `env:` reaches that process
94
+ only: `ENV` is never touched, so concurrent calls in a threaded job
95
+ runner each get their own settings. Names must be in `Oximg::KNOBS`
96
+ (the pipeline knobs; server settings such as `OXIMG_KEY` are refused),
97
+ values are Strings or Integers, and `nil` unsets a name for that call.
98
+ An unknown name raises `ArgumentError` before anything runs; a bad
99
+ value is refused by the binary (`ProcessingError`, status 2).
100
+
78
101
  ## Probing
79
102
 
80
103
  ```ruby
@@ -138,8 +161,8 @@ Oximg.in_process_probe? #=> true on a platform gem
138
161
 
139
162
  ```ruby
140
163
  Oximg.available? #=> true
141
- Oximg.executable #=> "/…/gems/oximg-0.18.0-arm64-darwin/exe/oximg"
142
- Oximg.version #=> "0.18.0" # the binary's version, not the gem's
164
+ Oximg.executable #=> "/…/gems/oximg-0.18.1-arm64-darwin/exe/oximg"
165
+ Oximg.version #=> "0.18.1" # the binary's version, not the gem's
143
166
  ```
144
167
 
145
168
  `resize` shells out to that binary — one process per image, which is
data/exe/oximg CHANGED
Binary file
data/lib/oximg/binary.rb CHANGED
@@ -60,8 +60,14 @@ module Oximg
60
60
  # nothing in a filename can be interpreted as syntax. Returns
61
61
  # [stdout, stderr]; a non-zero exit raises with the binary's own
62
62
  # stderr, which already names what it refused.
63
- def run(*args)
64
- out, err, status = Open3.capture3(path, *args)
63
+ #
64
+ # +env+ is merged into the child's environment only (a nil value
65
+ # unsets that name there): one process per call means one call's
66
+ # knobs never reach another's, even across threads, and the
67
+ # parent's ENV is never touched. Validate it first; Oximg.resize
68
+ # and Oximg.strip do.
69
+ def run(*args, env: {})
70
+ out, err, status = Open3.capture3(env, path, *args)
65
71
  unless status.success?
66
72
  message = err.strip
67
73
  message = "oximg exited #{status.exitstatus}" if message.empty?
Binary file
data/lib/oximg/version.rb CHANGED
@@ -7,5 +7,5 @@ module Oximg
7
7
  # names a binary it does not carry. `Oximg.version` reports what the
8
8
  # resolved executable actually is, which can differ when it comes
9
9
  # from PATH.
10
- VERSION = "0.18.0"
10
+ VERSION = "0.18.1"
11
11
  end
data/lib/oximg.rb CHANGED
@@ -45,9 +45,52 @@ module Oximg
45
45
  # mozjpeg's fast/small trade differently.
46
46
  PRESETS = %i[jpegli fast small].freeze
47
47
 
48
- # What `strip` may do with an embedded colour profile.
48
+ # What `resize` and `strip` may do with an embedded colour profile.
49
49
  ICC_MODES = %i[keep strip].freeze
50
50
 
51
+ # The OXIMG_* pipeline knobs an +env:+ may set for one call. Pinned to
52
+ # the binary's own inventory (KNOBS in src/config.rs) by a test there,
53
+ # so a knob the binary gains or drops cannot drift from this list.
54
+ # Server-startup settings (OXIMG_KEY, OXIMG_BIND, ...) are not here:
55
+ # a one-shot never reads them.
56
+ KNOBS = %w[
57
+ OXIMG_TIMING
58
+ OXIMG_RESIZE
59
+ OXIMG_RESIZE_BACKEND
60
+ OXIMG_AUTO_ROTATE
61
+ OXIMG_ICC
62
+ OXIMG_DCT_MARGIN
63
+ OXIMG_LINEAR_SHRINK
64
+ OXIMG_JPEG_PROGRESSIVE
65
+ OXIMG_JPEG_SEQUENTIAL_PIXELS
66
+ OXIMG_JPEG_STREAMING_PIXELS
67
+ OXIMG_FLATTEN_BG
68
+ OXIMG_PNG_EFFORT
69
+ OXIMG_PNG_QUANTIZE
70
+ OXIMG_PNG_QUANTIZE_COLORS
71
+ OXIMG_WEBP_QUALITY
72
+ OXIMG_WEBP_EFFORT
73
+ OXIMG_WEBP_DECODE_THREADS
74
+ OXIMG_AVIF_QUALITY
75
+ OXIMG_AVIF_ALPHA_QUALITY
76
+ OXIMG_AVIF_SPEED
77
+ OXIMG_AVIF_DECODE_THREADS
78
+ OXIMG_MAX_SOURCE_BYTES
79
+ OXIMG_MAX_SRC_PIXELS
80
+ OXIMG_GIF_ANIMATION
81
+ OXIMG_MAX_ANIM_FRAMES
82
+ OXIMG_MAX_ANIM_WORK
83
+ OXIMG_ANIM_FRAME_STEP
84
+ OXIMG_MAX_DECODED_BYTES
85
+ OXIMG_LOG_DECODED_BYTES_ABOVE
86
+ OXIMG_UPSTREAM_CONNECT_TIMEOUT
87
+ OXIMG_UPSTREAM_TIMEOUT
88
+ OXIMG_GCS_ENDPOINT
89
+ OXIMG_S3_ENDPOINT
90
+ OXIMG_S3_PATH_STYLE
91
+ OXIMG_OVERLAP
92
+ ].freeze
93
+
51
94
  CONTENT_TYPE_FORMATS = {
52
95
  "image/jpeg" => :jpeg,
53
96
  "image/png" => :png,
@@ -106,10 +149,24 @@ module Oximg
106
149
  # The output format follows +format:+, else +destination+'s
107
150
  # extension, else the source's own format. Returns the destination
108
151
  # path.
109
- def resize(source, destination, width: 0, height: 0, quality: nil, format: nil, preset: nil)
152
+ #
153
+ # +icc:+ (:keep or :strip) and +auto_rotate:+ (true or false)
154
+ # override the binary's OXIMG_ICC and OXIMG_AUTO_ROTATE for this
155
+ # call only; nil leaves them to the environment (default: keep the
156
+ # profile, apply the EXIF orientation).
157
+ #
158
+ # +env:+ sets any other pipeline knob for this call only, e.g.
159
+ # <tt>env: {"OXIMG_JPEG_PROGRESSIVE" => "0"}</tt>: it reaches the
160
+ # one child process this call spawns, never ENV, so it is safe in a
161
+ # threaded job runner. Names must be in KNOBS; nil unsets a name
162
+ # for the call. The binary validates the values, fail-closed.
163
+ def resize(source, destination, width: 0, height: 0, quality: nil, format: nil, preset: nil,
164
+ icc: nil, auto_rotate: nil, env: nil)
110
165
  output = expand(destination, "destination")
111
- Binary.run(*resize_argv(source, output,
112
- width: width, height: height, quality: quality, format: format, preset: preset))
166
+ argv = resize_argv(source, output,
167
+ width: width, height: height, quality: quality, format: format, preset: preset,
168
+ icc: icc, auto_rotate: auto_rotate)
169
+ Binary.run(*argv, env: knob_env(env))
113
170
  output
114
171
  end
115
172
 
@@ -123,12 +180,37 @@ module Oximg
123
180
  #
124
181
  # This is what <tt>saver(strip: true)</tt> / <tt>-strip</tt> did
125
182
  # under libvips or ImageMagick, minus the re-encode.
126
- def strip(source, destination, icc: nil)
183
+ #
184
+ # +env:+ is per-call knobs, as for resize.
185
+ def strip(source, destination, icc: nil, env: nil)
127
186
  output = expand(destination, "destination")
128
- Binary.run(*strip_argv(source, output, icc: icc))
187
+ argv = strip_argv(source, output, icc: icc)
188
+ Binary.run(*argv, env: knob_env(env))
129
189
  output
130
190
  end
131
191
 
192
+ # The child environment an +env:+ turns into: String names and
193
+ # values, each name a known knob. A typo raises here rather than
194
+ # being passed along and ignored, which is the same fail-closed
195
+ # promise the binary makes about the values. Public for the same
196
+ # reason as resize_argv.
197
+ def knob_env(env)
198
+ return {} if env.nil?
199
+ raise ArgumentError, "env must be a Hash, got #{env.inspect}" unless env.is_a?(Hash)
200
+
201
+ env.each_with_object({}) do |(name, value), out|
202
+ key = name.to_s
203
+ unless KNOBS.include?(key)
204
+ raise ArgumentError, "unknown knob #{name.inspect} in env (one of Oximg::KNOBS)"
205
+ end
206
+ unless value.nil? || value.is_a?(String) || value.is_a?(Integer)
207
+ raise ArgumentError, "#{key} must be a String, an Integer or nil, got #{value.inspect}"
208
+ end
209
+
210
+ out[key] = value&.to_s
211
+ end
212
+ end
213
+
132
214
  # The argv `strip` would run, minus the executable; public for the
133
215
  # same reason as resize_argv.
134
216
  def strip_argv(source, destination, icc: nil)
@@ -180,7 +262,8 @@ module Oximg
180
262
  # The argv `resize` would run, minus the executable. Public because
181
263
  # it is the honest way to see what a call turns into — and the way
182
264
  # to test that without a binary present.
183
- def resize_argv(source, destination, width: 0, height: 0, quality: nil, format: nil, preset: nil)
265
+ def resize_argv(source, destination, width: 0, height: 0, quality: nil, format: nil, preset: nil,
266
+ icc: nil, auto_rotate: nil)
184
267
  argv = [
185
268
  "resize",
186
269
  expand(source, "source"),
@@ -191,6 +274,8 @@ module Oximg
191
274
  argv.push("-q", bounded(quality, "quality", 1, 100).to_s) unless quality.nil?
192
275
  argv.push("-f", token(format, FORMATS, "format")) unless format.nil?
193
276
  argv.push("--preset", token(preset, PRESETS, "preset")) unless preset.nil?
277
+ argv.push("--icc", token(icc, ICC_MODES, "icc")) unless icc.nil?
278
+ argv.push(boolean(auto_rotate, "auto_rotate") ? "--auto-rotate" : "--no-auto-rotate") unless auto_rotate.nil?
194
279
  argv
195
280
  end
196
281
 
@@ -319,6 +404,16 @@ module Oximg
319
404
  value
320
405
  end
321
406
 
407
+ # Strictly true or false: a truthy "0" or "false" read as true would
408
+ # rotate exactly the images the caller asked not to.
409
+ def boolean(value, name)
410
+ unless value == true || value == false
411
+ raise ArgumentError, "#{name} must be true or false, got #{value.inspect}"
412
+ end
413
+
414
+ value
415
+ end
416
+
322
417
  def token(value, allowed, name)
323
418
  token = value.to_s.downcase
324
419
  unless allowed.include?(token.to_sym)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: oximg
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.18.0
4
+ version: 0.18.1
5
5
  platform: aarch64-linux-gnu
6
6
  authors:
7
7
  - oximg contributors