image_optim 0.28.0 → 0.31.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|