oximg 0.19.0-x86_64-linux-gnu → 0.20.0-x86_64-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 +4 -4
- data/README.md +26 -4
- data/exe/oximg +0 -0
- data/lib/oximg/binary.rb +12 -0
- data/lib/oximg/native/liboximg_probe.so +0 -0
- data/lib/oximg/stripped.rb +109 -0
- data/lib/oximg/version.rb +1 -1
- data/lib/oximg.rb +57 -6
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '06849ede237a4dc45defa469d60d6eb4099d96faa5331943309d67fb663f3f28'
|
|
4
|
+
data.tar.gz: 71ea1303b3b7e214e1b45a356f13a020767e1d2a4e29261df5b8952b0fc28fe7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b16f37b956d93aef19e8878d8cc5577b0e6d88fd70bfa2270a49bad0aa2686fc149c982ca1aaf24593ad765ba28e34d84df1dff12737ed8d667c104472b0c19e
|
|
7
|
+
data.tar.gz: fa6968198360451ca0527316b722a2c643192016a980d46ab8e30969617b6682f70e00b2f6d492afa015cc8b85d8caca04fefd13e60cd37ac74301b4ae462708
|
data/README.md
CHANGED
|
@@ -85,10 +85,32 @@ re-encoding: its compressed 8x8 blocks are moved, as `jpegtran
|
|
|
85
85
|
reader, not only for ones that honor EXIF. That is only exact when the
|
|
86
86
|
edges it moves are whole 8 or 16 px blocks (a phone's 4032x3024 is);
|
|
87
87
|
otherwise, and for PNG and WebP, the file is stripped as without it,
|
|
88
|
-
orientation kept.
|
|
89
|
-
1, or the one kept. It costs a full entropy decode and encode, about
|
|
88
|
+
orientation kept. It costs a full entropy decode and encode, about
|
|
90
89
|
170 ms for a 12 MP photo.
|
|
91
90
|
|
|
91
|
+
`strip` returns an `Oximg::Stripped`, the binary's own account of the
|
|
92
|
+
run, so telling which happened needs no second read:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
result = Oximg.strip(upload, clean, icc: :keep, auto_rotate: true)
|
|
96
|
+
result.upright? # no orientation tag left: stored size is displayed size
|
|
97
|
+
result.rotated? # the pixels were turned upright
|
|
98
|
+
result.orientation #=> 6, the tag kept, or nil
|
|
99
|
+
result.not_rotated&.code #=> :unaligned_edges, :corrupt, :over_limit or :not_jpeg
|
|
100
|
+
result.not_rotated&.message # for humans; the wording may change
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`not_rotated` is nil unless `auto_rotate: true` left a rotated source
|
|
104
|
+
tagged. The code is the contract (the table is in
|
|
105
|
+
[docs/features/paths.md](../../docs/features/paths.md)), so count
|
|
106
|
+
fallbacks by it. The result also stands in for the destination path
|
|
107
|
+
`strip` used to return: `to_s`, `to_path` and `to_str` are the path and
|
|
108
|
+
it compares equal to that String, so `File.size(Oximg.strip(...))`
|
|
109
|
+
still works. With an `oximg` older than 0.20, which has no `strip
|
|
110
|
+
--json`, the gem reads the summary line that binary prints instead:
|
|
111
|
+
`orientation` and `rotated?` are still right, `removed` is nil, and
|
|
112
|
+
the code is `:unknown` beside the binary's own reason.
|
|
113
|
+
|
|
92
114
|
## Knobs, per process or per call
|
|
93
115
|
|
|
94
116
|
Everything the server tunes through `OXIMG_*` environment variables
|
|
@@ -181,8 +203,8 @@ Oximg.in_process_probe? #=> true on a platform gem
|
|
|
181
203
|
|
|
182
204
|
```ruby
|
|
183
205
|
Oximg.available? #=> true
|
|
184
|
-
Oximg.executable #=> "/…/gems/oximg-0.
|
|
185
|
-
Oximg.version #=> "0.
|
|
206
|
+
Oximg.executable #=> "/…/gems/oximg-0.20.0-arm64-darwin/exe/oximg"
|
|
207
|
+
Oximg.version #=> "0.20.0" # the binary's version, not the gem's
|
|
186
208
|
```
|
|
187
209
|
|
|
188
210
|
`resize` shells out to that binary — one process per image, which is
|
data/exe/oximg
CHANGED
|
Binary file
|
data/lib/oximg/binary.rb
CHANGED
|
@@ -76,11 +76,23 @@ module Oximg
|
|
|
76
76
|
[out, err]
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
+
# True once the executable refused `strip --json` (an oximg older
|
|
80
|
+
# than 0.20): Oximg.strip then runs without it and reads the
|
|
81
|
+
# summary line that binary prints. Memoized per executable, like
|
|
82
|
+
# the path, so a process
|
|
83
|
+
# pays the refused spawn once.
|
|
84
|
+
attr_accessor :legacy_strip
|
|
85
|
+
|
|
86
|
+
def legacy_strip?
|
|
87
|
+
@legacy_strip == true
|
|
88
|
+
end
|
|
89
|
+
|
|
79
90
|
# Resets the memoized lookup. For tests, and for a process that
|
|
80
91
|
# changes OXIMG_BIN after boot.
|
|
81
92
|
def reset!
|
|
82
93
|
@path = nil
|
|
83
94
|
@version = nil
|
|
95
|
+
@legacy_strip = nil
|
|
84
96
|
end
|
|
85
97
|
|
|
86
98
|
private
|
|
Binary file
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Oximg
|
|
4
|
+
# What Oximg.strip did: where it wrote, and whether the output
|
|
5
|
+
# displays upright. Built from <tt>oximg strip --json</tt>, so it is
|
|
6
|
+
# the binary's own account of the run, not a second read of the file.
|
|
7
|
+
#
|
|
8
|
+
# It stands in for the destination path Oximg.strip returned before
|
|
9
|
+
# (#176): #to_s, #to_path and #to_str are the path, and it compares
|
|
10
|
+
# equal to that path as a String, so
|
|
11
|
+
# <tt>File.size(Oximg.strip(...))</tt>, <tt>File.join</tt> and
|
|
12
|
+
# <tt>assert_equal out, Oximg.strip(...)</tt> keep working.
|
|
13
|
+
#
|
|
14
|
+
# result = Oximg.strip(upload, clean, auto_rotate: true)
|
|
15
|
+
# result.rotated? #=> false
|
|
16
|
+
# result.orientation #=> 6, the tag the output keeps
|
|
17
|
+
# result.not_rotated.code #=> :unaligned_edges
|
|
18
|
+
# result.not_rotated.message #=> "1601x1203 does not divide into ..."
|
|
19
|
+
class Stripped
|
|
20
|
+
# Why <tt>auto_rotate: true</tt> left a rotated source tagged.
|
|
21
|
+
# +code+ is the contract, a Symbol from NOT_ROTATED_CODES (or
|
|
22
|
+
# :unknown, below); +message+ is for humans and may change.
|
|
23
|
+
NotRotated = Data.define(:code, :message)
|
|
24
|
+
|
|
25
|
+
# The binary's codes (docs/features/paths.md). A code missing here
|
|
26
|
+
# still comes through, as its Symbol: a newer binary may add one.
|
|
27
|
+
NOT_ROTATED_CODES = %i[unaligned_edges corrupt over_limit not_jpeg].freeze
|
|
28
|
+
|
|
29
|
+
# The destination, absolute.
|
|
30
|
+
attr_reader :path
|
|
31
|
+
# The output's content type ("image/jpeg", ...).
|
|
32
|
+
attr_reader :content_type
|
|
33
|
+
# Output size in bytes.
|
|
34
|
+
attr_reader :bytes
|
|
35
|
+
# What was dropped: <tt>[{name: "APP1 Exif", count: 1, bytes: 1204}, ...]</tt>;
|
|
36
|
+
# nil from an oximg older than 0.20.
|
|
37
|
+
attr_reader :removed
|
|
38
|
+
# The orientation tag the output keeps, 2..8, or nil when it
|
|
39
|
+
# displays as stored (upright, untagged, or rotated upright).
|
|
40
|
+
attr_reader :orientation
|
|
41
|
+
# The orientation turned into the pixels, 2..8, or nil.
|
|
42
|
+
attr_reader :rotated
|
|
43
|
+
# A NotRotated, or nil unless auto_rotate left a rotated source
|
|
44
|
+
# tagged. From an oximg older than 0.20, which has no --json and
|
|
45
|
+
# gives the reason only as a sentence, the code is :unknown.
|
|
46
|
+
attr_reader :not_rotated
|
|
47
|
+
|
|
48
|
+
# From the hash <tt>oximg strip --json</tt> prints.
|
|
49
|
+
def self.from_json(path, report)
|
|
50
|
+
why = report["not_rotated"]
|
|
51
|
+
new(
|
|
52
|
+
path: path,
|
|
53
|
+
content_type: report["content_type"],
|
|
54
|
+
bytes: report["bytes"],
|
|
55
|
+
removed: report["removed"]&.map { |r| {name: r["name"], count: r["count"], bytes: r["bytes"]} },
|
|
56
|
+
orientation: report["orientation"],
|
|
57
|
+
rotated: report["rotated"],
|
|
58
|
+
not_rotated: why && NotRotated.new(code: why["code"].to_sym, message: why["message"])
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def initialize(path:, bytes:, content_type:, orientation:, rotated:, not_rotated:, removed: nil)
|
|
63
|
+
@path = path
|
|
64
|
+
@content_type = content_type
|
|
65
|
+
@bytes = bytes
|
|
66
|
+
@removed = removed
|
|
67
|
+
@orientation = orientation
|
|
68
|
+
@rotated = rotated
|
|
69
|
+
@not_rotated = not_rotated
|
|
70
|
+
freeze
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# True when the pixels were turned upright (auto_rotate).
|
|
74
|
+
def rotated?
|
|
75
|
+
!@rotated.nil?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# True when the output displays as stored: no orientation tag left,
|
|
79
|
+
# so its stored width and height are the displayed ones.
|
|
80
|
+
def upright?
|
|
81
|
+
@orientation.nil?
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def to_s
|
|
85
|
+
@path
|
|
86
|
+
end
|
|
87
|
+
alias_method :to_path, :to_s
|
|
88
|
+
alias_method :to_str, :to_s
|
|
89
|
+
|
|
90
|
+
# Equal to another result for the same run, or to the destination
|
|
91
|
+
# path as a String (String#== defers here for anything with to_str).
|
|
92
|
+
def ==(other)
|
|
93
|
+
case other
|
|
94
|
+
when Stripped then to_h == other.to_h
|
|
95
|
+
when String then @path == other
|
|
96
|
+
else false
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def to_h
|
|
101
|
+
{path: @path, content_type: @content_type, bytes: @bytes, removed: @removed,
|
|
102
|
+
orientation: @orientation, rotated: @rotated, not_rotated: @not_rotated&.to_h}
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def inspect
|
|
106
|
+
"#<#{self.class.name} #{@path} #{to_h.except(:path).compact.inspect}>"
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
data/lib/oximg/version.rb
CHANGED
data/lib/oximg.rb
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "fcntl"
|
|
4
|
+
require "json"
|
|
4
5
|
require "tempfile"
|
|
5
6
|
require_relative "oximg/version"
|
|
6
7
|
require_relative "oximg/binary"
|
|
7
8
|
require_relative "oximg/native"
|
|
9
|
+
require_relative "oximg/stripped"
|
|
8
10
|
|
|
9
11
|
# Ruby bindings for oximg (https://github.com/oximg/oximg):
|
|
10
12
|
# high-performance image compression and resizing, in-process work for a
|
|
@@ -198,7 +200,12 @@ module Oximg
|
|
|
198
200
|
# a rotated photo keeps only its EXIF orientation, so it still
|
|
199
201
|
# displays upright. +icc:+ is :keep or :strip (default: the
|
|
200
202
|
# binary's OXIMG_ICC, else keep); dropping a CMYK JPEG's profile is
|
|
201
|
-
# refused with a ProcessingError.
|
|
203
|
+
# refused with a ProcessingError.
|
|
204
|
+
#
|
|
205
|
+
# Returns an Oximg::Stripped: the binary's own account of the run
|
|
206
|
+
# (orientation kept, rotated?, not_rotated with a stable code). It
|
|
207
|
+
# stands in for the destination path this returned before:
|
|
208
|
+
# #to_path, #to_str and == against a String are the path.
|
|
202
209
|
#
|
|
203
210
|
# This is what <tt>saver(strip: true)</tt> / <tt>-strip</tt> did
|
|
204
211
|
# under libvips or ImageMagick, minus the re-encode.
|
|
@@ -209,14 +216,33 @@ module Oximg
|
|
|
209
216
|
# dimensions agree for every reader and no orientation tag is left.
|
|
210
217
|
# That is only exact when the edges it moves are whole 8 or 16 px
|
|
211
218
|
# blocks; otherwise, and for PNG and WebP, the orientation is kept
|
|
212
|
-
# as without it
|
|
219
|
+
# as without it, and the result says why:
|
|
220
|
+
#
|
|
221
|
+
# result = Oximg.strip(upload, clean, auto_rotate: true)
|
|
222
|
+
# unless result.upright?
|
|
223
|
+
# result.not_rotated.code #=> :unaligned_edges, :corrupt, :over_limit, :not_jpeg
|
|
224
|
+
# end
|
|
213
225
|
#
|
|
214
226
|
# +env:+ is per-call knobs, as for resize.
|
|
215
227
|
def strip(source, destination, icc: nil, auto_rotate: nil, env: nil)
|
|
216
228
|
output = expand(destination, "destination")
|
|
217
229
|
argv = strip_argv(source, output, icc: icc, auto_rotate: auto_rotate)
|
|
218
|
-
|
|
219
|
-
|
|
230
|
+
child_env = knob_env(env)
|
|
231
|
+
unless Binary.legacy_strip?
|
|
232
|
+
begin
|
|
233
|
+
out, = Binary.run(*argv, "--json", env: child_env)
|
|
234
|
+
return Stripped.from_json(output, JSON.parse(out))
|
|
235
|
+
rescue ProcessingError => e
|
|
236
|
+
# An oximg older than 0.20 refuses the flag at parse time,
|
|
237
|
+
# before it reads or writes anything: remember that, and run
|
|
238
|
+
# the strip it does know.
|
|
239
|
+
raise unless e.status == 2 && e.message.include?('unknown option "--json"')
|
|
240
|
+
|
|
241
|
+
Binary.legacy_strip = true
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
_, err = Binary.run(*argv, env: child_env)
|
|
245
|
+
legacy_stripped(output, err)
|
|
220
246
|
end
|
|
221
247
|
|
|
222
248
|
# The child environment an +env:+ turns into: String names and
|
|
@@ -241,8 +267,9 @@ module Oximg
|
|
|
241
267
|
end
|
|
242
268
|
end
|
|
243
269
|
|
|
244
|
-
# The argv `strip` would run, minus the executable
|
|
245
|
-
# same reason as
|
|
270
|
+
# The argv `strip` would run, minus the executable and the --json it
|
|
271
|
+
# adds to read the outcome; public for the same reason as
|
|
272
|
+
# resize_argv.
|
|
246
273
|
def strip_argv(source, destination, icc: nil, auto_rotate: nil)
|
|
247
274
|
argv = ["strip", expand(source, "source"), expand(destination, "destination")]
|
|
248
275
|
argv.push("--icc", token(icc, ICC_MODES, "icc")) unless icc.nil?
|
|
@@ -402,6 +429,30 @@ module Oximg
|
|
|
402
429
|
Header.build(match[1], Integer(match[2]), Integer(match[3]), match[4] ? Integer(match[4]) : 1)
|
|
403
430
|
end
|
|
404
431
|
|
|
432
|
+
# The summary every oximg since 0.18 ends a strip with, after
|
|
433
|
+
# "oximg: wrote <out> " (src/cli.rs). Those releases are frozen, so
|
|
434
|
+
# their wording is a fixed format here, though it is not a contract
|
|
435
|
+
# for later ones (they have --json). Removed block names are JPEG
|
|
436
|
+
# segment names or 4-byte chunk types, so the tail is unambiguous.
|
|
437
|
+
LEGACY_STRIP_SUMMARY = /\A\((\d+) bytes, ([^;\s]+); (.*?)(?:; kept orientation ([2-8])(?:, not rotated: (.*))?|; rotated orientation ([2-8]) upright)?\)\z/m
|
|
438
|
+
|
|
439
|
+
# The result from an oximg without --json, read from that summary
|
|
440
|
+
# rather than by probing: probe stops looking for the orientation
|
|
441
|
+
# after 1 MiB of metadata and needs a header libjpeg reads, where
|
|
442
|
+
# strip does neither. The reason a rotation was not made has no
|
|
443
|
+
# code before 0.20, so it is :unknown, with the binary's sentence.
|
|
444
|
+
def legacy_stripped(output, stderr)
|
|
445
|
+
prefix = "oximg: wrote #{output} "
|
|
446
|
+
line = stderr.lines.map(&:chomp).reverse.find { |l| l.start_with?(prefix) }
|
|
447
|
+
match = line && line.delete_prefix(prefix).match(LEGACY_STRIP_SUMMARY)
|
|
448
|
+
raise ProcessingError, "unparsable strip summary: #{stderr.strip.inspect}" unless match
|
|
449
|
+
|
|
450
|
+
kept = match[4] && Integer(match[4])
|
|
451
|
+
why = match[5] && Stripped::NotRotated.new(code: :unknown, message: match[5])
|
|
452
|
+
Stripped.new(path: output, bytes: Integer(match[1]), content_type: match[2],
|
|
453
|
+
orientation: kept, rotated: match[6] && Integer(match[6]), not_rotated: why)
|
|
454
|
+
end
|
|
455
|
+
|
|
405
456
|
# Absolute paths throughout: a relative name beginning with "-"
|
|
406
457
|
# would otherwise reach the CLI's argument parser as a flag, and
|
|
407
458
|
# the caller rarely controls what an uploaded file is called.
|
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.
|
|
4
|
+
version: 0.20.0
|
|
5
5
|
platform: x86_64-linux-gnu
|
|
6
6
|
authors:
|
|
7
7
|
- oximg contributors
|
|
@@ -37,6 +37,7 @@ files:
|
|
|
37
37
|
- lib/oximg/binary.rb
|
|
38
38
|
- lib/oximg/native.rb
|
|
39
39
|
- lib/oximg/native/liboximg_probe.so
|
|
40
|
+
- lib/oximg/stripped.rb
|
|
40
41
|
- lib/oximg/version.rb
|
|
41
42
|
homepage: https://github.com/oximg/oximg
|
|
42
43
|
licenses:
|