mini_magick 4.12.0 → 5.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +540 -0
- data/lib/mini_magick/configuration.rb +8 -135
- data/lib/mini_magick/image/info.rb +7 -58
- data/lib/mini_magick/image.rb +34 -70
- data/lib/mini_magick/shell.rb +15 -45
- data/lib/mini_magick/tool.rb +37 -70
- data/lib/mini_magick/utilities.rb +4 -6
- data/lib/mini_magick/version.rb +3 -3
- data/lib/mini_magick.rb +17 -42
- metadata +12 -66
- data/lib/mini_gmagick.rb +0 -3
- data/lib/mini_magick/tool/animate.rb +0 -14
- data/lib/mini_magick/tool/compare.rb +0 -14
- data/lib/mini_magick/tool/composite.rb +0 -14
- data/lib/mini_magick/tool/conjure.rb +0 -14
- data/lib/mini_magick/tool/convert.rb +0 -14
- data/lib/mini_magick/tool/display.rb +0 -14
- data/lib/mini_magick/tool/identify.rb +0 -14
- data/lib/mini_magick/tool/import.rb +0 -14
- data/lib/mini_magick/tool/magick.rb +0 -14
- data/lib/mini_magick/tool/mogrify.rb +0 -14
- data/lib/mini_magick/tool/mogrify_restricted.rb +0 -15
- data/lib/mini_magick/tool/montage.rb +0 -14
- data/lib/mini_magick/tool/stream.rb +0 -14
@@ -4,14 +4,6 @@ require 'logger'
|
|
4
4
|
module MiniMagick
|
5
5
|
module Configuration
|
6
6
|
|
7
|
-
##
|
8
|
-
# If you don't have the CLI tools in your PATH, you can set the path to the
|
9
|
-
# executables.
|
10
|
-
#
|
11
|
-
attr_writer :cli_path
|
12
|
-
# @private (for backwards compatibility)
|
13
|
-
attr_accessor :processor_path
|
14
|
-
|
15
7
|
##
|
16
8
|
# Adds a prefix to the CLI command.
|
17
9
|
# For example, you could use `firejail` to run all commands in a sandbox.
|
@@ -31,16 +23,8 @@ module MiniMagick
|
|
31
23
|
#
|
32
24
|
attr_accessor :timeout
|
33
25
|
##
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
# @return [Boolean]
|
38
|
-
#
|
39
|
-
attr_reader :debug
|
40
|
-
##
|
41
|
-
# Logger for {#debug}, default is `MiniMagick::Logger.new(STDOUT)`, but
|
42
|
-
# you can override it, for example if you want the logs to be written to
|
43
|
-
# a file.
|
26
|
+
# Logger for commands, default is `Logger.new($stdout)`, but you can
|
27
|
+
# override it, for example if you want the logs to be written to a file.
|
44
28
|
#
|
45
29
|
# @return [Logger]
|
46
30
|
#
|
@@ -53,146 +37,35 @@ module MiniMagick
|
|
53
37
|
#
|
54
38
|
attr_accessor :tmpdir
|
55
39
|
|
56
|
-
##
|
57
|
-
# If set to `true`, it will `identify` every newly created image, and raise
|
58
|
-
# `MiniMagick::Invalid` if the image is not valid. Useful for validating
|
59
|
-
# user input, although it adds a bit of overhead. Defaults to `true`.
|
60
|
-
#
|
61
|
-
# @return [Boolean]
|
62
|
-
#
|
63
|
-
attr_accessor :validate_on_create
|
64
|
-
##
|
65
|
-
# If set to `true`, it will `identify` every image that gets written (with
|
66
|
-
# {MiniMagick::Image#write}), and raise `MiniMagick::Invalid` if the image
|
67
|
-
# is not valid. Useful for validating that processing was sucessful,
|
68
|
-
# although it adds a bit of overhead. Defaults to `true`.
|
69
|
-
#
|
70
|
-
# @return [Boolean]
|
71
|
-
#
|
72
|
-
attr_accessor :validate_on_write
|
73
|
-
|
74
40
|
##
|
75
41
|
# If set to `false`, it will not raise errors when ImageMagick returns
|
76
42
|
# status code different than 0. Defaults to `true`.
|
77
43
|
#
|
78
44
|
# @return [Boolean]
|
79
45
|
#
|
80
|
-
attr_accessor :
|
46
|
+
attr_accessor :errors
|
81
47
|
|
82
48
|
##
|
83
|
-
#
|
84
|
-
#
|
85
|
-
|
86
|
-
#
|
87
|
-
# @return [String]
|
88
|
-
#
|
89
|
-
attr_accessor :shell_api
|
49
|
+
# If set to `false`, it will not forward warnings from ImageMagick to
|
50
|
+
# standard error.
|
51
|
+
attr_accessor :warnings
|
90
52
|
|
91
53
|
def self.extended(base)
|
92
54
|
base.tmpdir = Dir.tmpdir
|
93
|
-
base.
|
94
|
-
base.validate_on_write = true
|
95
|
-
base.whiny = true
|
96
|
-
base.shell_api = "open3"
|
55
|
+
base.errors = true
|
97
56
|
base.logger = Logger.new($stdout).tap { |l| l.level = Logger::INFO }
|
57
|
+
base.warnings = true
|
98
58
|
end
|
99
59
|
|
100
60
|
##
|
101
61
|
# @yield [self]
|
102
62
|
# @example
|
103
63
|
# MiniMagick.configure do |config|
|
104
|
-
# config.cli = :graphicsmagick
|
105
64
|
# config.timeout = 5
|
106
65
|
# end
|
107
66
|
#
|
108
67
|
def configure
|
109
68
|
yield self
|
110
69
|
end
|
111
|
-
|
112
|
-
CLI_DETECTION = {
|
113
|
-
imagemagick7: "magick",
|
114
|
-
imagemagick: "mogrify",
|
115
|
-
graphicsmagick: "gm",
|
116
|
-
}
|
117
|
-
|
118
|
-
# @private (for backwards compatibility)
|
119
|
-
def processor
|
120
|
-
@processor ||= CLI_DETECTION.values.detect do |processor|
|
121
|
-
MiniMagick::Utilities.which(processor)
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
# @private (for backwards compatibility)
|
126
|
-
def processor=(processor)
|
127
|
-
@processor = processor.to_s
|
128
|
-
|
129
|
-
unless CLI_DETECTION.value?(@processor)
|
130
|
-
raise ArgumentError,
|
131
|
-
"processor has to be set to either \"magick\", \"mogrify\" or \"gm\"" \
|
132
|
-
", was set to #{@processor.inspect}"
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
##
|
137
|
-
# Get [ImageMagick](http://www.imagemagick.org) or
|
138
|
-
# [GraphicsMagick](http://www.graphicsmagick.org).
|
139
|
-
#
|
140
|
-
# @return [Symbol] `:imagemagick` or `:graphicsmagick`
|
141
|
-
#
|
142
|
-
def cli
|
143
|
-
if instance_variable_defined?("@cli")
|
144
|
-
instance_variable_get("@cli")
|
145
|
-
else
|
146
|
-
cli = CLI_DETECTION.key(processor) or
|
147
|
-
fail MiniMagick::Error, "You must have ImageMagick or GraphicsMagick installed"
|
148
|
-
|
149
|
-
instance_variable_set("@cli", cli)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
##
|
154
|
-
# Set whether you want to use [ImageMagick](http://www.imagemagick.org) or
|
155
|
-
# [GraphicsMagick](http://www.graphicsmagick.org).
|
156
|
-
#
|
157
|
-
def cli=(value)
|
158
|
-
@cli = value
|
159
|
-
|
160
|
-
if not CLI_DETECTION.key?(@cli)
|
161
|
-
raise ArgumentError,
|
162
|
-
"CLI has to be set to either :imagemagick, :imagemagick7 or :graphicsmagick" \
|
163
|
-
", was set to #{@cli.inspect}"
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
##
|
168
|
-
# If you set the path of CLI tools, you can get the path of the
|
169
|
-
# executables.
|
170
|
-
#
|
171
|
-
# @return [String]
|
172
|
-
#
|
173
|
-
def cli_path
|
174
|
-
if instance_variable_defined?("@cli_path")
|
175
|
-
instance_variable_get("@cli_path")
|
176
|
-
else
|
177
|
-
processor_path = instance_variable_get("@processor_path") if instance_variable_defined?("@processor_path")
|
178
|
-
|
179
|
-
instance_variable_set("@cli_path", processor_path)
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
183
|
-
##
|
184
|
-
# When set to `true`, it outputs each command to STDOUT in their shell
|
185
|
-
# version.
|
186
|
-
#
|
187
|
-
def debug=(value)
|
188
|
-
warn "MiniMagick.debug is deprecated and will be removed in MiniMagick 5. Use `MiniMagick.logger.level = Logger::DEBUG` instead."
|
189
|
-
logger.level = value ? Logger::DEBUG : Logger::INFO
|
190
|
-
end
|
191
|
-
|
192
|
-
# Backwards compatibility
|
193
|
-
def reload_tools
|
194
|
-
warn "MiniMagick.reload_tools is deprecated because it is no longer necessary"
|
195
|
-
end
|
196
|
-
|
197
70
|
end
|
198
71
|
end
|
@@ -17,8 +17,6 @@ module MiniMagick
|
|
17
17
|
cheap_info(value)
|
18
18
|
when "colorspace"
|
19
19
|
colorspace
|
20
|
-
when "mime_type"
|
21
|
-
mime_type
|
22
20
|
when "resolution"
|
23
21
|
resolution(*args)
|
24
22
|
when "signature"
|
@@ -27,8 +25,6 @@ module MiniMagick
|
|
27
25
|
raw_exif(value)
|
28
26
|
when "exif"
|
29
27
|
exif
|
30
|
-
when "details"
|
31
|
-
details
|
32
28
|
when "data"
|
33
29
|
data
|
34
30
|
else
|
@@ -76,10 +72,6 @@ module MiniMagick
|
|
76
72
|
@info["colorspace"] ||= self["%r"]
|
77
73
|
end
|
78
74
|
|
79
|
-
def mime_type
|
80
|
-
"image/#{self["format"].downcase}"
|
81
|
-
end
|
82
|
-
|
83
75
|
def resolution(unit = nil)
|
84
76
|
output = identify do |b|
|
85
77
|
b.units unit if unit
|
@@ -100,20 +92,12 @@ module MiniMagick
|
|
100
92
|
output.each_line do |line|
|
101
93
|
line = line.chomp("\n")
|
102
94
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
key, value = match.post_match.split("=", 2)
|
107
|
-
value = decode_comma_separated_ascii_characters(value) if ASCII_ENCODED_EXIF_KEYS.include?(key)
|
108
|
-
hash[key] = value
|
109
|
-
else
|
110
|
-
hash[hash.keys.last] << "\n#{line}"
|
111
|
-
end
|
112
|
-
when :graphicsmagick
|
113
|
-
next if line == "unknown"
|
114
|
-
key, value = line.split("=", 2)
|
115
|
-
value.gsub!("\\012", "\n") # convert "\012" characters to newlines
|
95
|
+
if match = line.match(/^exif:/)
|
96
|
+
key, value = match.post_match.split("=", 2)
|
97
|
+
value = decode_comma_separated_ascii_characters(value) if ASCII_ENCODED_EXIF_KEYS.include?(key)
|
116
98
|
hash[key] = value
|
99
|
+
else
|
100
|
+
hash[hash.keys.last] << "\n#{line}"
|
117
101
|
end
|
118
102
|
end
|
119
103
|
|
@@ -129,44 +113,9 @@ module MiniMagick
|
|
129
113
|
@info["signature"] ||= self["%#"]
|
130
114
|
end
|
131
115
|
|
132
|
-
def details
|
133
|
-
warn "[MiniMagick] MiniMagick::Image#details has been deprecated, as it was causing too many parsing errors. You should use MiniMagick::Image#data instead, which differs in a way that the keys are in camelcase." if MiniMagick.imagemagick? || MiniMagick.imagemagick7?
|
134
|
-
|
135
|
-
@info["details"] ||= (
|
136
|
-
details_string = identify(&:verbose)
|
137
|
-
key_stack = []
|
138
|
-
details_string.lines.to_a[1..-1].each_with_object({}) do |line, details_hash|
|
139
|
-
next if !line.valid_encoding? || line.strip.length.zero?
|
140
|
-
|
141
|
-
level = line[/^\s*/].length / 2 - 1
|
142
|
-
if level >= 0
|
143
|
-
key_stack.pop until key_stack.size <= level
|
144
|
-
else
|
145
|
-
# Some metadata, such as SVG clipping paths, will be saved without
|
146
|
-
# indentation, resulting in a level of -1
|
147
|
-
last_key = details_hash.keys.last
|
148
|
-
details_hash[last_key] = '' if details_hash[last_key].empty?
|
149
|
-
details_hash[last_key] << line
|
150
|
-
next
|
151
|
-
end
|
152
|
-
|
153
|
-
key, _, value = line.partition(/:[\s]/).map(&:strip)
|
154
|
-
hash = key_stack.inject(details_hash) { |_hash, _key| _hash.fetch(_key) }
|
155
|
-
if value.empty?
|
156
|
-
hash[key] = {}
|
157
|
-
key_stack.push key
|
158
|
-
else
|
159
|
-
hash[key] = value
|
160
|
-
end
|
161
|
-
end
|
162
|
-
)
|
163
|
-
end
|
164
|
-
|
165
116
|
def data
|
166
|
-
raise Error, "MiniMagick::Image#data isn't supported on GraphicsMagick. Use MiniMagick::Image#details instead." if MiniMagick.graphicsmagick?
|
167
|
-
|
168
117
|
@info["data"] ||= (
|
169
|
-
json = MiniMagick
|
118
|
+
json = MiniMagick.convert do |convert|
|
170
119
|
convert << path
|
171
120
|
convert << "json:"
|
172
121
|
end
|
@@ -178,7 +127,7 @@ module MiniMagick
|
|
178
127
|
end
|
179
128
|
|
180
129
|
def identify
|
181
|
-
MiniMagick
|
130
|
+
MiniMagick.identify do |builder|
|
182
131
|
yield builder if block_given?
|
183
132
|
builder << path
|
184
133
|
end
|
data/lib/mini_magick/image.rb
CHANGED
@@ -51,11 +51,11 @@ module MiniMagick
|
|
51
51
|
#
|
52
52
|
def self.import_pixels(blob, columns, rows, depth, map, format = 'png')
|
53
53
|
# Create an image object with the raw pixel data string:
|
54
|
-
|
54
|
+
read(blob, ".dat").tap do |image|
|
55
55
|
output_path = image.path.sub(/\.\w+$/, ".#{format}")
|
56
56
|
# Use ImageMagick to convert the raw data file to an image file of the
|
57
57
|
# desired format:
|
58
|
-
MiniMagick
|
58
|
+
MiniMagick.convert do |convert|
|
59
59
|
convert.size "#{columns}x#{rows}"
|
60
60
|
convert.depth depth
|
61
61
|
convert << "#{map}:#{image.path}"
|
@@ -79,33 +79,15 @@ module MiniMagick
|
|
79
79
|
# @param options [Hash] Specify options for the open method
|
80
80
|
# @return [MiniMagick::Image] The loaded image
|
81
81
|
#
|
82
|
-
def self.open(path_or_url, ext = nil, options
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
if path_or_url.respond_to?(:open)
|
88
|
-
path_or_url
|
89
|
-
elsif path_or_url.respond_to?(:to_str) &&
|
90
|
-
%r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ path_or_url &&
|
91
|
-
(uri = URI.parse(path_or_url)).respond_to?(:open)
|
92
|
-
uri
|
93
|
-
else
|
94
|
-
options = { binmode: true }.merge(options)
|
95
|
-
Pathname(path_or_url)
|
96
|
-
end
|
97
|
-
|
98
|
-
if openable.is_a?(URI::Generic)
|
99
|
-
ext ||= File.extname(openable.path)
|
100
|
-
else
|
101
|
-
ext ||= File.extname(openable.to_s)
|
102
|
-
end
|
103
|
-
ext.sub!(/:.*/, '') # hack for filenames or URLs that include a colon
|
104
|
-
|
105
|
-
if openable.is_a?(URI::Generic)
|
106
|
-
openable.open(options) { |file| read(file, ext) }
|
82
|
+
def self.open(path_or_url, ext = nil, **options)
|
83
|
+
if path_or_url.to_s =~ %r{\A(https?|ftp)://}
|
84
|
+
uri = URI(path_or_url)
|
85
|
+
ext ||= File.extname(uri.path).sub(/:.*/, '') # handle URL including a colon
|
86
|
+
uri.open(options) { |file| read(file, ext) }
|
107
87
|
else
|
108
|
-
|
88
|
+
pathname = Pathname(path_or_url)
|
89
|
+
ext ||= File.extname(pathname.to_s)
|
90
|
+
pathname.open(binmode: true, **options) { |file| read(file, ext) }
|
109
91
|
end
|
110
92
|
end
|
111
93
|
|
@@ -118,18 +100,14 @@ module MiniMagick
|
|
118
100
|
# we have a good tempfile.
|
119
101
|
#
|
120
102
|
# @param ext [String] Specify the extension you want to read it as
|
121
|
-
# @param validate [Boolean] If false, skips validation of the created
|
122
|
-
# image. Defaults to true.
|
123
103
|
# @yield [Tempfile] You can #write bits to this object to create the new
|
124
104
|
# Image
|
125
105
|
# @return [MiniMagick::Image] The created image
|
126
106
|
#
|
127
|
-
def self.create(ext = nil,
|
107
|
+
def self.create(ext = nil, &block)
|
128
108
|
tempfile = MiniMagick::Utilities.tempfile(ext.to_s.downcase, &block)
|
129
109
|
|
130
|
-
new(tempfile.path, tempfile)
|
131
|
-
image.validate! if validate
|
132
|
-
end
|
110
|
+
new(tempfile.path, tempfile)
|
133
111
|
end
|
134
112
|
|
135
113
|
##
|
@@ -164,7 +142,7 @@ module MiniMagick
|
|
164
142
|
# which creates a temporary file for you and protects your original.
|
165
143
|
#
|
166
144
|
# @param input_path [String, Pathname] The location of an image file
|
167
|
-
# @yield [MiniMagick::Tool
|
145
|
+
# @yield [MiniMagick::Tool] If block is given, {#combine_options}
|
168
146
|
# is called.
|
169
147
|
#
|
170
148
|
def initialize(input_path, tempfile = nil, &block)
|
@@ -228,10 +206,6 @@ module MiniMagick
|
|
228
206
|
#
|
229
207
|
attribute :type, "format"
|
230
208
|
##
|
231
|
-
# @return [String]
|
232
|
-
#
|
233
|
-
attribute :mime_type
|
234
|
-
##
|
235
209
|
# @return [Integer]
|
236
210
|
#
|
237
211
|
attribute :width
|
@@ -274,7 +248,7 @@ module MiniMagick
|
|
274
248
|
#
|
275
249
|
attribute :resolution
|
276
250
|
##
|
277
|
-
# Returns the message digest of this image as a SHA-256,
|
251
|
+
# Returns the message digest of this image as a SHA-256, hexadecimal
|
278
252
|
# encoded string. This signature uniquely identifies the image and is
|
279
253
|
# convenient for determining if an image has been modified or whether two
|
280
254
|
# images are identical.
|
@@ -286,17 +260,10 @@ module MiniMagick
|
|
286
260
|
#
|
287
261
|
attribute :signature
|
288
262
|
##
|
289
|
-
# Returns the
|
290
|
-
# ImageMagick.
|
263
|
+
# Returns the result of converting the image to JSON format.
|
291
264
|
#
|
292
265
|
# @return [Hash]
|
293
266
|
attribute :data
|
294
|
-
##
|
295
|
-
# Returns the information from `identify -verbose` in a Hash format, for
|
296
|
-
# GraphicsMagick.
|
297
|
-
#
|
298
|
-
# @return [Hash]
|
299
|
-
attribute :details
|
300
267
|
|
301
268
|
##
|
302
269
|
# Use this method if you want to access raw Identify's format API.
|
@@ -367,7 +334,7 @@ module MiniMagick
|
|
367
334
|
# @return [Array] Matrix of each color of each pixel
|
368
335
|
def get_pixels(map="RGB")
|
369
336
|
raise ArgumentError, "Invalid map value" unless ["RGB", "RGBA"].include?(map)
|
370
|
-
convert = MiniMagick
|
337
|
+
convert = MiniMagick.convert
|
371
338
|
convert << path
|
372
339
|
convert.depth(8)
|
373
340
|
convert << "#{map}:-"
|
@@ -397,10 +364,10 @@ module MiniMagick
|
|
397
364
|
# @example
|
398
365
|
# # It is given in readme.md file
|
399
366
|
##
|
400
|
-
def self.get_image_from_pixels(pixels, dimension, map, depth,
|
367
|
+
def self.get_image_from_pixels(pixels, dimension, map, depth, format)
|
401
368
|
pixels = pixels.flatten
|
402
369
|
blob = pixels.pack('C*')
|
403
|
-
import_pixels(blob, *dimension, depth, map,
|
370
|
+
import_pixels(blob, *dimension, depth, map, format)
|
404
371
|
end
|
405
372
|
|
406
373
|
##
|
@@ -426,7 +393,7 @@ module MiniMagick
|
|
426
393
|
# will convert all pages.
|
427
394
|
# @param read_opts [Hash] Any read options to be passed to ImageMagick
|
428
395
|
# for example: image.format('jpg', page, {density: '300'})
|
429
|
-
# @yield [MiniMagick::Tool
|
396
|
+
# @yield [MiniMagick::Tool] It optionally yields the command,
|
430
397
|
# if you want to add something.
|
431
398
|
# @return [self]
|
432
399
|
#
|
@@ -441,7 +408,7 @@ module MiniMagick
|
|
441
408
|
input_path = path.dup
|
442
409
|
input_path << "[#{page}]" if page && !layer?
|
443
410
|
|
444
|
-
MiniMagick
|
411
|
+
MiniMagick.convert do |convert|
|
445
412
|
read_opts.each do |opt, val|
|
446
413
|
convert.send(opt.to_s, val)
|
447
414
|
end
|
@@ -477,7 +444,7 @@ module MiniMagick
|
|
477
444
|
# c.background "blue"
|
478
445
|
# end
|
479
446
|
#
|
480
|
-
# @yield [MiniMagick::
|
447
|
+
# @yield [MiniMagick::Command]
|
481
448
|
# @see http://www.imagemagick.org/script/mogrify.php
|
482
449
|
# @return [self]
|
483
450
|
#
|
@@ -498,8 +465,11 @@ module MiniMagick
|
|
498
465
|
end
|
499
466
|
end
|
500
467
|
|
501
|
-
|
502
|
-
|
468
|
+
##
|
469
|
+
# Prevents ruby from calling `#to_ary` on the image when checking if it's a
|
470
|
+
# splattable data structure in certain cases.
|
471
|
+
def respond_to_missing?(name, include_all)
|
472
|
+
false
|
503
473
|
end
|
504
474
|
|
505
475
|
##
|
@@ -514,7 +484,7 @@ module MiniMagick
|
|
514
484
|
case output_to
|
515
485
|
when String, Pathname
|
516
486
|
if layer?
|
517
|
-
MiniMagick
|
487
|
+
MiniMagick.convert do |builder|
|
518
488
|
builder << path
|
519
489
|
builder << output_to
|
520
490
|
end
|
@@ -541,7 +511,7 @@ module MiniMagick
|
|
541
511
|
def composite(other_image, output_extension = type.downcase, mask = nil)
|
542
512
|
output_tempfile = MiniMagick::Utilities.tempfile(".#{output_extension}")
|
543
513
|
|
544
|
-
MiniMagick
|
514
|
+
MiniMagick.composite do |composite|
|
545
515
|
yield composite if block_given?
|
546
516
|
composite << other_image.path
|
547
517
|
composite << path
|
@@ -583,27 +553,21 @@ module MiniMagick
|
|
583
553
|
# b.verbose
|
584
554
|
# end # runs `identify -verbose image.jpg`
|
585
555
|
# @return [String] Output from `identify`
|
586
|
-
# @yield [MiniMagick::Tool
|
556
|
+
# @yield [MiniMagick::Tool]
|
587
557
|
#
|
588
558
|
def identify
|
589
|
-
MiniMagick
|
559
|
+
MiniMagick.identify do |builder|
|
590
560
|
yield builder if block_given?
|
591
561
|
builder << path
|
592
562
|
end
|
593
563
|
end
|
594
564
|
|
595
|
-
# @private
|
596
|
-
def run_command(tool_name, *args)
|
597
|
-
MiniMagick::Tool.const_get(tool_name.capitalize).new do |builder|
|
598
|
-
args.each do |arg|
|
599
|
-
builder << arg
|
600
|
-
end
|
601
|
-
end
|
602
|
-
end
|
603
|
-
|
604
565
|
def mogrify(page = nil)
|
605
|
-
MiniMagick
|
566
|
+
MiniMagick.mogrify do |builder|
|
606
567
|
yield builder if block_given?
|
568
|
+
if builder.args.include?("-format")
|
569
|
+
fail MiniMagick::Error, "you must call #format on a MiniMagick::Image directly"
|
570
|
+
end
|
607
571
|
builder << (page ? "#{path}[#{page}]" : path)
|
608
572
|
end
|
609
573
|
|
data/lib/mini_magick/shell.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require "
|
1
|
+
require "open3"
|
2
2
|
require "benchmark"
|
3
3
|
|
4
4
|
module MiniMagick
|
@@ -10,64 +10,34 @@ module MiniMagick
|
|
10
10
|
#
|
11
11
|
class Shell
|
12
12
|
|
13
|
-
def run(command,
|
14
|
-
stdout, stderr, status = execute(command,
|
13
|
+
def run(command, errors: MiniMagick.errors, warnings: MiniMagick.warnings, **options)
|
14
|
+
stdout, stderr, status = execute(command, **options)
|
15
15
|
|
16
|
-
if status != 0
|
17
|
-
|
16
|
+
if status != 0
|
17
|
+
if stderr.include?("time limit exceeded")
|
18
|
+
fail MiniMagick::TimeoutError, "`#{command.join(" ")}` has timed out"
|
19
|
+
elsif errors
|
20
|
+
fail MiniMagick::Error, "`#{command.join(" ")}` failed with status: #{status.inspect} and error:\n#{stderr}"
|
21
|
+
end
|
18
22
|
end
|
19
23
|
|
20
|
-
$stderr.print(stderr)
|
24
|
+
$stderr.print(stderr) if warnings
|
21
25
|
|
22
26
|
[stdout, stderr, status]
|
23
27
|
end
|
24
28
|
|
25
|
-
def execute(command,
|
26
|
-
stdout, stderr, status =
|
27
|
-
|
28
|
-
|
29
|
-
end
|
29
|
+
def execute(command, stdin: "", timeout: MiniMagick.timeout)
|
30
|
+
stdout, stderr, status = log(command.join(" ")) do
|
31
|
+
Open3.capture3({ "MAGICK_TIME_LIMIT" => timeout&.to_s }, *command, stdin_data: stdin)
|
32
|
+
end
|
30
33
|
|
31
|
-
[stdout, stderr, status
|
34
|
+
[stdout, stderr, status&.exitstatus]
|
32
35
|
rescue Errno::ENOENT, IOError
|
33
36
|
["", "executable not found: \"#{command.first}\"", 127]
|
34
37
|
end
|
35
38
|
|
36
39
|
private
|
37
40
|
|
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
|
-
unless thread.join(MiniMagick.timeout)
|
54
|
-
Process.kill("TERM", thread.pid) rescue nil
|
55
|
-
Process.waitpid(thread.pid) rescue nil
|
56
|
-
raise Timeout::Error, "MiniMagick command timed out: #{command}"
|
57
|
-
end
|
58
|
-
|
59
|
-
[stdout_reader.value, stderr_reader.value, thread.value]
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def execute_posix_spawn(command, options = {})
|
64
|
-
require "posix-spawn"
|
65
|
-
child = POSIX::Spawn::Child.new(*command, input: options[:stdin].to_s, timeout: MiniMagick.timeout)
|
66
|
-
[child.out, child.err, child.status]
|
67
|
-
rescue POSIX::Spawn::TimeoutExceeded
|
68
|
-
raise Timeout::Error, "MiniMagick command timed out: #{command}"
|
69
|
-
end
|
70
|
-
|
71
41
|
def log(command, &block)
|
72
42
|
value = nil
|
73
43
|
duration = Benchmark.realtime { value = block.call }
|