manipulator 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +12 -19
- data/lib/manipulator/manipulator.rb +34 -33
- data/test/test_manipulator.rb +37 -14
- metadata +49 -21
data/README.rdoc
CHANGED
@@ -1,41 +1,34 @@
|
|
1
|
-
=
|
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
|
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
|
-
|
15
|
-
img.
|
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
|
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
|
-
|
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
|
-
|
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
|
-
*
|
38
|
-
*
|
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)
|
39
|
+
Copyright (c) 2010 The New York Times Company. See LICENSE for details.
|
@@ -1,58 +1,59 @@
|
|
1
|
-
|
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
|
-
#
|
23
|
-
|
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
|
-
|
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
|
40
|
-
# Note that
|
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)
|
39
|
+
# img.rotate(90)
|
40
|
+
# img.resize(100x100)
|
44
41
|
# end
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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 = ::
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
data/test/test_manipulator.rb
CHANGED
@@ -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 "
|
23
|
-
|
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
|
47
|
-
|
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
|
56
|
-
|
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
|
-
|
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) {
|
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
|
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
|
-
|
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:
|
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
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
34
|
+
type: :development
|
35
|
+
version_requirements: *id001
|
26
36
|
- !ruby/object:Gem::Dependency
|
27
37
|
name: mocha
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
48
|
+
type: :development
|
49
|
+
version_requirements: *id002
|
36
50
|
- !ruby/object:Gem::Dependency
|
37
51
|
name: aws_credentials
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
62
|
+
type: :runtime
|
63
|
+
version_requirements: *id003
|
46
64
|
- !ruby/object:Gem::Dependency
|
47
65
|
name: aws-s3
|
48
|
-
|
49
|
-
|
50
|
-
|
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
|
-
|
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.
|
124
|
+
rubygems_version: 1.3.7
|
97
125
|
signing_key:
|
98
126
|
specification_version: 3
|
99
127
|
summary: manipulate your photos on s3
|