image_optim 0.28.0 → 0.31.1
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/.github/workflows/check.yml +89 -0
- data/.pre-commit-hooks.yaml +9 -0
- data/.rubocop.yml +6 -3
- data/CHANGELOG.markdown +20 -0
- data/CONTRIBUTING.markdown +1 -1
- data/Gemfile +1 -7
- data/LICENSE.txt +1 -1
- data/README.markdown +18 -9
- data/Vagrantfile +1 -1
- data/image_optim.gemspec +6 -3
- data/lib/image_optim/bin_resolver/bin.rb +9 -9
- data/lib/image_optim/cache.rb +6 -0
- data/lib/image_optim/cmd.rb +45 -6
- data/lib/image_optim/config.rb +11 -5
- data/lib/image_optim/elapsed_time.rb +26 -0
- data/lib/image_optim/errors.rb +9 -0
- data/lib/image_optim/path.rb +1 -1
- data/lib/image_optim/runner/option_parser.rb +21 -17
- data/lib/image_optim/runner.rb +1 -1
- data/lib/image_optim/timer.rb +25 -0
- data/lib/image_optim/worker/advpng.rb +7 -7
- data/lib/image_optim/worker/gifsicle.rb +11 -11
- data/lib/image_optim/worker/jhead.rb +2 -2
- data/lib/image_optim/worker/jpegoptim.rb +11 -11
- data/lib/image_optim/worker/jpegrecompress.rb +6 -6
- data/lib/image_optim/worker/jpegtran.rb +4 -4
- data/lib/image_optim/worker/optipng.rb +7 -7
- data/lib/image_optim/worker/oxipng.rb +53 -0
- data/lib/image_optim/worker/pngcrush.rb +6 -6
- data/lib/image_optim/worker/pngout.rb +7 -7
- data/lib/image_optim/worker/pngquant.rb +9 -9
- data/lib/image_optim/worker/svgo.rb +2 -2
- data/lib/image_optim/worker.rb +32 -29
- data/lib/image_optim.rb +16 -10
- data/script/update_worker_options_in_readme +1 -1
- data/script/worker_analysis +16 -18
- data/spec/image_optim/bin_resolver_spec.rb +5 -5
- data/spec/image_optim/cache_path_spec.rb +7 -10
- data/spec/image_optim/cache_spec.rb +7 -7
- data/spec/image_optim/cmd_spec.rb +64 -6
- data/spec/image_optim/config_spec.rb +36 -20
- data/spec/image_optim/elapsed_time_spec.rb +14 -0
- data/spec/image_optim/handler_spec.rb +1 -1
- data/spec/image_optim/hash_helpers_spec.rb +18 -18
- data/spec/image_optim/option_definition_spec.rb +6 -6
- data/spec/image_optim/path_spec.rb +8 -11
- data/spec/image_optim/runner/option_parser_spec.rb +4 -4
- data/spec/image_optim/timer_spec.rb +32 -0
- data/spec/image_optim/worker/jpegrecompress_spec.rb +2 -2
- data/spec/image_optim/worker/optipng_spec.rb +11 -11
- data/spec/image_optim/worker/oxipng_spec.rb +89 -0
- data/spec/image_optim/worker/pngquant_spec.rb +5 -5
- data/spec/image_optim/worker_spec.rb +17 -17
- data/spec/image_optim_spec.rb +47 -10
- data/spec/images/invisiblepixels/generate +1 -1
- data/spec/spec_helper.rb +24 -21
- metadata +35 -11
- data/.appveyor.yml +0 -53
- data/.travis.yml +0 -48
data/lib/image_optim/runner.rb
CHANGED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'image_optim/elapsed_time'
|
4
|
+
|
5
|
+
class ImageOptim
|
6
|
+
# Hold start time and timeout
|
7
|
+
class Timer
|
8
|
+
include ElapsedTime
|
9
|
+
|
10
|
+
def initialize(seconds)
|
11
|
+
@start = now
|
12
|
+
@seconds = seconds
|
13
|
+
end
|
14
|
+
|
15
|
+
def elapsed
|
16
|
+
now - @start
|
17
|
+
end
|
18
|
+
|
19
|
+
def left
|
20
|
+
@seconds - elapsed
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method :to_f, :left
|
24
|
+
end
|
25
|
+
end
|
@@ -9,11 +9,11 @@ class ImageOptim
|
|
9
9
|
class Advpng < Worker
|
10
10
|
LEVEL_OPTION =
|
11
11
|
option(:level, 4, 'Compression level: '\
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
'`0` - don\'t compress, '\
|
13
|
+
'`1` - fast, '\
|
14
|
+
'`2` - normal, '\
|
15
|
+
'`3` - extra, '\
|
16
|
+
'`4` - extreme') do |v|
|
17
17
|
OptionHelpers.limit_with_range(v.to_i, 0..4)
|
18
18
|
end
|
19
19
|
|
@@ -21,7 +21,7 @@ class ImageOptim
|
|
21
21
|
4
|
22
22
|
end
|
23
23
|
|
24
|
-
def optimize(src, dst)
|
24
|
+
def optimize(src, dst, options = {})
|
25
25
|
src.copy(dst)
|
26
26
|
args = %W[
|
27
27
|
--recompress
|
@@ -30,7 +30,7 @@ class ImageOptim
|
|
30
30
|
--
|
31
31
|
#{dst}
|
32
32
|
]
|
33
|
-
execute(:advpng,
|
33
|
+
execute(:advpng, args, options) && optimized?(src, dst)
|
34
34
|
end
|
35
35
|
end
|
36
36
|
end
|
@@ -12,32 +12,32 @@ class ImageOptim
|
|
12
12
|
return super if options.key?(:interlace)
|
13
13
|
|
14
14
|
[false, true].map do |interlace|
|
15
|
-
new(image_optim, options.merge(:
|
15
|
+
new(image_optim, options.merge(interlace: interlace))
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
19
|
INTERLACE_OPTION =
|
20
20
|
option(:interlace, false, TrueFalseNil, 'Interlace: '\
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
21
|
+
'`true` - interlace on, '\
|
22
|
+
'`false` - interlace off, '\
|
23
|
+
'`nil` - as is in original image '\
|
24
|
+
'(defaults to running two instances, one with interlace off and '\
|
25
|
+
'one with on)') do |v|
|
26
26
|
TrueFalseNil.convert(v)
|
27
27
|
end
|
28
28
|
|
29
29
|
LEVEL_OPTION =
|
30
30
|
option(:level, 3, 'Compression level: '\
|
31
|
-
|
32
|
-
|
33
|
-
|
31
|
+
'`1` - light and fast, '\
|
32
|
+
'`2` - normal, '\
|
33
|
+
'`3` - heavy (slower)') do |v|
|
34
34
|
OptionHelpers.limit_with_range(v.to_i, 1..3)
|
35
35
|
end
|
36
36
|
|
37
37
|
CAREFUL_OPTION =
|
38
38
|
option(:careful, false, 'Avoid bugs with some software'){ |v| !!v }
|
39
39
|
|
40
|
-
def optimize(src, dst)
|
40
|
+
def optimize(src, dst, options = {})
|
41
41
|
args = %W[
|
42
42
|
--output=#{dst}
|
43
43
|
--no-comments
|
@@ -58,7 +58,7 @@ class ImageOptim
|
|
58
58
|
end
|
59
59
|
args.unshift '--careful' if careful
|
60
60
|
args.unshift "--optimize=#{level}" if level
|
61
|
-
execute(:gifsicle,
|
61
|
+
execute(:gifsicle, args, options) && optimized?(src, dst)
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
@@ -25,7 +25,7 @@ class ImageOptim
|
|
25
25
|
[:jhead, :jpegtran]
|
26
26
|
end
|
27
27
|
|
28
|
-
def optimize(src, dst)
|
28
|
+
def optimize(src, dst, options = {})
|
29
29
|
return false unless oriented?(src)
|
30
30
|
|
31
31
|
src.copy(dst)
|
@@ -34,7 +34,7 @@ class ImageOptim
|
|
34
34
|
#{dst}
|
35
35
|
]
|
36
36
|
resolve_bin!(:jpegtran)
|
37
|
-
execute(:jhead,
|
37
|
+
execute(:jhead, args, options) && dst.size?
|
38
38
|
end
|
39
39
|
|
40
40
|
private
|
@@ -12,13 +12,13 @@ class ImageOptim
|
|
12
12
|
|
13
13
|
STRIP_OPTION =
|
14
14
|
option(:strip, :all, Array, 'List of markers to strip: '\
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
15
|
+
'`:com`, '\
|
16
|
+
'`:exif`, '\
|
17
|
+
'`:iptc`, '\
|
18
|
+
'`:icc`, '\
|
19
|
+
'`:xmp`, '\
|
20
|
+
'`:none` or '\
|
21
|
+
'`:all`') do |v|
|
22
22
|
values = Array(v).map(&:to_s)
|
23
23
|
known_values = %w[com exif iptc icc xmp none all]
|
24
24
|
unknown_values = values - known_values
|
@@ -30,13 +30,13 @@ class ImageOptim
|
|
30
30
|
|
31
31
|
MAX_QUALITY_OPTION =
|
32
32
|
option(:max_quality, 100, 'Maximum image quality factor '\
|
33
|
-
|
33
|
+
'`0`..`100`, ignored in default/lossless mode') do |v, opt_def|
|
34
34
|
if allow_lossy
|
35
35
|
OptionHelpers.limit_with_range(v.to_i, 0..100)
|
36
36
|
else
|
37
37
|
if v != opt_def.default
|
38
38
|
warn "#{self.class.bin_sym} #{opt_def.name} #{v} ignored " \
|
39
|
-
|
39
|
+
'in lossless mode'
|
40
40
|
end
|
41
41
|
opt_def.default
|
42
42
|
end
|
@@ -47,7 +47,7 @@ class ImageOptim
|
|
47
47
|
max_quality < 100 ? -1 : 0
|
48
48
|
end
|
49
49
|
|
50
|
-
def optimize(src, dst)
|
50
|
+
def optimize(src, dst, options = {})
|
51
51
|
src.copy(dst)
|
52
52
|
args = %W[
|
53
53
|
--quiet
|
@@ -58,7 +58,7 @@ class ImageOptim
|
|
58
58
|
args.unshift "--strip-#{strip_marker}"
|
59
59
|
end
|
60
60
|
args.unshift "--max=#{max_quality}" if max_quality < 100
|
61
|
-
execute(:jpegoptim,
|
61
|
+
execute(:jpegoptim, args, options) && optimized?(src, dst)
|
62
62
|
end
|
63
63
|
end
|
64
64
|
end
|
@@ -28,10 +28,10 @@ class ImageOptim
|
|
28
28
|
|
29
29
|
METHOD_OPTION =
|
30
30
|
option(:method, 'ssim', 'Comparison Metric: '\
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
'`mpe` - Mean pixel error, '\
|
32
|
+
'`ssim` - Structural similarity, '\
|
33
|
+
'`ms-ssim` - Multi-scale structural similarity (slow!), '\
|
34
|
+
'`smallfry` - Linear-weighted BBCQ-like (may be patented)') do |v, opt_def|
|
35
35
|
if %w[mpe ssim ms-ssim smallfry].include? v
|
36
36
|
v
|
37
37
|
else
|
@@ -49,7 +49,7 @@ class ImageOptim
|
|
49
49
|
-5
|
50
50
|
end
|
51
51
|
|
52
|
-
def optimize(src, dst)
|
52
|
+
def optimize(src, dst, options = {})
|
53
53
|
args = %W[
|
54
54
|
--quality #{QUALITY_NAMES[quality]}
|
55
55
|
--method #{method}
|
@@ -57,7 +57,7 @@ class ImageOptim
|
|
57
57
|
#{src}
|
58
58
|
#{dst}
|
59
59
|
]
|
60
|
-
execute(:'jpeg-recompress',
|
60
|
+
execute(:'jpeg-recompress', args, options) && optimized?(src, dst)
|
61
61
|
end
|
62
62
|
end
|
63
63
|
end
|
@@ -17,13 +17,13 @@ class ImageOptim
|
|
17
17
|
|
18
18
|
JPEGRESCAN_OPTION =
|
19
19
|
option(:jpegrescan, true, 'Use jpegtran through jpegrescan, '\
|
20
|
-
|
20
|
+
'ignore progressive option'){ |v| !!v }
|
21
21
|
|
22
22
|
def used_bins
|
23
23
|
jpegrescan ? [:jpegtran, :jpegrescan] : [:jpegtran]
|
24
24
|
end
|
25
25
|
|
26
|
-
def optimize(src, dst)
|
26
|
+
def optimize(src, dst, options = {})
|
27
27
|
if jpegrescan
|
28
28
|
args = %W[
|
29
29
|
#{src}
|
@@ -31,7 +31,7 @@ class ImageOptim
|
|
31
31
|
]
|
32
32
|
args.unshift '-s' unless copy_chunks
|
33
33
|
resolve_bin!(:jpegtran)
|
34
|
-
execute(:jpegrescan,
|
34
|
+
execute(:jpegrescan, args, options) && optimized?(src, dst)
|
35
35
|
else
|
36
36
|
args = %W[
|
37
37
|
-optimize
|
@@ -40,7 +40,7 @@ class ImageOptim
|
|
40
40
|
]
|
41
41
|
args.unshift '-copy', (copy_chunks ? 'all' : 'none')
|
42
42
|
args.unshift '-progressive' if progressive
|
43
|
-
execute(:jpegtran,
|
43
|
+
execute(:jpegtran, args, options) && optimized?(src, dst)
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
@@ -10,16 +10,16 @@ class ImageOptim
|
|
10
10
|
class Optipng < Worker
|
11
11
|
LEVEL_OPTION =
|
12
12
|
option(:level, 6, 'Optimization level preset: '\
|
13
|
-
|
14
|
-
|
13
|
+
'`0` is least, '\
|
14
|
+
'`7` is best') do |v|
|
15
15
|
OptionHelpers.limit_with_range(v.to_i, 0..7)
|
16
16
|
end
|
17
17
|
|
18
18
|
INTERLACE_OPTION =
|
19
19
|
option(:interlace, false, TrueFalseNil, 'Interlace: '\
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
'`true` - interlace on, '\
|
21
|
+
'`false` - interlace off, '\
|
22
|
+
'`nil` - as is in original image') do |v|
|
23
23
|
TrueFalseNil.convert(v)
|
24
24
|
end
|
25
25
|
|
@@ -30,7 +30,7 @@ class ImageOptim
|
|
30
30
|
-4
|
31
31
|
end
|
32
32
|
|
33
|
-
def optimize(src, dst)
|
33
|
+
def optimize(src, dst, options = {})
|
34
34
|
src.copy(dst)
|
35
35
|
args = %W[
|
36
36
|
-o #{level}
|
@@ -42,7 +42,7 @@ class ImageOptim
|
|
42
42
|
if strip && resolve_bin!(:optipng).version >= '0.7'
|
43
43
|
args.unshift '-strip', 'all'
|
44
44
|
end
|
45
|
-
execute(:optipng,
|
45
|
+
execute(:optipng, args, options) && optimized?(src, dst)
|
46
46
|
end
|
47
47
|
|
48
48
|
def optimized?(src, dst)
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'image_optim/worker'
|
4
|
+
require 'image_optim/option_helpers'
|
5
|
+
require 'image_optim/true_false_nil'
|
6
|
+
|
7
|
+
class ImageOptim
|
8
|
+
class Worker
|
9
|
+
# https://github.com/shssoichiro/oxipng
|
10
|
+
class Oxipng < Worker
|
11
|
+
LEVEL_OPTION =
|
12
|
+
option(:level, 3, 'Optimization level preset: '\
|
13
|
+
'`0` is least, '\
|
14
|
+
'`6` is best') do |v|
|
15
|
+
OptionHelpers.limit_with_range(v.to_i, 0..6)
|
16
|
+
end
|
17
|
+
|
18
|
+
INTERLACE_OPTION =
|
19
|
+
option(:interlace, false, TrueFalseNil, 'Interlace: '\
|
20
|
+
'`true` - interlace on, '\
|
21
|
+
'`false` - interlace off, '\
|
22
|
+
'`nil` - as is in original image') do |v|
|
23
|
+
TrueFalseNil.convert(v)
|
24
|
+
end
|
25
|
+
|
26
|
+
STRIP_OPTION =
|
27
|
+
option(:strip, true, 'Remove all auxiliary chunks'){ |v| !!v }
|
28
|
+
|
29
|
+
def run_order
|
30
|
+
-4
|
31
|
+
end
|
32
|
+
|
33
|
+
def optimize(src, dst, options = {})
|
34
|
+
src.copy(dst)
|
35
|
+
args = %W[
|
36
|
+
-o #{level}
|
37
|
+
--quiet
|
38
|
+
--
|
39
|
+
#{dst}
|
40
|
+
]
|
41
|
+
args.unshift "-i#{interlace ? 1 : 0}" unless interlace.nil?
|
42
|
+
if strip
|
43
|
+
args.unshift '--strip', 'all'
|
44
|
+
end
|
45
|
+
execute(:oxipng, args, options) && optimized?(src, dst)
|
46
|
+
end
|
47
|
+
|
48
|
+
def optimized?(src, dst)
|
49
|
+
interlace ? dst.size? : super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -8,18 +8,18 @@ class ImageOptim
|
|
8
8
|
class Pngcrush < Worker
|
9
9
|
CHUNKS_OPTION =
|
10
10
|
option(:chunks, :alla, Array, 'List of chunks to remove or '\
|
11
|
-
|
12
|
-
|
11
|
+
'`:alla` - all except tRNS/transparency or '\
|
12
|
+
'`:allb` - all except tRNS and gAMA/gamma') do |v|
|
13
13
|
Array(v).map(&:to_s)
|
14
14
|
end
|
15
15
|
|
16
16
|
FIX_OPTION =
|
17
17
|
option(:fix, false, 'Fix otherwise fatal conditions '\
|
18
|
-
|
18
|
+
'such as bad CRCs'){ |v| !!v }
|
19
19
|
|
20
20
|
BRUTE_OPTION =
|
21
21
|
option(:brute, false, 'Brute force try all methods, '\
|
22
|
-
|
22
|
+
'very time-consuming and generally not worthwhile'){ |v| !!v }
|
23
23
|
|
24
24
|
BLACKEN_OPTION =
|
25
25
|
option(:blacken, true, 'Blacken fully transparent pixels'){ |v| !!v }
|
@@ -28,7 +28,7 @@ class ImageOptim
|
|
28
28
|
-6
|
29
29
|
end
|
30
30
|
|
31
|
-
def optimize(src, dst)
|
31
|
+
def optimize(src, dst, options = {})
|
32
32
|
flags = %w[
|
33
33
|
-reduce
|
34
34
|
-cc
|
@@ -49,7 +49,7 @@ class ImageOptim
|
|
49
49
|
#{dst}
|
50
50
|
]
|
51
51
|
|
52
|
-
execute(:pngcrush,
|
52
|
+
execute(:pngcrush, args, options) && optimized?(src, dst)
|
53
53
|
end
|
54
54
|
end
|
55
55
|
end
|
@@ -12,11 +12,11 @@ class ImageOptim
|
|
12
12
|
|
13
13
|
STRATEGY_OPTION =
|
14
14
|
option(:strategy, 0, 'Strategy: '\
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
'`0` - xtreme, '\
|
16
|
+
'`1` - intense, '\
|
17
|
+
'`2` - longest Match, '\
|
18
|
+
'`3` - huffman Only, '\
|
19
|
+
'`4` - uncompressed') do |v|
|
20
20
|
OptionHelpers.limit_with_range(v.to_i, 0..4)
|
21
21
|
end
|
22
22
|
|
@@ -24,7 +24,7 @@ class ImageOptim
|
|
24
24
|
2
|
25
25
|
end
|
26
26
|
|
27
|
-
def optimize(src, dst)
|
27
|
+
def optimize(src, dst, options = {})
|
28
28
|
args = %W[
|
29
29
|
-k#{copy_chunks ? 1 : 0}
|
30
30
|
-s#{strategy}
|
@@ -33,7 +33,7 @@ class ImageOptim
|
|
33
33
|
#{src}
|
34
34
|
#{dst}
|
35
35
|
]
|
36
|
-
execute(:pngout,
|
36
|
+
execute(:pngout, args, options) && optimized?(src, dst)
|
37
37
|
rescue SignalException => e
|
38
38
|
raise unless Signal.list.key(e.signo) == 'SEGV'
|
39
39
|
raise unless resolve_bin!(:pngout).version <= '20150920'
|
@@ -19,9 +19,9 @@ class ImageOptim
|
|
19
19
|
QUALITY_OPTION =
|
20
20
|
option(:quality, '`100..100`, `0..100` in lossy mode',
|
21
21
|
NonNegativeIntegerRange, 'min..max - don\'t '\
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
'save below min, use less colors below max (both in range `0..100`; '\
|
23
|
+
'in yaml - `!ruby/range 0..100`), ignored in default/lossless '\
|
24
|
+
'mode') do |v, opt_def|
|
25
25
|
if allow_lossy
|
26
26
|
if v == opt_def.default
|
27
27
|
0..100
|
@@ -32,7 +32,7 @@ class ImageOptim
|
|
32
32
|
else
|
33
33
|
if v != opt_def.default
|
34
34
|
warn "#{self.class.bin_sym} #{opt_def.name} #{v} ignored " \
|
35
|
-
|
35
|
+
'in lossless mode'
|
36
36
|
end
|
37
37
|
100..100
|
38
38
|
end
|
@@ -40,9 +40,9 @@ class ImageOptim
|
|
40
40
|
|
41
41
|
SPEED_OPTION =
|
42
42
|
option(:speed, 3, 'speed/quality trade-off: '\
|
43
|
-
|
44
|
-
|
45
|
-
|
43
|
+
'`1` - slow, '\
|
44
|
+
'`3` - default, '\
|
45
|
+
'`11` - fast & rough') do |v|
|
46
46
|
OptionHelpers.limit_with_range(v.to_i, 1..11)
|
47
47
|
end
|
48
48
|
|
@@ -50,7 +50,7 @@ class ImageOptim
|
|
50
50
|
-2
|
51
51
|
end
|
52
52
|
|
53
|
-
def optimize(src, dst)
|
53
|
+
def optimize(src, dst, options = {})
|
54
54
|
args = %W[
|
55
55
|
--quality=#{quality.begin}-#{quality.end}
|
56
56
|
--speed=#{speed}
|
@@ -61,7 +61,7 @@ class ImageOptim
|
|
61
61
|
--
|
62
62
|
#{src}
|
63
63
|
]
|
64
|
-
execute(:pngquant,
|
64
|
+
execute(:pngquant, args, options) && optimized?(src, dst)
|
65
65
|
end
|
66
66
|
end
|
67
67
|
end
|
@@ -16,7 +16,7 @@ class ImageOptim
|
|
16
16
|
Array(v).map(&:to_s)
|
17
17
|
end
|
18
18
|
|
19
|
-
def optimize(src, dst)
|
19
|
+
def optimize(src, dst, options = {})
|
20
20
|
args = %W[
|
21
21
|
--input #{src}
|
22
22
|
--output #{dst}
|
@@ -27,7 +27,7 @@ class ImageOptim
|
|
27
27
|
enable_plugins.each do |plugin_name|
|
28
28
|
args.unshift "--enable=#{plugin_name}"
|
29
29
|
end
|
30
|
-
execute(:svgo,
|
30
|
+
execute(:svgo, args, options) && optimized?(src, dst)
|
31
31
|
end
|
32
32
|
end
|
33
33
|
end
|
data/lib/image_optim/worker.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
|
4
4
|
require 'image_optim/cmd'
|
5
5
|
require 'image_optim/configuration_error'
|
6
|
+
require 'image_optim/elapsed_time'
|
6
7
|
require 'image_optim/path'
|
7
8
|
require 'image_optim/worker/class_methods'
|
8
9
|
require 'shellwords'
|
@@ -41,7 +42,7 @@ class ImageOptim
|
|
41
42
|
|
42
43
|
# Optimize image at src, output at dst, must be overriden in subclass
|
43
44
|
# return true on success
|
44
|
-
def optimize(_src, _dst)
|
45
|
+
def optimize(_src, _dst, options = {})
|
45
46
|
fail NotImplementedError, "implement method optimize in #{self.class}"
|
46
47
|
end
|
47
48
|
|
@@ -104,7 +105,7 @@ class ImageOptim
|
|
104
105
|
return if unknown_options.empty?
|
105
106
|
|
106
107
|
fail ConfigurationError, "unknown options #{unknown_options.inspect} "\
|
107
|
-
|
108
|
+
"for #{self}"
|
108
109
|
end
|
109
110
|
|
110
111
|
# Forward bin resolving to image_optim
|
@@ -117,47 +118,49 @@ class ImageOptim
|
|
117
118
|
def wrap_resolver_error_message(message)
|
118
119
|
name = self.class.bin_sym
|
119
120
|
"#{name} worker: #{message}; please provide proper binary or "\
|
120
|
-
|
121
|
-
|
121
|
+
"disable this worker (--no-#{name} argument or "\
|
122
|
+
"`:#{name} => false` through options)"
|
122
123
|
end
|
123
124
|
|
124
125
|
# Run command setting priority and hiding output
|
125
|
-
def execute(bin,
|
126
|
+
def execute(bin, arguments, options)
|
126
127
|
resolve_bin!(bin)
|
127
128
|
|
128
129
|
cmd_args = [bin, *arguments].map(&:to_s)
|
129
130
|
|
130
|
-
start = Time.now
|
131
|
-
|
132
|
-
success = run_command(cmd_args)
|
133
|
-
|
134
131
|
if @image_optim.verbose
|
135
|
-
|
136
|
-
|
132
|
+
run_command_verbose(cmd_args, options)
|
133
|
+
else
|
134
|
+
run_command(cmd_args, options)
|
137
135
|
end
|
138
|
-
|
139
|
-
success
|
140
136
|
end
|
141
137
|
|
142
138
|
# Run command defining environment, setting nice level, removing output and
|
143
139
|
# reraising signal exception
|
144
|
-
def run_command(cmd_args)
|
145
|
-
args =
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
].join(' ')
|
152
|
-
else
|
153
|
-
[
|
154
|
-
{'PATH' => @image_optim.env_path},
|
155
|
-
%W[nice -n #{@image_optim.nice}],
|
156
|
-
cmd_args,
|
157
|
-
{:out => Path::NULL, :err => Path::NULL},
|
158
|
-
].flatten
|
159
|
-
end
|
140
|
+
def run_command(cmd_args, options)
|
141
|
+
args = [
|
142
|
+
{'PATH' => @image_optim.env_path},
|
143
|
+
*%W[nice -n #{@image_optim.nice}],
|
144
|
+
*cmd_args,
|
145
|
+
options.merge(out: Path::NULL, err: Path::NULL),
|
146
|
+
]
|
160
147
|
Cmd.run(*args)
|
161
148
|
end
|
149
|
+
|
150
|
+
# Wrap run_command and output status, elapsed time and command
|
151
|
+
def run_command_verbose(cmd_args, options)
|
152
|
+
start = ElapsedTime.now
|
153
|
+
|
154
|
+
begin
|
155
|
+
success = run_command(cmd_args, options)
|
156
|
+
status = success ? '✓' : '✗'
|
157
|
+
success
|
158
|
+
rescue Errors::TimeoutExceeded
|
159
|
+
status = 'timeout'
|
160
|
+
raise
|
161
|
+
ensure
|
162
|
+
$stderr << format("%s %.1fs %s\n", status, ElapsedTime.now - start, cmd_args.shelljoin)
|
163
|
+
end
|
164
|
+
end
|
162
165
|
end
|
163
166
|
end
|
data/lib/image_optim.rb
CHANGED
@@ -3,16 +3,18 @@
|
|
3
3
|
require 'image_optim/bin_resolver'
|
4
4
|
require 'image_optim/cache'
|
5
5
|
require 'image_optim/config'
|
6
|
+
require 'image_optim/errors'
|
6
7
|
require 'image_optim/handler'
|
7
8
|
require 'image_optim/image_meta'
|
8
9
|
require 'image_optim/optimized_path'
|
9
10
|
require 'image_optim/path'
|
11
|
+
require 'image_optim/timer'
|
10
12
|
require 'image_optim/worker'
|
11
13
|
require 'in_threads'
|
12
14
|
require 'shellwords'
|
13
15
|
|
14
16
|
%w[
|
15
|
-
pngcrush pngout advpng optipng pngquant
|
17
|
+
pngcrush pngout advpng optipng pngquant oxipng
|
16
18
|
jhead jpegoptim jpegrecompress jpegtran
|
17
19
|
gifsicle
|
18
20
|
svgo
|
@@ -46,6 +48,9 @@ class ImageOptim
|
|
46
48
|
# Cache worker digests
|
47
49
|
attr_reader :cache_worker_digests
|
48
50
|
|
51
|
+
# Timeout in seconds for each image
|
52
|
+
attr_reader :timeout
|
53
|
+
|
49
54
|
# Initialize workers, specify options using worker underscored name:
|
50
55
|
#
|
51
56
|
# pass false to disable worker
|
@@ -78,6 +83,7 @@ class ImageOptim
|
|
78
83
|
allow_lossy
|
79
84
|
cache_dir
|
80
85
|
cache_worker_digests
|
86
|
+
timeout
|
81
87
|
].each do |name|
|
82
88
|
instance_variable_set(:"@#{name}", config.send(name))
|
83
89
|
$stderr << "#{name}: #{send(name)}\n" if verbose
|
@@ -110,11 +116,17 @@ class ImageOptim
|
|
110
116
|
return unless (workers = workers_for_image(original))
|
111
117
|
|
112
118
|
optimized = @cache.fetch(original) do
|
119
|
+
timer = timeout && Timer.new(timeout)
|
120
|
+
|
113
121
|
Handler.for(original) do |handler|
|
114
|
-
|
115
|
-
|
116
|
-
|
122
|
+
begin
|
123
|
+
workers.each do |worker|
|
124
|
+
handler.process do |src, dst|
|
125
|
+
worker.optimize(src, dst, timeout: timer)
|
126
|
+
end
|
117
127
|
end
|
128
|
+
rescue Errors::TimeoutExceeded
|
129
|
+
handler.result
|
118
130
|
end
|
119
131
|
end
|
120
132
|
end
|
@@ -188,12 +200,6 @@ class ImageOptim
|
|
188
200
|
optimize_image_method?(method) || super
|
189
201
|
end
|
190
202
|
|
191
|
-
if RUBY_VERSION < '1.9'
|
192
|
-
def respond_to?(method, include_private = false)
|
193
|
-
optimize_image_method?(method) || super
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
203
|
# Version of image_optim gem spec loaded
|
198
204
|
def version
|
199
205
|
Gem.loaded_specs['image_optim'].version.to_s
|