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.
- 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
|