evented_magick 0.1.0
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.
- data/Gemfile +6 -0
- data/Gemfile.lock +16 -0
- data/README.md +20 -0
- data/Rakefile +14 -0
- data/VERSION +1 -0
- data/evented_magick.gemspec +42 -0
- data/examples/processor.rb +56 -0
- data/lib/evented_magick.rb +184 -0
- data/lib/image_temp_file.rb +9 -0
- metadata +75 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/README.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
EventedMagick
|
2
|
+
----------------
|
3
|
+
|
4
|
+
A EventMachine-aware wrapper for the ImageMagick command line. Uses EM.system to execute if available.
|
5
|
+
Requires Ruby 1.9 since it uses Fibers. The internals have also been rewritten to reduce the number
|
6
|
+
of system() calls. These changes together reduced the time required to run my test from 20sec to 5sec versus the stock MiniMagick library.
|
7
|
+
|
8
|
+
Thanks
|
9
|
+
==========
|
10
|
+
|
11
|
+
Based on mini_magick by <http://github.com/probablycorey>
|
12
|
+
|
13
|
+
Author
|
14
|
+
==========
|
15
|
+
|
16
|
+
Mike Perham, mperham AT gmail.com,
|
17
|
+
[Github](http://github.com/mperham),
|
18
|
+
[Twitter](http://twitter.com/mperham),
|
19
|
+
[Blog](http://mikeperham.com)
|
20
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gem|
|
4
|
+
gem.name = 'evented_magick'
|
5
|
+
gem.summary = "MiniMagick on Eventmachine"
|
6
|
+
gem.email = "mperham@gmail.com"
|
7
|
+
gem.homepage = "https://github.com/mperham/evented/tree/master/evented_magick"
|
8
|
+
gem.authors = ["Mike Perham"]
|
9
|
+
end
|
10
|
+
|
11
|
+
Jeweler::GemcutterTasks.new
|
12
|
+
rescue LoadError
|
13
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: gem install jeweler"
|
14
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{evented_magick}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Mike Perham"]
|
12
|
+
s.date = %q{2011-05-19}
|
13
|
+
s.email = %q{mperham@gmail.com}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"README.md"
|
16
|
+
]
|
17
|
+
s.files = [
|
18
|
+
"Gemfile",
|
19
|
+
"Gemfile.lock",
|
20
|
+
"README.md",
|
21
|
+
"Rakefile",
|
22
|
+
"VERSION",
|
23
|
+
"evented_magick.gemspec",
|
24
|
+
"examples/processor.rb",
|
25
|
+
"lib/evented_magick.rb",
|
26
|
+
"lib/image_temp_file.rb"
|
27
|
+
]
|
28
|
+
s.homepage = %q{https://github.com/mperham/evented/tree/master/evented_magick}
|
29
|
+
s.require_paths = ["lib"]
|
30
|
+
s.rubygems_version = %q{1.6.2}
|
31
|
+
s.summary = %q{MiniMagick on Eventmachine}
|
32
|
+
|
33
|
+
if s.respond_to? :specification_version then
|
34
|
+
s.specification_version = 3
|
35
|
+
|
36
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
37
|
+
else
|
38
|
+
end
|
39
|
+
else
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Change this to a directory full of random images
|
2
|
+
GLOB='/Users/mike/junk/test_images/*.jpg'
|
3
|
+
|
4
|
+
require 'evented_magick'
|
5
|
+
|
6
|
+
# Execute using Ruby's normal system() call.
|
7
|
+
a = Time.now
|
8
|
+
files = Dir[GLOB]
|
9
|
+
files.each do |filename|
|
10
|
+
image = EventedMagick::Image.new(filename)
|
11
|
+
image['dimensions']
|
12
|
+
end
|
13
|
+
|
14
|
+
puts "Processed #{files.size} in #{Time.now - a} sec"
|
15
|
+
|
16
|
+
# Use Fibers in Ruby 1.9 and EventMachine to run the system
|
17
|
+
# calls in parallel. We only run up to 5 system() calls in parallel
|
18
|
+
# to prevent a fork bomb.
|
19
|
+
if defined? Fiber
|
20
|
+
require 'fiber'
|
21
|
+
require 'eventmachine'
|
22
|
+
|
23
|
+
NUM = 5
|
24
|
+
items = EM::Queue.new
|
25
|
+
total = 0
|
26
|
+
process = proc do |filename|
|
27
|
+
begin
|
28
|
+
Fiber.new do
|
29
|
+
image = EventedMagick::Image.new(filename)
|
30
|
+
image['dimensions']
|
31
|
+
total = total + 1
|
32
|
+
items.pop(process)
|
33
|
+
end.resume
|
34
|
+
rescue Exception => ex
|
35
|
+
puts ex.message
|
36
|
+
puts ex.backtrace.join("\n")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
EM.run do
|
41
|
+
a = Time.now
|
42
|
+
files = Dir[GLOB]
|
43
|
+
files.each do |filename|
|
44
|
+
items.push filename
|
45
|
+
end
|
46
|
+
|
47
|
+
NUM.times{ items.pop(process) }
|
48
|
+
EM.add_periodic_timer(1) do
|
49
|
+
if items.empty?
|
50
|
+
puts "Processed #{total} in #{Time.now - a} sec"
|
51
|
+
EM.stop
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require "open-uri"
|
2
|
+
require "stringio"
|
3
|
+
require "fileutils"
|
4
|
+
require "open3"
|
5
|
+
|
6
|
+
require File.join(File.dirname(__FILE__), '/image_temp_file')
|
7
|
+
|
8
|
+
module EventedMagick
|
9
|
+
class MiniMagickError < RuntimeError; end
|
10
|
+
|
11
|
+
class Image
|
12
|
+
attr :path
|
13
|
+
attr :tempfile
|
14
|
+
attr :output
|
15
|
+
|
16
|
+
# Class Methods
|
17
|
+
# -------------
|
18
|
+
class << self
|
19
|
+
def from_blob(blob, ext = nil)
|
20
|
+
begin
|
21
|
+
tempfile = ImageTempFile.new(ext)
|
22
|
+
tempfile.binmode
|
23
|
+
tempfile.write(blob)
|
24
|
+
ensure
|
25
|
+
tempfile.close if tempfile
|
26
|
+
end
|
27
|
+
|
28
|
+
return self.new(tempfile.path, tempfile)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Use this if you don't want to overwrite the image file
|
32
|
+
def open(image_path)
|
33
|
+
File.open(image_path, "rb") do |f|
|
34
|
+
self.from_blob(f.read, File.extname(image_path))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
alias_method :from_file, :open
|
38
|
+
end
|
39
|
+
|
40
|
+
# Instance Methods
|
41
|
+
# ----------------
|
42
|
+
def initialize(input_path, tempfile=nil)
|
43
|
+
@path = input_path
|
44
|
+
@tempfile = tempfile # ensures that the tempfile will stick around until this image is garbage collected.
|
45
|
+
@method = defined?(::EM) && EM.reactor_running? ? :evented_execute : :blocking_execute
|
46
|
+
|
47
|
+
# Ensure that the file is an image
|
48
|
+
output = run_command("identify", "-format", format_option("%m %w %h"), @path)
|
49
|
+
(format, width, height) = output.split
|
50
|
+
@values = { 'format' => format, 'width' => width.to_i, 'height' => height.to_i, 'dimensions' => [width.to_i, height.to_i] }
|
51
|
+
end
|
52
|
+
|
53
|
+
# For reference see http://www.imagemagick.org/script/command-line-options.php#format
|
54
|
+
def [](value)
|
55
|
+
key = value.to_s
|
56
|
+
return @values[key] if %w(format width height dimensions).include? key
|
57
|
+
if key == "size"
|
58
|
+
File.size(@path)
|
59
|
+
else
|
60
|
+
run_command('identify', '-format', "\"#{key}\"", @path).split("\n")[0]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Sends raw commands to imagemagick's mogrify command. The image path is automatically appended to the command
|
65
|
+
def <<(*args)
|
66
|
+
run_command("mogrify", *args << @path)
|
67
|
+
end
|
68
|
+
|
69
|
+
# This is a 'special' command because it needs to change @path to reflect the new extension
|
70
|
+
# Formatting an animation into a non-animated type will result in ImageMagick creating multiple
|
71
|
+
# pages (starting with 0). You can choose which page you want to manipulate. We default to the
|
72
|
+
# first page.
|
73
|
+
def format(format, page=0)
|
74
|
+
run_command("mogrify", "-format", format, @path)
|
75
|
+
|
76
|
+
old_path = @path.dup
|
77
|
+
@path.sub!(/(\.\w+)?$/, ".#{format}")
|
78
|
+
File.delete(old_path) unless old_path == @path
|
79
|
+
|
80
|
+
unless File.exists?(@path)
|
81
|
+
begin
|
82
|
+
FileUtils.copy_file(@path.sub(".#{format}", "-#{page}.#{format}"), @path)
|
83
|
+
rescue e
|
84
|
+
raise MiniMagickError, "Unable to format to #{format}; #{e}" unless File.exist?(@path)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
ensure
|
88
|
+
Dir[@path.sub(/(\.\w+)?$/, "-[0-9]*.#{format}")].each do |fname|
|
89
|
+
File.unlink(fname)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Writes the temporary image that we are using for processing to the output path
|
94
|
+
def write(output_path)
|
95
|
+
FileUtils.copy_file @path, output_path
|
96
|
+
run_command "identify", output_path # Verify that we have a good image
|
97
|
+
end
|
98
|
+
|
99
|
+
# Give you raw data back
|
100
|
+
def to_blob
|
101
|
+
f = File.new @path
|
102
|
+
f.binmode
|
103
|
+
f.read
|
104
|
+
ensure
|
105
|
+
f.close if f
|
106
|
+
end
|
107
|
+
|
108
|
+
# If an unknown method is called then it is sent through the morgrify program
|
109
|
+
# Look here to find all the commands (http://www.imagemagick.org/script/mogrify.php)
|
110
|
+
def method_missing(symbol, *args)
|
111
|
+
args.push(@path) # push the path onto the end
|
112
|
+
run_command("mogrify", "-#{symbol}", *args)
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
# You can use multiple commands together using this method
|
117
|
+
def combine_options(&block)
|
118
|
+
c = CommandBuilder.new
|
119
|
+
block.call c
|
120
|
+
run_command("mogrify", *c.args << @path)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Check to see if we are running on win32 -- we need to escape things differently
|
124
|
+
def windows?
|
125
|
+
!(RUBY_PLATFORM =~ /win32/).nil?
|
126
|
+
end
|
127
|
+
|
128
|
+
# Outputs a carriage-return delimited format string for Unix and Windows
|
129
|
+
def format_option(format)
|
130
|
+
windows? ? "#{format}\\n" : "#{format}\\\\n"
|
131
|
+
end
|
132
|
+
|
133
|
+
def run_command(command, *args)
|
134
|
+
full_args = args.collect do |arg|
|
135
|
+
# args can contain characters like '>' so we must escape them, but don't quote switches
|
136
|
+
if arg !~ /^[\+\-]/
|
137
|
+
"\"#{arg}\""
|
138
|
+
else
|
139
|
+
arg.to_s
|
140
|
+
end
|
141
|
+
end.join(' ')
|
142
|
+
|
143
|
+
full_cmd = "#{command} #{full_args}"
|
144
|
+
(output, status) = send(@method, full_cmd)
|
145
|
+
|
146
|
+
if status.exitstatus == 0
|
147
|
+
output
|
148
|
+
else
|
149
|
+
raise MiniMagickError, "ImageMagick command (#{full_cmd.inspect}) failed: #{{:status_code => status, :output => output}.inspect}"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def evented_execute(cmd)
|
154
|
+
fiber = Fiber.current
|
155
|
+
EM::system(cmd) do |output, status|
|
156
|
+
fiber.resume([output, status])
|
157
|
+
end
|
158
|
+
|
159
|
+
Fiber.yield
|
160
|
+
end
|
161
|
+
|
162
|
+
def blocking_execute(cmd)
|
163
|
+
output = `#{cmd}`
|
164
|
+
[output, $?]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class CommandBuilder
|
169
|
+
attr :args
|
170
|
+
|
171
|
+
def initialize
|
172
|
+
@args = []
|
173
|
+
end
|
174
|
+
|
175
|
+
def method_missing(symbol, *args)
|
176
|
+
@args << "-#{symbol}"
|
177
|
+
@args += args
|
178
|
+
end
|
179
|
+
|
180
|
+
def +(value)
|
181
|
+
@args << "+#{value}"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: evented_magick
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Mike Perham
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-05-19 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description:
|
23
|
+
email: mperham@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- README.md
|
30
|
+
files:
|
31
|
+
- Gemfile
|
32
|
+
- Gemfile.lock
|
33
|
+
- README.md
|
34
|
+
- Rakefile
|
35
|
+
- VERSION
|
36
|
+
- evented_magick.gemspec
|
37
|
+
- examples/processor.rb
|
38
|
+
- lib/evented_magick.rb
|
39
|
+
- lib/image_temp_file.rb
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: https://github.com/mperham/evented/tree/master/evented_magick
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
hash: 3
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
version: "0"
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
hash: 3
|
64
|
+
segments:
|
65
|
+
- 0
|
66
|
+
version: "0"
|
67
|
+
requirements: []
|
68
|
+
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.6.2
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: MiniMagick on Eventmachine
|
74
|
+
test_files: []
|
75
|
+
|