evented_magick 0.1.0

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