mini_magick 3.8.1 → 4.9.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/mini_gmagick.rb +1 -0
- data/lib/mini_magick/configuration.rb +198 -0
- data/lib/mini_magick/image/info.rb +192 -0
- data/lib/mini_magick/image.rb +456 -341
- data/lib/mini_magick/shell.rb +81 -0
- data/lib/mini_magick/tool/animate.rb +14 -0
- data/lib/mini_magick/tool/compare.rb +14 -0
- data/lib/mini_magick/tool/composite.rb +14 -0
- data/lib/mini_magick/tool/conjure.rb +14 -0
- data/lib/mini_magick/tool/convert.rb +14 -0
- data/lib/mini_magick/tool/display.rb +14 -0
- data/lib/mini_magick/tool/identify.rb +14 -0
- data/lib/mini_magick/tool/import.rb +14 -0
- data/lib/mini_magick/tool/magick.rb +14 -0
- data/lib/mini_magick/tool/mogrify.rb +14 -0
- data/lib/mini_magick/tool/mogrify_restricted.rb +15 -0
- data/lib/mini_magick/tool/montage.rb +14 -0
- data/lib/mini_magick/tool/stream.rb +14 -0
- data/lib/mini_magick/tool.rb +297 -0
- data/lib/mini_magick/utilities.rb +23 -50
- data/lib/mini_magick/version.rb +5 -5
- data/lib/mini_magick.rb +54 -65
- metadata +49 -51
- data/lib/mini_magick/command_builder.rb +0 -94
- data/lib/mini_magick/errors.rb +0 -4
- data/spec/files/actually_a_gif.jpg +0 -0
- data/spec/files/animation.gif +0 -0
- data/spec/files/composited.jpg +0 -0
- data/spec/files/erroneous.jpg +0 -0
- data/spec/files/layers.psd +0 -0
- data/spec/files/leaves (spaced).tiff +0 -0
- data/spec/files/not_an_image.php +0 -1
- data/spec/files/png.png +0 -0
- data/spec/files/simple-minus.gif +0 -0
- data/spec/files/simple.gif +0 -0
- data/spec/files/trogdor.jpg +0 -0
- data/spec/files/trogdor_capitalized.JPG +0 -0
- data/spec/lib/mini_magick/command_builder_spec.rb +0 -153
- data/spec/lib/mini_magick/image_spec.rb +0 -499
- data/spec/lib/mini_magick_spec.rb +0 -63
- data/spec/spec_helper.rb +0 -29
@@ -0,0 +1,81 @@
|
|
1
|
+
require "timeout"
|
2
|
+
require "benchmark"
|
3
|
+
|
4
|
+
module MiniMagick
|
5
|
+
##
|
6
|
+
# Sends commands to the shell (more precisely, it sends commands directly to
|
7
|
+
# the operating system).
|
8
|
+
#
|
9
|
+
# @private
|
10
|
+
#
|
11
|
+
class Shell
|
12
|
+
|
13
|
+
def run(command, options = {})
|
14
|
+
stdout, stderr, status = execute(command, stdin: options[:stdin])
|
15
|
+
|
16
|
+
if status != 0 && options.fetch(:whiny, MiniMagick.whiny)
|
17
|
+
fail MiniMagick::Error, "`#{command.join(" ")}` failed with error:\n#{stderr}"
|
18
|
+
end
|
19
|
+
|
20
|
+
$stderr.print(stderr) unless options[:stderr] == false
|
21
|
+
|
22
|
+
[stdout, stderr, status]
|
23
|
+
end
|
24
|
+
|
25
|
+
def execute(command, options = {})
|
26
|
+
stdout, stderr, status =
|
27
|
+
log(command.join(" ")) do
|
28
|
+
send("execute_#{MiniMagick.shell_api.gsub("-", "_")}", command, options)
|
29
|
+
end
|
30
|
+
|
31
|
+
[stdout, stderr, status.exitstatus]
|
32
|
+
rescue Errno::ENOENT, IOError
|
33
|
+
["", "executable not found: \"#{command.first}\"", 127]
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def execute_open3(command, options = {})
|
39
|
+
require "open3"
|
40
|
+
|
41
|
+
# We would ideally use Open3.capture3, but it wouldn't allow us to
|
42
|
+
# terminate the command after timing out.
|
43
|
+
Open3.popen3(*command) do |in_w, out_r, err_r, thread|
|
44
|
+
[in_w, out_r, err_r].each(&:binmode)
|
45
|
+
stdout_reader = Thread.new { out_r.read }
|
46
|
+
stderr_reader = Thread.new { err_r.read }
|
47
|
+
begin
|
48
|
+
in_w.write options[:stdin].to_s
|
49
|
+
rescue Errno::EPIPE
|
50
|
+
end
|
51
|
+
in_w.close
|
52
|
+
|
53
|
+
begin
|
54
|
+
Timeout.timeout(MiniMagick.timeout) { thread.join }
|
55
|
+
rescue Timeout::Error
|
56
|
+
Process.kill("TERM", thread.pid) rescue nil
|
57
|
+
Process.waitpid(thread.pid) rescue nil
|
58
|
+
raise Timeout::Error, "MiniMagick command timed out: #{command}"
|
59
|
+
end
|
60
|
+
|
61
|
+
[stdout_reader.value, stderr_reader.value, thread.value]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def execute_posix_spawn(command, options = {})
|
66
|
+
require "posix-spawn"
|
67
|
+
child = POSIX::Spawn::Child.new(*command, input: options[:stdin].to_s, timeout: MiniMagick.timeout)
|
68
|
+
[child.out, child.err, child.status]
|
69
|
+
rescue POSIX::Spawn::TimeoutExceeded
|
70
|
+
raise Timeout::Error, "MiniMagick command timed out: #{command}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def log(command, &block)
|
74
|
+
value = nil
|
75
|
+
duration = Benchmark.realtime { value = block.call }
|
76
|
+
MiniMagick.logger.debug "[%.2fs] %s" % [duration, command]
|
77
|
+
value
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "mini_magick/tool/mogrify"
|
2
|
+
|
3
|
+
module MiniMagick
|
4
|
+
class Tool
|
5
|
+
##
|
6
|
+
# @see http://www.imagemagick.org/script/mogrify.php
|
7
|
+
#
|
8
|
+
class MogrifyRestricted < Mogrify
|
9
|
+
def format(*args)
|
10
|
+
fail NoMethodError,
|
11
|
+
"you must call #format on a MiniMagick::Image directly"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,297 @@
|
|
1
|
+
require "mini_magick/shell"
|
2
|
+
|
3
|
+
module MiniMagick
|
4
|
+
##
|
5
|
+
# Abstract class that wraps command-line tools. It shouldn't be used directly,
|
6
|
+
# but through one of its subclasses (e.g. {MiniMagick::Tool::Mogrify}). Use
|
7
|
+
# this class if you want to be closer to the metal and execute ImageMagick
|
8
|
+
# commands directly, but still with a nice Ruby interface.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# MiniMagick::Tool::Mogrify.new do |builder|
|
12
|
+
# builder.resize "500x500"
|
13
|
+
# builder << "path/to/image.jpg"
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
class Tool
|
17
|
+
|
18
|
+
CREATION_OPERATORS = %w[xc canvas logo rose gradient radial-gradient plasma
|
19
|
+
pattern text pango]
|
20
|
+
|
21
|
+
##
|
22
|
+
# Aside from classic instantiation, it also accepts a block, and then
|
23
|
+
# executes the command in the end.
|
24
|
+
#
|
25
|
+
# @example
|
26
|
+
# version = MiniMagick::Tool::Identify.new { |b| b.version }
|
27
|
+
# puts version
|
28
|
+
#
|
29
|
+
# @return [MiniMagick::Tool, String] If no block is given, returns an
|
30
|
+
# instance of the tool, if block is given, returns the output of the
|
31
|
+
# command.
|
32
|
+
#
|
33
|
+
def self.new(*args)
|
34
|
+
instance = super(*args)
|
35
|
+
|
36
|
+
if block_given?
|
37
|
+
yield instance
|
38
|
+
instance.call
|
39
|
+
else
|
40
|
+
instance
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# @private
|
45
|
+
attr_reader :name, :args
|
46
|
+
|
47
|
+
# @param whiny [Boolean] Whether to raise errors on exit codes different
|
48
|
+
# than 0.
|
49
|
+
# @example
|
50
|
+
# MiniMagick::Tool::Identify.new(whiny: false) do |identify|
|
51
|
+
# identify.help # returns exit status 1, which would otherwise throw an error
|
52
|
+
# end
|
53
|
+
def initialize(name, options = {})
|
54
|
+
warn "MiniMagick::Tool.new(false) is deprecated and will be removed in MiniMagick 5, use MiniMagick::Tool.new(whiny: false) instead." if !options.is_a?(Hash)
|
55
|
+
|
56
|
+
@name = name
|
57
|
+
@args = []
|
58
|
+
@whiny = options.is_a?(Hash) ? options.fetch(:whiny, MiniMagick.whiny) : options
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Executes the command that has been built up.
|
63
|
+
#
|
64
|
+
# @example
|
65
|
+
# mogrify = MiniMagick::Tool::Mogrify.new
|
66
|
+
# mogrify.resize("500x500")
|
67
|
+
# mogrify << "path/to/image.jpg"
|
68
|
+
# mogrify.call # executes `mogrify -resize 500x500 path/to/image.jpg`
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# mogrify = MiniMagick::Tool::Mogrify.new
|
72
|
+
# # build the command
|
73
|
+
# mogrify.call do |stdout, stderr, status|
|
74
|
+
# # ...
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# @yield [Array] Optionally yields stdout, stderr, and exit status
|
78
|
+
#
|
79
|
+
# @return [String] Returns the output of the command
|
80
|
+
#
|
81
|
+
def call(*args)
|
82
|
+
options = args[-1].is_a?(Hash) ? args.pop : {}
|
83
|
+
warn "Passing whiny to MiniMagick::Tool#call is deprecated and will be removed in MiniMagick 5, use MiniMagick::Tool.new(whiny: false) instead." if args.any?
|
84
|
+
whiny = args.fetch(0, @whiny)
|
85
|
+
|
86
|
+
options[:whiny] = whiny
|
87
|
+
options[:stderr] = false if block_given?
|
88
|
+
|
89
|
+
shell = MiniMagick::Shell.new
|
90
|
+
stdout, stderr, status = shell.run(command, options)
|
91
|
+
yield stdout, stderr, status if block_given?
|
92
|
+
|
93
|
+
stdout.chomp("\n")
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# The currently built-up command.
|
98
|
+
#
|
99
|
+
# @return [Array<String>]
|
100
|
+
#
|
101
|
+
# @example
|
102
|
+
# mogrify = MiniMagick::Tool::Mogrify.new
|
103
|
+
# mogrify.resize "500x500"
|
104
|
+
# mogrify.contrast
|
105
|
+
# mogrify.command #=> ["mogrify", "-resize", "500x500", "-contrast"]
|
106
|
+
#
|
107
|
+
def command
|
108
|
+
[*executable, *args]
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# The executable used for this tool. Respects
|
113
|
+
# {MiniMagick::Configuration#cli}, {MiniMagick::Configuration#cli_path},
|
114
|
+
# and {MiniMagick::Configuration#cli_prefix}.
|
115
|
+
#
|
116
|
+
# @return [Array<String>]
|
117
|
+
#
|
118
|
+
# @example
|
119
|
+
# MiniMagick.configure { |config| config.cli = :graphicsmagick }
|
120
|
+
# identify = MiniMagick::Tool::Identify.new
|
121
|
+
# identify.executable #=> ["gm", "identify"]
|
122
|
+
#
|
123
|
+
# @example
|
124
|
+
# MiniMagick.configure do |config|
|
125
|
+
# config.cli = :graphicsmagick
|
126
|
+
# config.cli_prefix = ['firejail', '--force']
|
127
|
+
# end
|
128
|
+
# identify = MiniMagick::Tool::Identify.new
|
129
|
+
# identify.executable #=> ["firejail", "--force", "gm", "identify"]
|
130
|
+
#
|
131
|
+
def executable
|
132
|
+
exe = [name]
|
133
|
+
exe.unshift "magick" if MiniMagick.imagemagick7? && name != "magick"
|
134
|
+
exe.unshift "gm" if MiniMagick.graphicsmagick?
|
135
|
+
exe.unshift File.join(MiniMagick.cli_path, exe.shift) if MiniMagick.cli_path
|
136
|
+
Array(MiniMagick.cli_prefix).reverse_each { |p| exe.unshift p } if MiniMagick.cli_prefix
|
137
|
+
exe
|
138
|
+
end
|
139
|
+
|
140
|
+
##
|
141
|
+
# Appends raw options, useful for appending image paths.
|
142
|
+
#
|
143
|
+
# @return [self]
|
144
|
+
#
|
145
|
+
def <<(arg)
|
146
|
+
args << arg.to_s
|
147
|
+
self
|
148
|
+
end
|
149
|
+
|
150
|
+
##
|
151
|
+
# Merges a list of raw options.
|
152
|
+
#
|
153
|
+
# @return [self]
|
154
|
+
#
|
155
|
+
def merge!(new_args)
|
156
|
+
new_args.each { |arg| self << arg }
|
157
|
+
self
|
158
|
+
end
|
159
|
+
|
160
|
+
##
|
161
|
+
# Changes the last operator to its "plus" form.
|
162
|
+
#
|
163
|
+
# @example
|
164
|
+
# MiniMagick::Tool::Mogrify.new do |mogrify|
|
165
|
+
# mogrify.antialias.+
|
166
|
+
# mogrify.distort.+("Perspective", "0,0,4,5 89,0,45,46")
|
167
|
+
# end
|
168
|
+
# # executes `mogrify +antialias +distort Perspective '0,0,4,5 89,0,45,46'`
|
169
|
+
#
|
170
|
+
# @return [self]
|
171
|
+
#
|
172
|
+
def +(*values)
|
173
|
+
args[-1] = args[-1].sub(/^-/, '+')
|
174
|
+
self.merge!(values)
|
175
|
+
self
|
176
|
+
end
|
177
|
+
|
178
|
+
##
|
179
|
+
# Create an ImageMagick stack in the command (surround.
|
180
|
+
#
|
181
|
+
# @example
|
182
|
+
# MiniMagick::Tool::Convert.new do |convert|
|
183
|
+
# convert << "wand.gif"
|
184
|
+
# convert.stack do |stack|
|
185
|
+
# stack << "wand.gif"
|
186
|
+
# stack.rotate(30)
|
187
|
+
# end
|
188
|
+
# convert.append.+
|
189
|
+
# convert << "images.gif"
|
190
|
+
# end
|
191
|
+
# # executes `convert wand.gif \( wizard.gif -rotate 30 \) +append images.gif`
|
192
|
+
#
|
193
|
+
def stack
|
194
|
+
self << "("
|
195
|
+
yield self
|
196
|
+
self << ")"
|
197
|
+
end
|
198
|
+
|
199
|
+
##
|
200
|
+
# Adds ImageMagick's pseudo-filename `-` for standard input.
|
201
|
+
#
|
202
|
+
# @example
|
203
|
+
# identify = MiniMagick::Tool::Identify.new
|
204
|
+
# identify.stdin
|
205
|
+
# identify.call(stdin: image_content)
|
206
|
+
# # executes `identify -` with the given standard input
|
207
|
+
#
|
208
|
+
def stdin
|
209
|
+
self << "-"
|
210
|
+
end
|
211
|
+
|
212
|
+
##
|
213
|
+
# Adds ImageMagick's pseudo-filename `-` for standard output.
|
214
|
+
#
|
215
|
+
# @example
|
216
|
+
# content = MiniMagick::Tool::Convert.new do |convert|
|
217
|
+
# convert << "input.jpg"
|
218
|
+
# convert.auto_orient
|
219
|
+
# convert.stdout
|
220
|
+
# end
|
221
|
+
# # executes `convert input.jpg -auto-orient -` which returns file contents
|
222
|
+
#
|
223
|
+
def stdout
|
224
|
+
self << "-"
|
225
|
+
end
|
226
|
+
|
227
|
+
##
|
228
|
+
# Define creator operator methods
|
229
|
+
#
|
230
|
+
# mogrify = MiniMagick::Tool.new("mogrify")
|
231
|
+
# mogrify.canvas("khaki")
|
232
|
+
# mogrify.command.join(" ") #=> "mogrify canvas:khaki"
|
233
|
+
#
|
234
|
+
CREATION_OPERATORS.each do |operator|
|
235
|
+
define_method(operator.gsub('-', '_')) do |value = nil|
|
236
|
+
self << "#{operator}:#{value}"
|
237
|
+
self
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
##
|
242
|
+
# This option is a valid ImageMagick option, but it's also a Ruby method,
|
243
|
+
# so we need to override it so that it correctly acts as an option method.
|
244
|
+
#
|
245
|
+
def clone(*args)
|
246
|
+
self << '-clone'
|
247
|
+
self.merge!(args)
|
248
|
+
self
|
249
|
+
end
|
250
|
+
|
251
|
+
##
|
252
|
+
# Any undefined method will be transformed into a CLI option
|
253
|
+
#
|
254
|
+
# mogrify = MiniMagick::Tool.new("mogrify")
|
255
|
+
# mogrify.adaptive_blur("...")
|
256
|
+
# mogrify.foo_bar
|
257
|
+
# mogrify.command.join(" ") "mogrify -adaptive-blur ... -foo-bar"
|
258
|
+
#
|
259
|
+
def method_missing(name, *args)
|
260
|
+
option = "-#{name.to_s.tr('_', '-')}"
|
261
|
+
self << option
|
262
|
+
self.merge!(args)
|
263
|
+
self
|
264
|
+
end
|
265
|
+
|
266
|
+
def self.option_methods
|
267
|
+
@option_methods ||= (
|
268
|
+
tool = new(whiny: false)
|
269
|
+
tool << "-help"
|
270
|
+
help_page = tool.call(stderr: false)
|
271
|
+
|
272
|
+
cli_options = help_page.scan(/^\s+-[a-z\-]+/).map(&:strip)
|
273
|
+
if tool.name == "mogrify" && MiniMagick.graphicsmagick?
|
274
|
+
# These options were undocumented before 2015-06-14 (see gm bug 302)
|
275
|
+
cli_options |= %w[-box -convolve -gravity -linewidth -mattecolor -render -shave]
|
276
|
+
end
|
277
|
+
|
278
|
+
cli_options.map { |o| o[1..-1].tr('-','_') }
|
279
|
+
)
|
280
|
+
end
|
281
|
+
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
require "mini_magick/tool/animate"
|
286
|
+
require "mini_magick/tool/compare"
|
287
|
+
require "mini_magick/tool/composite"
|
288
|
+
require "mini_magick/tool/conjure"
|
289
|
+
require "mini_magick/tool/convert"
|
290
|
+
require "mini_magick/tool/display"
|
291
|
+
require "mini_magick/tool/identify"
|
292
|
+
require "mini_magick/tool/import"
|
293
|
+
require "mini_magick/tool/magick"
|
294
|
+
require "mini_magick/tool/mogrify"
|
295
|
+
require "mini_magick/tool/mogrify_restricted"
|
296
|
+
require "mini_magick/tool/montage"
|
297
|
+
require "mini_magick/tool/stream"
|
@@ -1,62 +1,35 @@
|
|
1
|
-
require
|
2
|
-
require 'shellwords'
|
3
|
-
require 'pathname'
|
1
|
+
require "tempfile"
|
4
2
|
|
5
3
|
module MiniMagick
|
4
|
+
# @private
|
6
5
|
module Utilities
|
7
|
-
class << self
|
8
|
-
# Cross-platform way of finding an executable in the $PATH.
|
9
|
-
#
|
10
|
-
# which('ruby') #=> /usr/bin/ruby
|
11
|
-
def which(cmd)
|
12
|
-
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
13
|
-
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
14
|
-
exts.each do |ext|
|
15
|
-
exe = File.join(path, "#{cmd}#{ext}")
|
16
|
-
return exe if File.executable? exe
|
17
|
-
end
|
18
|
-
end
|
19
|
-
nil
|
20
|
-
end
|
21
6
|
|
22
|
-
|
23
|
-
def windows?
|
24
|
-
RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
|
25
|
-
end
|
7
|
+
module_function
|
26
8
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
def windows_escape(value)
|
40
|
-
# For Windows, ^ is the escape char, equivalent to \ in Unix.
|
41
|
-
escaped = value.gsub(/\^/, '^^').gsub(/>/, '^>')
|
42
|
-
if escaped !~ /^".+"$/ && escaped.include?("'")
|
43
|
-
escaped.inspect
|
44
|
-
else
|
45
|
-
escaped
|
9
|
+
##
|
10
|
+
# Cross-platform way of finding an executable in the $PATH.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# MiniMagick::Utilities.which('ruby') #=> "/usr/bin/ruby"
|
14
|
+
#
|
15
|
+
def which(cmd)
|
16
|
+
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
17
|
+
ENV.fetch('PATH').split(File::PATH_SEPARATOR).each do |path|
|
18
|
+
exts.each do |ext|
|
19
|
+
exe = File.join(path, "#{cmd}#{ext}")
|
20
|
+
return exe if File.executable? exe
|
46
21
|
end
|
47
22
|
end
|
23
|
+
nil
|
24
|
+
end
|
48
25
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
pathname = Pathname.new(path).to_s
|
55
|
-
path.include?(' ') ? pathname.inspect : pathname
|
56
|
-
else
|
57
|
-
path
|
58
|
-
end
|
26
|
+
def tempfile(extension)
|
27
|
+
Tempfile.new(["mini_magick", extension]).tap do |tempfile|
|
28
|
+
tempfile.binmode
|
29
|
+
yield tempfile if block_given?
|
30
|
+
tempfile.close
|
59
31
|
end
|
60
32
|
end
|
33
|
+
|
61
34
|
end
|
62
35
|
end
|
data/lib/mini_magick/version.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
module MiniMagick
|
2
2
|
##
|
3
|
-
#
|
4
|
-
#
|
3
|
+
# @return [Gem::Version]
|
4
|
+
#
|
5
5
|
def self.version
|
6
6
|
Gem::Version.new VERSION::STRING
|
7
7
|
end
|
8
8
|
|
9
9
|
module VERSION
|
10
|
-
MAJOR =
|
11
|
-
MINOR =
|
12
|
-
TINY =
|
10
|
+
MAJOR = 4
|
11
|
+
MINOR = 9
|
12
|
+
TINY = 4
|
13
13
|
PRE = nil
|
14
14
|
|
15
15
|
STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
|