manipulator 0.1.0 → 1.0.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.
@@ -1,41 +1,34 @@
1
- = manipulator
1
+ = Manipulator
2
2
 
3
3
  S3 is great for storing image files, but manipulating images after they've been saved to S3 (rotating, resizing, cropping, etc.) is a bit of a chore. Usually it goes something like this:
4
4
 
5
5
  * download file from S3, save to local tmp file
6
- * run imagemagick over local tmp file
6
+ * run graphics or image magick over local tmp file
7
7
  * push modified image back to S3
8
8
  * remember to clean up tmp file
9
9
 
10
10
  After re-implementing this pattern a few times over (and filling up /tmp by forgetting the last step), we decided to roll this into a gem that makes S3 image manipulation as easy as:
11
11
 
12
12
  m = Manipulator.new
13
-
14
- m.manipulate('my-bucket','some/file.jpg') do |img|
15
- img.rotate(90).resize_to_fit(75,75)
13
+ m.manipulate({:bucket => 'my-bucket', :key => 'some/file.jpg'}) do |img|
14
+ img.rotate(90)
15
+ img.resize_to_fit(75,75)
16
16
  end
17
17
 
18
- The idea is to instantiate a Manipulator and then pass it a bucket name and key to manipulate. Manipulator will download your image to a local tmp file, then yield an RMagick Image wrapping that file. Call methods on the yielded image to make modifications; as soon as the block returns, the modified image is pushed back to S3 and the tmp file is removed.
19
-
20
- == Install
21
-
22
- Manipulator is released on gemcutter. If you haven't already set yourself up with that, here's how:
18
+ The idea is to instantiate a Manipulator and then pass it a bucket name and key to manipulate. Manipulator will download your image to a local tmp file, then yield a MiniMagick::Image wrapping that file. Call methods on the yielded image to make modifications; as soon as the block returns, the modified image is pushed back to S3 and the tmp file is removed.
23
19
 
24
- $ gem install gemcutter
25
- $ gem tumble
20
+ We're using MiniMagick now instead of RMagick to save memory - we found that RMagick was a hog. MiniMagick is a lot more straightforward and has a nice interface for both GraphicsMagick (recommended) and ImageMagick.
26
21
 
27
- Then, to install Manipulator:
22
+ == Install
28
23
 
29
24
  $ gem install manipulator
30
25
 
31
- You can also just declare the source:
32
-
33
- $ gem install manipulator -s http://gemcutter.org
34
26
 
35
27
  == Dependencies
36
28
 
37
- * AWSCredentials (http://github.com/bkoski/aws_credentials)
38
- * AWS/S3 (http://gemcutter.org/gems/aws-s3)
29
+ * AWS/S3 (http://rubygems.org/gems/aws-s3)
30
+ * AWSCredentials (http://rubygems.org/gems/aws_credentials)
31
+ * MiniMagick (http://rubygems.org/gems/mini_magick)
39
32
 
40
33
  == Future
41
34
 
@@ -43,4 +36,4 @@ You can also just declare the source:
43
36
 
44
37
  == Copyright
45
38
 
46
- Copyright (c) 2009 The New York Times Company. See LICENSE for details.
39
+ Copyright (c) 2010 The New York Times Company. See LICENSE for details.
@@ -1,58 +1,59 @@
1
- # Tries to avoid case-sensitivity issues when requiring RMagick.
2
- # Borrowed from jnicklas' carrierwave.
3
- unless defined? Magick
4
- begin
5
- require 'rmagick'
6
- rescue LoadError
7
- require 'RMagick'
8
- rescue LoadError
9
- puts "WARNING: Failed to require rmagick, image processing may fail!"
10
- end
11
- end
12
-
1
+ require 'mini_magick'
13
2
 
14
3
  # Using Manipulator is as simple as creating an instance and calling manipulate.
15
4
  #
16
5
  # The connection to S3 is opened using credentials read from AWSCredentials.
17
6
  class Manipulator
18
-
19
- # Path to the local temp file used during manipulation
20
7
  attr_accessor :temp_file_path
21
8
 
22
- # Downloads the specified bucket, key from S3 to a local temp file
23
- # and sets temp_file_path
9
+ # Establishes a connection to S3 if not already connected
10
+ def connect_to_s3
11
+ unless AWS::S3::Base.connected?
12
+ AWS::S3::Base.establish_connection!(:access_key_id => AWSCredentials.access_key, :secret_access_key => AWSCredentials.secret_access_key)
13
+ end
14
+ end
15
+
16
+ # Downloads the specified key from the S3 bucket to a local temp file
24
17
  def download(bucket, key)
25
- AWS::S3::Base.establish_connection!(:access_key_id => AWSCredentials.access_key, :secret_access_key => AWSCredentials.secret_access_key)
18
+ connect_to_s3
26
19
  @temp_file_path = File.join(Dir.tmpdir, key.gsub('/', '-'))
27
20
  File.open(temp_file_path, 'w+') do |f|
28
21
  f.puts AWS::S3::S3Object.value(key,bucket)
29
22
  end
30
23
  end
31
-
24
+
32
25
  # Pushes contents of temp file back to specified bucket, key on S3
26
+ # Returns the url for the file
33
27
  def upload(bucket, key)
28
+ connect_to_s3
34
29
  AWS::S3::S3Object.store(key, File.open(temp_file_path, 'r'), bucket, :access => :public_read)
30
+ AWS::S3::S3Object.url_for(key, bucket, :authenticated => false)
35
31
  end
36
32
 
37
33
  # Specify a S3 key to manipulate (and its bucket).
38
34
  #
39
- # Block yields a Magick::Image image instance with access to the usual RMagick methods.
40
- # Note that RMagick methods should be chained so that block returns image with all manipulations.
35
+ # Block yields a MiniMagick::Image image instance with access to MiniMagick's methods
36
+ # Note that you don't have to chain methods to return the image with all manipulations using MiniMagick:
41
37
  # For example, use
42
38
  # m.manipulate('my-bucket', 'my-key') do |img|
43
- # img.rotate(90).sepia_tone
39
+ # img.rotate(90)
40
+ # img.resize(100x100)
44
41
  # end
45
- # rather than
46
- # img.rotate(90)
47
- # img.sepia_tone
48
- # This last case will only <tt>sepia_tone</tt> the image.
49
- def manipulate(bucket, key, &block)
50
- download(bucket, key)
42
+ def manipulate(options, &block)
43
+ # TODO: make this configurable - use graphics magick
44
+ MiniMagick.processor = :gm
45
+
46
+ download(options[:bucket], options[:key])
51
47
  begin
52
- image = ::Magick::Image.read(temp_file_path).first
53
- new_image = yield(image)
54
- new_image.write(temp_file_path)
55
- upload(bucket, key)
48
+ image = MiniMagick::Image.open(temp_file_path)
49
+ image.combine_options do |i|
50
+ yield(i)
51
+ end
52
+ image.write(temp_file_path)
53
+ upload(options[:bucket], options[:key]) unless options[:keep_local]
54
+ rescue Exception => e
55
+ puts e.message
56
+ puts e.backtrace
56
57
  ensure
57
58
  cleanup
58
59
  end
@@ -62,5 +63,5 @@ class Manipulator
62
63
  def cleanup
63
64
  File.delete(temp_file_path)
64
65
  end
65
-
66
- end
66
+
67
+ end
@@ -14,13 +14,29 @@ class TestManipulator < Test::Unit::TestCase
14
14
  File.stubs(:open).returns(@new_file)
15
15
  end
16
16
 
17
+ context "#connect_to_s3" do
18
+ setup do
19
+ @manipulator = Manipulator.new
20
+ end
21
+ should "use existing connection" do
22
+ AWS::S3::Base.stubs(:connected?).returns(true)
23
+ AWS::S3::Base.expects(:establish_connection!).never
24
+ @manipulator.connect_to_s3
25
+ end
26
+ should "establish a new connection if none exists" do
27
+ AWS::S3::Base.stubs(:connected?).returns(false)
28
+ AWS::S3::Base.expects(:establish_connection!).with(:access_key_id => @access_key, :secret_access_key => @secret_key)
29
+ @manipulator.connect_to_s3
30
+ end
31
+ end
17
32
  context "#download" do
18
33
  setup do
19
34
  @manipulator = Manipulator.new
35
+ @manipulator.stubs(:connect_to_s3)
20
36
  AWS::S3::S3Object.stubs(:value).returns('123')
21
37
  end
22
- should "establish connection with credentials from aws_credentials" do
23
- AWS::S3::Base.expects(:establish_connection!).with(:access_key_id => @access_key, :secret_access_key => @secret_key)
38
+ should "connect to s3" do
39
+ @manipulator.expects(:connect_to_s3)
24
40
  @manipulator.download(@bucket, @key)
25
41
  end
26
42
  should "set temp_file_path, replacing any /s with -s" do
@@ -38,48 +54,53 @@ class TestManipulator < Test::Unit::TestCase
38
54
  context "#manipulate" do
39
55
  setup do
40
56
  @manipulator = Manipulator.new
57
+ @manipulator.stubs(:connect_to_s3)
41
58
  @file = stub_everything('fake file')
42
59
  @manipulator.stubs(:download).returns(@key)
43
60
  @manipulator.stubs(:upload)
44
61
  @manipulator.stubs(:cleanup)
45
62
  @manipulator.stubs(:temp_file_path).returns(@temp_file_path)
46
- @mocked = mock('fake rmagick instance')
47
- Magick::Image.stubs(:read).returns([@mocked])
63
+ @mocked = mock('fake mini_magick instance')
64
+ MiniMagick::Image.stubs(:read).returns(@mocked)
48
65
  @mocked.stubs(:write)
49
66
  end
50
67
 
51
68
  should "download the file" do
52
69
  @manipulator.expects(:download).with(@bucket, @key).returns(@key)
53
- @manipulator.manipulate(@bucket, @key) { |img| img }
70
+ @manipulator.manipulate({:bucket => @bucket, :key => @key}) { |img| img }
54
71
  end
55
- should "create an RMagick Image instance and yield it to the block" do
56
- Magick::Image.expects(:read).with(@temp_file_path).returns([@mocked])
57
- @manipulator.manipulate(@bucket, @key) do |img|
72
+ should "create an MiniMagick Image instance and yield it to the block" do
73
+ MiniMagick::Image.expects(:read).with(@temp_file_path).returns(@mocked)
74
+ @manipulator.manipulate({:bucket => @bucket, :key => @key}) do |img|
58
75
  assert_equal @mocked, img
59
76
  img
60
77
  end
61
78
  end
62
79
  should "write the returned manipulated image to the temp file" do
63
80
  @mocked.expects(:write).with(@temp_file_path)
64
- Magick::Image.stubs(:read).with(@temp_file_path).returns([@mocked])
65
- @manipulator.manipulate(@bucket, @key) { |img| img }
81
+ MiniMagick::Image.stubs(:read).with(@temp_file_path).returns(@mocked)
82
+ @manipulator.manipulate({:bucket => @bucket, :key => @key}) { |img| img }
66
83
  end
67
84
  should "upload the file back to s3" do
68
85
  @manipulator.expects(:upload).with(@bucket, @key)
69
- @manipulator.manipulate(@bucket, @key) { |img| img }
86
+ @manipulator.manipulate({:bucket => @bucket, :key => @key}) { |img| img }
87
+ end
88
+ should "not upload the file back to s3 when the keep_local option is passed" do
89
+ @manipulator.expects(:upload).never
90
+ @manipulator.manipulate({:bucket => @bucket, :key => @key, :keep_local => true}) { |img| img }
70
91
  end
71
92
  should "not run cleanup if download raises an error" do
72
93
  @manipulator.stubs(:download).raises('foo')
73
94
  @manipulator.expects(:cleanup).never
74
95
  begin
75
- @manipulator.manipulate(@bucket, @key) { |img| img }
96
+ @manipulator.manipulate({:bucket => @bucket, :key => @key}) {|img| img }
76
97
  rescue
77
98
  end
78
99
  end
79
100
  should "ensure that it calls cleanup if download success" do
80
101
  @manipulator.expects(:cleanup)
81
102
  begin
82
- @manipulator.manipulate(@bucket, @key) do |img|
103
+ @manipulator.manipulate({:bucket => @bucket, :key => @key}) do |img|
83
104
  raise "an error"
84
105
  end
85
106
  rescue
@@ -89,11 +110,13 @@ class TestManipulator < Test::Unit::TestCase
89
110
 
90
111
  context "#upload" do
91
112
  setup do
113
+ AWS::S3::S3Object.stubs(:url_for)
92
114
  @manipulator = Manipulator.new
93
115
  @manipulator.stubs(:temp_file_path).returns(@temp_file_path)
116
+ @manipulator.stubs(:connect_to_s3)
94
117
  end
95
118
  should "call S3Object.store with contents of tempfile" do
96
- AWS::S3::S3Object.expects(:store).with(@key,@new_file,@bucket, :access => :public_read)
119
+ AWS::S3::S3Object.expects(:store).with(@key, @new_file, @bucket, :access => :public_read)
97
120
  @manipulator.upload(@bucket, @key)
98
121
  end
99
122
  end
metadata CHANGED
@@ -1,7 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: manipulator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
5
11
  platform: ruby
6
12
  authors:
7
13
  - Jacqui Maher
@@ -10,49 +16,65 @@ autorequire:
10
16
  bindir: bin
11
17
  cert_chain: []
12
18
 
13
- date: 2009-12-22 00:00:00 -05:00
19
+ date: 2010-12-01 00:00:00 -05:00
14
20
  default_executable:
15
21
  dependencies:
16
22
  - !ruby/object:Gem::Dependency
17
23
  name: shoulda
18
- type: :development
19
- version_requirement:
20
- version_requirements: !ruby/object:Gem::Requirement
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
21
27
  requirements:
22
28
  - - ">="
23
29
  - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 0
24
33
  version: "0"
25
- version:
34
+ type: :development
35
+ version_requirements: *id001
26
36
  - !ruby/object:Gem::Dependency
27
37
  name: mocha
28
- type: :development
29
- version_requirement:
30
- version_requirements: !ruby/object:Gem::Requirement
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
31
41
  requirements:
32
42
  - - ">="
33
43
  - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 0
34
47
  version: "0"
35
- version:
48
+ type: :development
49
+ version_requirements: *id002
36
50
  - !ruby/object:Gem::Dependency
37
51
  name: aws_credentials
38
- type: :runtime
39
- version_requirement:
40
- version_requirements: !ruby/object:Gem::Requirement
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
41
55
  requirements:
42
56
  - - ">="
43
57
  - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
44
61
  version: "0"
45
- version:
62
+ type: :runtime
63
+ version_requirements: *id003
46
64
  - !ruby/object:Gem::Dependency
47
65
  name: aws-s3
48
- type: :runtime
49
- version_requirement:
50
- version_requirements: !ruby/object:Gem::Requirement
66
+ prerelease: false
67
+ requirement: &id004 !ruby/object:Gem::Requirement
68
+ none: false
51
69
  requirements:
52
70
  - - ">="
53
71
  - !ruby/object:Gem::Version
72
+ hash: 3
73
+ segments:
74
+ - 0
54
75
  version: "0"
55
- version:
76
+ type: :runtime
77
+ version_requirements: *id004
56
78
  description: A library to allow photo manipulation on S3 images using RMagick
57
79
  email: jacqui@brighter.net
58
80
  executables: []
@@ -79,21 +101,27 @@ rdoc_options:
79
101
  require_paths:
80
102
  - lib
81
103
  required_ruby_version: !ruby/object:Gem::Requirement
104
+ none: false
82
105
  requirements:
83
106
  - - ">="
84
107
  - !ruby/object:Gem::Version
108
+ hash: 3
109
+ segments:
110
+ - 0
85
111
  version: "0"
86
- version:
87
112
  required_rubygems_version: !ruby/object:Gem::Requirement
113
+ none: false
88
114
  requirements:
89
115
  - - ">="
90
116
  - !ruby/object:Gem::Version
117
+ hash: 3
118
+ segments:
119
+ - 0
91
120
  version: "0"
92
- version:
93
121
  requirements: []
94
122
 
95
123
  rubyforge_project:
96
- rubygems_version: 1.3.5
124
+ rubygems_version: 1.3.7
97
125
  signing_key:
98
126
  specification_version: 3
99
127
  summary: manipulate your photos on s3