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 ADDED
@@ -0,0 +1,6 @@
1
+ source :rubygems
2
+
3
+ group :dev do
4
+ gem 'rake'
5
+ gem 'jeweler'
6
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,16 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ git (1.2.5)
5
+ jeweler (1.6.0)
6
+ bundler (~> 1.0.0)
7
+ git (>= 1.2.5)
8
+ rake
9
+ rake (0.8.7)
10
+
11
+ PLATFORMS
12
+ ruby
13
+
14
+ DEPENDENCIES
15
+ jeweler
16
+ rake
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
@@ -0,0 +1,9 @@
1
+ require "tempfile"
2
+
3
+ module EventedMagick
4
+ class ImageTempFile < Tempfile
5
+ def make_tmpname(ext, n)
6
+ 'mini_magick%d-%d%s' % [$$, n, ext ? ".#{ext}" : '']
7
+ end
8
+ end
9
+ 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
+