image_genie 0.2.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/Manifest ADDED
@@ -0,0 +1,12 @@
1
+ Manifest
2
+ README.rdoc
3
+ Rakefile
4
+ lib/image_genie.rb
5
+ lib/image_genie/base.rb
6
+ lib/image_genie/command.rb
7
+ lib/image_genie/convert.rb
8
+ lib/image_genie/identify.rb
9
+ lib/image_genie/montage.rb
10
+ lib/image_genie/verify.rb
11
+ test/test_helper.rb
12
+ test/unit/base_test.rb
data/README.rdoc ADDED
File without changes
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+ require 'rake/testtask'
5
+
6
+ Echoe.new('image_genie', '0.2.0') do |p|
7
+ p.description = "ImageGenie - Simple Wrapper for command line ImageMagick"
8
+ p.url = "https://github.com/bkimble"
9
+ p.author = "Billy Kimble"
10
+ p.email = "basslines@gmail.com"
11
+ p.ignore_pattern = ["tmp/*", "script/*"]
12
+ p.development_dependencies = ["open4"]
13
+ end
14
+
15
+ Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
16
+
17
+ Rake::TestTask.new(:test) do |t|
18
+ t.libs << 'test'
19
+ t.test_files = FileList['test/**/*_test.rb']
20
+ t.verbose = true
21
+ end
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{image_genie}
5
+ s.version = "0.2.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Billy Kimble"]
9
+ s.date = %q{2011-02-08}
10
+ s.description = %q{ImageGenie - Simple Wrapper for command line ImageMagick}
11
+ s.email = %q{basslines@gmail.com}
12
+ s.extra_rdoc_files = ["README.rdoc", "lib/image_genie.rb", "lib/image_genie/base.rb", "lib/image_genie/command.rb", "lib/image_genie/convert.rb", "lib/image_genie/identify.rb", "lib/image_genie/montage.rb", "lib/image_genie/verify.rb"]
13
+ s.files = ["Manifest", "README.rdoc", "Rakefile", "lib/image_genie.rb", "lib/image_genie/base.rb", "lib/image_genie/command.rb", "lib/image_genie/convert.rb", "lib/image_genie/identify.rb", "lib/image_genie/montage.rb", "lib/image_genie/verify.rb", "test/test_helper.rb", "test/unit/base_test.rb", "image_genie.gemspec"]
14
+ s.homepage = %q{https://github.com/bkimble}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Image_genie", "--main", "README"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{image_genie}
18
+ s.rubygems_version = %q{1.4.2}
19
+ s.summary = %q{ImageGenie - Simple Wrapper for command line ImageMagick}
20
+ s.test_files = ["test/test_helper.rb", "test/unit/base_test.rb"]
21
+
22
+ if s.respond_to? :specification_version then
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
26
+ s.add_development_dependency(%q<open4>, [">= 0"])
27
+ else
28
+ s.add_dependency(%q<open4>, [">= 0"])
29
+ end
30
+ else
31
+ s.add_dependency(%q<open4>, [">= 0"])
32
+ end
33
+ end
@@ -0,0 +1,49 @@
1
+ require 'open4'
2
+
3
+ # ImageGenie -- a simple wrapper for ImageMagick.
4
+ # Billy Kimble 2011
5
+
6
+ # Usage:
7
+ #
8
+ # ImageGenie.convert(src=<File>, options)
9
+ # converts a file at src.path with command line options given as options, and returns a handle to a tempfile containing the
10
+ # contents of the converted file. Raises an exception with the errors if it can't find the file or has problems converting it.
11
+ #
12
+ # ImageGenie.montage(filenames=[], options)
13
+ # combines all files contained in filenames into a montage (mosaic) image, and returns a tempfile handle to the contents of the montage.
14
+ # Raises an exception with the errors if any 1 of the files in filenames has errors.
15
+ #
16
+ # ImageGenie.identify(filename=<String>, options)
17
+ # identifies the file and returns a hash containing:
18
+ # :filename, :format, :dimensions, :geometry, :bit, :image_class, :colors, :filesize, :usertime, :identify_time
19
+ # Raises an exception with the errors if it can't find the file or has problems identfying it.
20
+ #
21
+ # ImageGenie.verify(filename=<String>, options)
22
+ # verifies that the file pointed to by filename is valid. Returns true on success, or an an exception with the errors
23
+ # if it can't find the file or has problems identfying it
24
+
25
+ module ImageGenie
26
+ class UnableToLocateBinaryError < StandardError; end;
27
+ # Autoload any modules defined in classes in the image_genie directory.
28
+ module_path = [File.dirname(__FILE__),self.name.underscore,'*'].join('/')
29
+ Dir[module_path].each do |file|
30
+ constant_name = File.basename(file,'.rb').camelcase.to_sym
31
+ autoload constant_name, file
32
+ end
33
+
34
+ module ClassMethods
35
+ # Make helper methods
36
+ [:convert,:montage,:identify,:verify].each do |method|
37
+ define_method(method) do |*splat|
38
+ module_name = "#{self.name}::#{method.to_s.capitalize}"
39
+ target = module_name.constantize
40
+ target.run(*splat)
41
+ end
42
+ end
43
+ end
44
+
45
+ class << self
46
+ include ClassMethods
47
+ end
48
+ end
49
+
@@ -0,0 +1,63 @@
1
+ module ImageGenie
2
+ class Base
3
+
4
+ # Discover ImageMagick paths on the current system
5
+ def self.paths
6
+ if !@paths
7
+ @paths = {:identify => "",:montage => "",:convert => ""}
8
+ @paths.keys.each do |program|
9
+ command = "which #{program}"
10
+ pid, stdin, stdout, stderr = Open4::popen4(command)
11
+ path = stdout.read.strip
12
+ @paths[program] = path
13
+ end
14
+ end
15
+ @paths
16
+ end
17
+
18
+ # Eventually I want this method to accept a model that encapsulates its command execution logic.
19
+ def self.execute(command)
20
+ logger.info("Executing #{command}")
21
+ pid, stdin, stdout, stderr = Open4::popen4(command)
22
+ yield pid,stdin,stdout,stderr
23
+ end
24
+
25
+ def self.path_for(program)
26
+ paths[program.to_sym]
27
+ end
28
+
29
+ def self.make_command(command,*args)
30
+ command.strip!
31
+ components = [command]
32
+ args.each do |component|
33
+ components << case (component.class.name)
34
+ when 'Hash': make_flags(component)
35
+ when 'Array': make_args(component)
36
+ else " #{component}"
37
+ end
38
+ end
39
+ components.compact.join(' ')
40
+ end
41
+
42
+ def self.make_args(args)
43
+ args = [args] if !args.is_a?(Array)
44
+ return nil if args.empty?
45
+ args.join(' ')
46
+ end
47
+
48
+ # There is probably a better library to handle creation of command line flags,
49
+ # but this works for now.
50
+ def self.make_flags(options={})
51
+ return nil if options.empty?
52
+ options.collect{|k,v| "-#{k} #{v}"}.join(' ')
53
+ end
54
+
55
+ def self.logger
56
+ @log ||= TimestampedBufferedLogger.new(Rails.root.join('log','image_genie.log'))
57
+ end
58
+
59
+ def logger
60
+ self.class.logger
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,11 @@
1
+ module ImageGenie
2
+ class Command < Base
3
+ def self.handle_errors(stderr,status,exception)
4
+ unless status.exitstatus.zero?
5
+ errors = stderr.read.strip
6
+ logger.error("#{self.name} #{errors}")
7
+ raise(exception, errors)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,33 @@
1
+ module ImageGenie
2
+ class ConvertError < StandardError; end;
3
+ class Convert < Command
4
+ def self.run(src, options={})
5
+ raise(UnableToLocateBinaryError, 'convert') if path_for(:convert).blank?
6
+
7
+ logger.info("#{self.name} Attempting to convert #{src.path}")
8
+
9
+ begin
10
+ raise Errno::ENOENT,"#{src.path}" unless File.exists?(src.path)
11
+ rescue Errno::ENOENT => e
12
+ logger.error("#{self.name} #{e.message}")
13
+ raise
14
+ end
15
+
16
+ format = File.extname(src.path).downcase.gsub(/\.|\?/,'')
17
+
18
+ command = "#{path_for(:convert)} #{make_flags(options)} #{format}:#{src.path} #{format}:-"
19
+ temp_image_file = Tempfile.new([src.original_filename,".#{format}"])
20
+
21
+ execute(command) do |pid,stdin,stdout,stderr|
22
+ while line = stdout.gets
23
+ temp_image_file.puts line
24
+ end
25
+ ignored, status = Process::waitpid2 pid
26
+ handle_errors(stderr,status,ConvertError)
27
+ end
28
+
29
+ temp_image_file.rewind
30
+ return temp_image_file
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # Verify an image is valid by calling 'identify' on it and testing for errors on the return.
2
+ module ImageGenie
3
+ class IdentifyError < StandardError; end;
4
+ class Identify < Command
5
+ def self.run(filename, options={})
6
+ raise(UnableToLocateBinaryError, 'identify') if path_for(:identify).blank?
7
+
8
+ begin
9
+ raise Errno::ENOENT,"#{filename}" unless File.exists?(filename)
10
+ rescue Errno::ENOENT => e
11
+ logger.error("#{self.name} #{e.message}")
12
+ raise
13
+ end
14
+
15
+ command = "#{path_for(:identify)} #{filename}"
16
+ output = ''
17
+
18
+ # We test for STDERR content
19
+ execute(command) do |pid,stdin,stdout,stderr|
20
+ while !stdout.eof?
21
+ output += stdout.readline
22
+ end
23
+
24
+ ignored, status = Process::waitpid2 pid
25
+ handle_errors(stderr,status,IdentifyError)
26
+ end
27
+
28
+ fields = [:filename,:format,:dimensions,:geometry,:bit,:image_class,:colors,:filesize,:usertime,:identify_time]
29
+ return Hash[fields.zip(stdout.read.split(' '))]
30
+ end
31
+ end
32
+ end
33
+
@@ -0,0 +1,57 @@
1
+ module ImageGenie
2
+ class MontageError < StandardError; end;
3
+ class Montage < Command
4
+ attr_accessor :filenames,:filename,:options
5
+
6
+ def initialize(options={})
7
+ self.filenames = []
8
+ self.options = options
9
+ end
10
+
11
+
12
+ def self.run(filenames, options={})
13
+ raise(UnableToLocateBinaryError, 'montage') if path_for(:montage).blank?
14
+ # Montage hates when you attempt to give it files with a path in it, so we
15
+ # need to change to the system specified temp directory to do our work.
16
+ Dir.chdir(Dir::tmpdir)
17
+ # Create a temp file that will be all component files of this montage
18
+ temp_image_file = Tempfile.new(['montage','.jpg'])
19
+ temp_image_list_file = Tempfile.new(['montage_image_filenames','.txt'])
20
+ temp_image_list_file.puts filenames.join("\n")
21
+ temp_image_list_file.rewind
22
+
23
+ logger.info("#{self.name} Attempting to make a montage of #{filenames.size} files")
24
+
25
+ command = make_command(path_for(:montage), "@\"#{File.basename(temp_image_list_file.path)}\"", options, "jpeg:-")
26
+
27
+ begin
28
+ execute(command) do |pid,stdin,stdout,stderr|
29
+ while !stdout.eof?
30
+ temp_image_file.puts stdout.readline
31
+ end
32
+ temp_image_file.rewind
33
+
34
+ # Still foggy on if we need to wait until we read from STDOUT before waiting, or if
35
+ # we wait until we read STDOUT and STDERR.
36
+ ignored, status = Process::waitpid2 pid
37
+ handle_errors(stderr,status,MontageError)
38
+ end
39
+ rescue Exception => e
40
+ # Close the tempfile before raising
41
+ temp_image_file.close
42
+ raise
43
+ ensure
44
+ logger.info("#{self.name} Cleaning up files")
45
+ temp_image_list_file.close
46
+ end
47
+
48
+ return temp_image_file
49
+
50
+ end
51
+ end
52
+ end
53
+
54
+
55
+
56
+
57
+
@@ -0,0 +1,27 @@
1
+ # Verify an image is valid by calling 'identify' on it with verbose turned on ,
2
+ # and testing for errors on the return.
3
+ module ImageGenie
4
+ class VerifyError < StandardError; end;
5
+ class Verify < Command
6
+
7
+ def self.run(filename, options={})
8
+ raise(UnableToLocateBinaryError, 'identify') if path_for(:identify).blank?
9
+
10
+ begin
11
+ raise Errno::ENOENT,"#{filename}" unless File.exists?(filename)
12
+ rescue Errno::ENOENT => e
13
+ logger.error("#{self.name} #{e.message}")
14
+ raise
15
+ end
16
+
17
+ command = "#{path_for(:identify)} #{filename}"
18
+ # We test for STDERR content
19
+ execute(command) do |pid,stdin,stdout,stderr|
20
+ ignored, status = Process::waitpid2 pid
21
+ handle_errors(stderr,status,VerifyError)
22
+ end
23
+
24
+ return true
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ require 'rubygems'
2
+ require 'tempfile'
3
+ require 'test/unit'
4
+ require 'active_support'
5
+ require 'active_support/inflector' # needed for string methods like underscore
6
+ require 'open4'
7
+ require 'mocha'
8
+ require 'image_genie'
9
+
10
+ require 'active_support/testing/declarative'
11
+ include ActiveSupport::Testing::Declarative # for syntactic sugar of "test 'foo' do .."
@@ -0,0 +1,61 @@
1
+ require 'test_helper'
2
+
3
+ class BaseTest < Test::Unit::TestCase
4
+ test "path for when path exists" do
5
+ ImageGenie::Base.expects(:paths).returns({:convert => "/some/dir/convert", :montage => "/some/dir/montage"}).times(2)
6
+ assert_equal("/some/dir/convert", ImageGenie::Base.path_for(:convert))
7
+ assert_equal("/some/dir/montage", ImageGenie::Base.path_for(:montage))
8
+ end
9
+
10
+ test "path for when path doesnt exist" do
11
+ ImageGenie::Base.expects(:paths).returns({:convert => "/some/dir/convert", :montage => "/some/dir/montage"})
12
+ assert_equal(nil, ImageGenie::Base.path_for(:identify))
13
+ end
14
+
15
+ test "make command with args only" do
16
+ command = "some_command"
17
+ args = ["abc","def"]
18
+ assert_equal("some_command abc def", ImageGenie::Base.make_command(command,args))
19
+ end
20
+
21
+ test "make command with flags only" do
22
+ command = "some_command"
23
+ flags = {:foo => 1, :bar => 2}
24
+ assert_equal("some_command -foo 1 -bar 2", ImageGenie::Base.make_command(command,flags))
25
+ end
26
+
27
+ test "make command" do
28
+ command = "some_command"
29
+ args = ["abc","def"]
30
+ flags = {:foo => 1, :bar => 2}
31
+ assert_equal("some_command -foo 1 -bar 2 abc def", ImageGenie::Base.make_command(command,flags,args))
32
+ end
33
+
34
+ test "make command with no flags or args" do
35
+ command = "some_command"
36
+ args = []
37
+ flags = {}
38
+ assert_equal("some_command", ImageGenie::Base.make_command(command))
39
+ end
40
+
41
+ test "make flags" do
42
+ flags = {:foo => 1, :bar => 2}
43
+ assert_equal("-foo 1 -bar 2", ImageGenie::Base.make_flags(flags))
44
+ end
45
+
46
+ test "make flags with nothing" do
47
+ flags = {}
48
+ assert_equal(nil, ImageGenie::Base.make_flags(flags))
49
+ end
50
+
51
+ test "make args" do
52
+ args = ["abc","def"]
53
+ assert_equal("abc def", ImageGenie::Base.make_args(args))
54
+ end
55
+
56
+ test "make args with nothing" do
57
+ args = []
58
+ assert_equal(nil, ImageGenie::Base.make_args(args))
59
+ end
60
+ end
61
+
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: image_genie
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 2
9
+ - 0
10
+ version: 0.2.0
11
+ platform: ruby
12
+ authors:
13
+ - Billy Kimble
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-02-08 00:00:00 -08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: open4
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ description: ImageGenie - Simple Wrapper for command line ImageMagick
36
+ email: basslines@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.rdoc
43
+ - lib/image_genie.rb
44
+ - lib/image_genie/base.rb
45
+ - lib/image_genie/command.rb
46
+ - lib/image_genie/convert.rb
47
+ - lib/image_genie/identify.rb
48
+ - lib/image_genie/montage.rb
49
+ - lib/image_genie/verify.rb
50
+ files:
51
+ - Manifest
52
+ - README.rdoc
53
+ - Rakefile
54
+ - lib/image_genie.rb
55
+ - lib/image_genie/base.rb
56
+ - lib/image_genie/command.rb
57
+ - lib/image_genie/convert.rb
58
+ - lib/image_genie/identify.rb
59
+ - lib/image_genie/montage.rb
60
+ - lib/image_genie/verify.rb
61
+ - test/test_helper.rb
62
+ - test/unit/base_test.rb
63
+ - image_genie.gemspec
64
+ has_rdoc: true
65
+ homepage: https://github.com/bkimble
66
+ licenses: []
67
+
68
+ post_install_message:
69
+ rdoc_options:
70
+ - --line-numbers
71
+ - --inline-source
72
+ - --title
73
+ - Image_genie
74
+ - --main
75
+ - README
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ hash: 3
84
+ segments:
85
+ - 0
86
+ version: "0"
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ hash: 11
93
+ segments:
94
+ - 1
95
+ - 2
96
+ version: "1.2"
97
+ requirements: []
98
+
99
+ rubyforge_project: image_genie
100
+ rubygems_version: 1.4.2
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: ImageGenie - Simple Wrapper for command line ImageMagick
104
+ test_files:
105
+ - test/test_helper.rb
106
+ - test/unit/base_test.rb