couch_photo 0.0.7 → 1.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +203 -105
- data/VERSION +1 -1
- data/couch_photo.gemspec +1 -0
- data/lib/couch_photo.rb +7 -2
- data/lib/couch_photo/couch_photo.rb +83 -88
- data/lib/couch_photo/custom_variation.rb +20 -0
- data/lib/couch_photo/{attachment.rb → fake_file.rb} +1 -1
- data/lib/couch_photo/original.rb +32 -0
- data/lib/couch_photo/variation.rb +10 -0
- data/lib/couch_photo/variation_config.rb +19 -0
- data/lib/couch_photo/variation_definition.rb +34 -0
- data/lib/couch_photo/variation_metadata.rb +70 -0
- metadata +36 -11
- data/lib/couch_photo/variations.rb +0 -103
data/README.markdown
CHANGED
@@ -4,15 +4,74 @@ A mixin for auto-generating image variations on Image documents with CouchDB.
|
|
4
4
|
|
5
5
|
##Installation
|
6
6
|
|
7
|
+
```sh
|
7
8
|
$ gem install couch_photo
|
8
|
-
|
9
|
+
```
|
9
10
|
## Documentation
|
10
11
|
|
11
|
-
Browse the documentation on rdoc.info: http://rdoc.info/
|
12
|
+
Browse the documentation on rdoc.info: http://rdoc.info/gems/couch_photo/frames
|
12
13
|
|
13
14
|
##How does it work?
|
14
15
|
|
15
|
-
Just "include CouchPhoto" in your "CouchRest::Model::Base" classes and
|
16
|
+
Just "include CouchPhoto" in your "CouchRest::Model::Base" classes and you can start creating image documents and autogenerating variations with ease.
|
17
|
+
|
18
|
+
### The original image
|
19
|
+
|
20
|
+
Each image document is centered around the concept of the "original" image. This is the original, unpolluted image that forms the core of the document.
|
21
|
+
|
22
|
+
You can use the `load_original_from_file` method on an instance of your `Image` class to create an image document with an original image from your filesystem:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
class Image < CouchRest::Model::Base
|
26
|
+
include CouchPhoto
|
27
|
+
end
|
28
|
+
|
29
|
+
@image = Image.new
|
30
|
+
@image.load_original_from_file "/path/to/my_file.jpg"
|
31
|
+
@image.save
|
32
|
+
```
|
33
|
+
|
34
|
+
Here, we've created a new image instance, then added an "original" image to it via the `load_original_from_file` method by specifiying a file on the disk. Upon `save`, this file is read from disk and stored in CouchDB. We could now access our original image thusly:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
@image.original.original_filename # ==> "my_file.jpg"
|
38
|
+
@image.original.path # ==> "/your_image_database/8383830jlkfdjskalfjdirewio/variations/original.jpg"
|
39
|
+
@image.original.url # ==> "http://your_couch_server/your_image_database/8383830jlkfdjskalfjdirewio/variations/original.jpg"
|
40
|
+
@image.original.extension # ==> "jpg"
|
41
|
+
@image.original.mimetype # ==> "image/jpg"
|
42
|
+
@image.original.data # ==> BINARY BLOB
|
43
|
+
@image.original.width # ==> 720
|
44
|
+
@image.original.height # ==> 480
|
45
|
+
```
|
46
|
+
|
47
|
+
Suppose the original image isn't on file, but simply in memory (perhaps this was a form upload). You can use the `load_original` method to pass a raw binary blob via the `:data` parameter:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
@image.load_original :filename => "my_file.jpg", :data => File.read("/path/to/my_file.jpg")
|
51
|
+
```
|
52
|
+
|
53
|
+
|
54
|
+
### The `override_id!` method
|
55
|
+
|
56
|
+
Instead of letting CouchDB generate a UUID for your image documents, you'll likely prefer that the basename of the "original" become the ID of your document.
|
57
|
+
|
58
|
+
You can accomplish this by simply calling the `override_id!` method inside your class definition:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
class Image < CouchRest::Model::Base
|
62
|
+
include CouchPhoto
|
63
|
+
override_id!
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
Now, if you create a new image document with an original, you'll have a much more human readable path to the original image on your CouchDB server:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
@image = Image.new
|
71
|
+
@image.load_original_from_file "/path/to/my_file.jpg"
|
72
|
+
@image.save
|
73
|
+
@image.original.url #==> http://your_couch_server/your_image_database/my_file.jpg/variations/original.jpg
|
74
|
+
```
|
16
75
|
|
17
76
|
###Simple Variations
|
18
77
|
|
@@ -20,77 +79,64 @@ Let's imagine we want to auto-generate small, medium, and large variations of ou
|
|
20
79
|
|
21
80
|
First, define an Image document and set the versions on it:
|
22
81
|
|
23
|
-
|
24
|
-
|
25
|
-
|
82
|
+
```ruby
|
83
|
+
class Image < CouchRest::Model::Base
|
84
|
+
use_database IMAGE_DB
|
85
|
+
include CouchPhoto
|
26
86
|
|
27
|
-
|
87
|
+
override_id! # the id of the document will be the basename of the original image file
|
28
88
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
89
|
+
variations do
|
90
|
+
small "20x20"
|
91
|
+
medium "100x100"
|
92
|
+
large "500x500"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
```
|
35
96
|
|
36
97
|
Next, create an instance of the image document and set the "original" image on it.
|
37
98
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
an actual file (e.g., maybe this was a form upload), then simply pass a blob as the second parameter. For example,
|
99
|
+
```ruby
|
100
|
+
i = Image.new
|
101
|
+
i.load_original_from_file "/path/to/avatar.jpg"
|
102
|
+
i.save
|
103
|
+
```
|
44
104
|
|
45
|
-
|
46
|
-
i.original = ["avatar.jpg", blob]
|
47
|
-
|
48
|
-
Now, if you look in your image document in CouchDB, you'd see the following attachments:
|
105
|
+
Now, if you look in your image document in CouchDB, you'll see the following attachments:
|
49
106
|
|
50
107
|
http://your_couch_server/your_image_database/avatar.jpg/original.jpg
|
51
108
|
http://your_couch_server/your_image_database/avatar.jpg/variations/small.jpg
|
52
109
|
http://your_couch_server/your_image_database/avatar.jpg/variations/medium.jpg
|
53
110
|
http://your_couch_server/your_image_database/avatar.jpg/variations/large.jpg
|
54
111
|
|
55
|
-
Notice that, because we called the `override_id!` method, `CouchPhoto` set the ID of your image document to the basename of your original image.
|
56
112
|
|
57
113
|
###Complex Variations
|
58
114
|
|
59
115
|
The previous variations were all simple image resizings. What if we wanted to do something a little more complex? Like grayscale the original image? Or create a watermark? No prob!
|
60
116
|
|
61
|
-
|
62
|
-
|
63
|
-
|
117
|
+
```ruby
|
118
|
+
class Image < CouchRest::Model::Base
|
119
|
+
use_database IMAGE_DB
|
120
|
+
include CouchPhoto
|
64
121
|
|
65
|
-
|
122
|
+
override_id! # the id of the document will be the basename of the original image file
|
66
123
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
124
|
+
variations do
|
125
|
+
grayscale do |original_image|
|
126
|
+
original_image.monochrome
|
127
|
+
end
|
71
128
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
end
|
76
|
-
end
|
129
|
+
watermark do |original_image|
|
130
|
+
original_image.composite(MiniMagick::Image.open("watermark.png", "jpg") do |c|
|
131
|
+
c.gravity "center"
|
77
132
|
end
|
78
133
|
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
```
|
79
137
|
|
80
138
|
The `original_image` variable in the blocks is simply the MiniMagick::Image instance of your original image.
|
81
139
|
|
82
|
-
### Accessing the Original Image
|
83
|
-
|
84
|
-
How do you access the the original image? Let me count the ways:
|
85
|
-
|
86
|
-
@image.original.original_filename # ==> "avatar.jpg"
|
87
|
-
@image.original.filename # ==> "original.jpg"
|
88
|
-
@image.original.path # ==> "/your_image_database/avatar.jpg/original.jpg"
|
89
|
-
@image.original.url # ==> "http://your_couch_server/your_image_database/avatar.jpg/original.jpg"
|
90
|
-
@image.original.basename # ==> "original.jpg"
|
91
|
-
@image.original.filetype # ==> "jpg"
|
92
|
-
@image.original.mimetype # ==> "image/jpg"
|
93
|
-
@image.original.data # ==> BINARY BLOB
|
94
140
|
|
95
141
|
### Accessing Variations
|
96
142
|
|
@@ -98,84 +144,136 @@ So, now that you've got some variations, how do you access them? Simple!
|
|
98
144
|
|
99
145
|
Let's go back to our original image example:
|
100
146
|
|
101
|
-
|
102
|
-
|
103
|
-
|
147
|
+
```ruby
|
148
|
+
class Image < CouchRest::Model::Base
|
149
|
+
include CouchPhoto
|
104
150
|
|
105
|
-
|
151
|
+
override_id!
|
106
152
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
153
|
+
variations do
|
154
|
+
small "20x20"
|
155
|
+
medium "100x100"
|
156
|
+
large "500x500"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
i = Image.new
|
161
|
+
i.load_original_from_file "/path/to/couchdb.jpg"
|
162
|
+
i.save
|
163
|
+
```
|
113
164
|
|
114
|
-
|
115
|
-
i.original = "avatar.jpg"
|
116
|
-
i.save
|
165
|
+
The `variations` method on our model is a hash of all the variations; the keys are the variation names, and the values are a variation object:
|
117
166
|
|
118
167
|
We can access our variations in one of three ways:
|
119
168
|
- the `variations` method, which will allow you to iterate over the variation_name/variation pairs via the `each` method
|
120
169
|
- the `variation.<variation_name>` method, which returns a specific variation
|
121
|
-
- the `variation
|
170
|
+
- the `variation[:<variation_name>]`, which also returns a specific variation
|
122
171
|
|
123
172
|
For example:
|
124
|
-
|
125
|
-
i.variations.each do |variation_name, variation_image|
|
126
|
-
puts variation_name
|
127
|
-
puts variation.name
|
128
|
-
puts variation.filename
|
129
|
-
# etc...
|
130
|
-
end
|
131
173
|
|
132
|
-
|
133
|
-
|
174
|
+
```ruby
|
175
|
+
# iterate over all variations
|
176
|
+
i.variations.each do |variation_name, variation_image|
|
177
|
+
puts variation_name
|
178
|
+
puts variation.filename
|
179
|
+
# etc...
|
180
|
+
end
|
181
|
+
|
182
|
+
# access the "small" variation
|
183
|
+
i.variations.small
|
184
|
+
|
185
|
+
# access the "large" variation
|
186
|
+
i.variations["large"]
|
187
|
+
#or...
|
188
|
+
i.variations[:large]
|
189
|
+
```
|
134
190
|
|
135
191
|
Once you've got a variation, you can call several methods on it:
|
136
192
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
193
|
+
```ruby
|
194
|
+
i.variations[:small].name # ==> "small"
|
195
|
+
i.variations[:small].path # ==> "/your_image_database/avatar.jpg/variations/small.jpg"
|
196
|
+
i.variations[:small].url # ==> "http://your.couch.server/your_image_database/avatar.jpg/variations/small.jpg"
|
197
|
+
i.variations[:small].width # ==> 50
|
198
|
+
i.variations[:small].height # ==> 50
|
199
|
+
i.variations[:small].basename # ==> "small.jpg"
|
200
|
+
i.variations[:small].filetype # ==> "jpg"
|
201
|
+
i.variations[:small].mimetype # ==> "image/jpg"
|
202
|
+
i.variations[:small].data # ==> BINARY BLOB
|
203
|
+
```
|
144
204
|
|
145
205
|
## Creating Custom Variations
|
146
206
|
|
147
|
-
If you'd like to add a variation to your image document that's not predefined / autogenerated, you can use the
|
207
|
+
If you'd like to add a variation to your image document that's not predefined / autogenerated, you can use the `load_custom_variation` and `load_custom_variation_from_file` methods:
|
148
208
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
209
|
+
```ruby
|
210
|
+
i = Image.new
|
211
|
+
i.load_original_from_file "/path/to/somefile.jpg"
|
212
|
+
i.load_custom_variation_from_file "/path/to/small_watermark.jpg"
|
213
|
+
i.load_custom_variation :filename => "large_greyscale.jpg", :data => File.read("/path/to/some_image.jpg")
|
214
|
+
i.save
|
215
|
+
```
|
153
216
|
|
154
|
-
These images would be accessible via the following urls
|
217
|
+
These images would be accessible via the following urls:
|
155
218
|
|
156
|
-
|
157
|
-
http://your_couch_server/your_image_database/somefile.jpg/variations/
|
158
|
-
http://your_couch_server/your_image_database/somefile.jpg/variations/
|
219
|
+
|
220
|
+
http://your_couch_server/your_image_database/somefile.jpg/variations/original.jpg
|
221
|
+
http://your_couch_server/your_image_database/somefile.jpg/variations/small_watermark.jpg
|
222
|
+
http://your_couch_server/your_image_database/somefile.jpg/variations/large_watermark.jpg
|
159
223
|
|
224
|
+
|
160
225
|
## XMP Metadata
|
161
|
-
Need to access XMP Metadata? It's as simple as adding xmp_metadata! into your document definition.
|
162
226
|
|
163
|
-
|
164
|
-
use_database IMAGE_DB
|
165
|
-
include CouchPhoto
|
227
|
+
Need to access the XMP Metadata on your original? It's as simple as adding `extract_xmp_metadata!` into your document definition.
|
166
228
|
|
167
|
-
|
168
|
-
|
229
|
+
```ruby
|
230
|
+
class Image < CouchRest::Model::Base
|
231
|
+
use_database IMAGE_DB
|
232
|
+
include CouchPhoto
|
169
233
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
234
|
+
extract_xmp_metadata!
|
235
|
+
override_id! # the id of the document will be the basename of the original image file
|
236
|
+
|
237
|
+
variations do
|
238
|
+
small "20x20"
|
239
|
+
medium "100x100"
|
240
|
+
large "500x500"
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
i = Image.new
|
245
|
+
i.load_original_from_file "avatar.jpg"
|
246
|
+
i.save
|
247
|
+
```
|
248
|
+
|
249
|
+
Now you can access your image's XMP metadata as a hash by calling `i.xmp_metadata`.
|
250
|
+
|
251
|
+
|
252
|
+
## Adding extra properties to your variations
|
253
|
+
|
254
|
+
Perhaps you'd like to add some extra properties that you can save for each of your image variations, like "alt" text, or captions. Simply use the `metadata` hash on your variations.
|
255
|
+
|
256
|
+
For example, given the following definition:
|
257
|
+
|
258
|
+
```ruby
|
259
|
+
class Image < CouchRest::Model::Base
|
260
|
+
use_database IMAGE_DB
|
261
|
+
include CouchPhoto
|
262
|
+
|
263
|
+
variations do
|
264
|
+
thumbnail "20x20"
|
265
|
+
grayscale do |original_image|
|
266
|
+
original_image.monochrome
|
175
267
|
end
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
268
|
+
end
|
269
|
+
end
|
270
|
+
```
|
271
|
+
|
272
|
+
You could now set any metadata properties you desire on your variations:
|
273
|
+
|
274
|
+
```ruby
|
275
|
+
@image = Image.first
|
276
|
+
@image.variations[:small].metadata["alt"] = "alt text"
|
277
|
+
@image.variations[:grayscale].metadata["caption"] = "Grayscale version of: #{@image.original.url}"
|
278
|
+
@image.save
|
279
|
+
```
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
1.0.0.beta.1
|
data/couch_photo.gemspec
CHANGED
data/lib/couch_photo.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
require 'mini_magick'
|
2
2
|
require 'couchrest_model'
|
3
|
-
require 'couch_photo/
|
4
|
-
require 'couch_photo/
|
3
|
+
require 'couch_photo/variation_metadata'
|
4
|
+
require 'couch_photo/custom_variation'
|
5
5
|
require 'couch_photo/couch_photo'
|
6
|
+
require 'couch_photo/fake_file'
|
7
|
+
require 'couch_photo/original'
|
8
|
+
require 'couch_photo/variation_definition'
|
9
|
+
require 'couch_photo/variation_config'
|
10
|
+
require 'couch_photo/variation'
|
@@ -1,119 +1,114 @@
|
|
1
1
|
module CouchPhoto
|
2
2
|
def self.included(base)
|
3
|
-
base.extend ClassMethods
|
4
3
|
base.property :original_filename
|
4
|
+
base.property :variation_metadata, Hash, :default => {}
|
5
|
+
base.extend ClassMethods
|
6
|
+
base.before_create :generate_variations
|
5
7
|
end
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
self.
|
10
|
-
|
11
|
-
|
9
|
+
module ClassMethods
|
10
|
+
def override_id!
|
11
|
+
self.before_create :set_id_to_original_filename
|
12
|
+
end
|
13
|
+
|
14
|
+
def extract_xmp_metadata!
|
15
|
+
@extract_xmp_metadata = true
|
16
|
+
self.property :xmp_metadata, Hash, :default => nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def variations(&block)
|
20
|
+
@variation_config ||= CouchPhoto::VariationConfig.new
|
21
|
+
@variation_config.instance_eval &block if block
|
22
|
+
@variation_config
|
23
|
+
end
|
24
|
+
|
25
|
+
def extract_xmp_metadata?
|
26
|
+
@extract_xmp_metadata
|
12
27
|
end
|
13
28
|
end
|
14
29
|
|
15
|
-
def
|
16
|
-
|
30
|
+
def load_original_from_file(filepath)
|
31
|
+
original.create_attachment File.basename(filepath), File.read(filepath)
|
17
32
|
end
|
18
33
|
|
19
|
-
def
|
20
|
-
raise "You
|
21
|
-
|
34
|
+
def load_original(options)
|
35
|
+
raise "You must provide a :filename parameter" unless options[:filename]
|
36
|
+
raise "You must provide a :data parameter" unless options[:data]
|
37
|
+
|
38
|
+
original.create_attachment options[:filename], options[:data]
|
22
39
|
end
|
23
40
|
|
24
|
-
def
|
25
|
-
|
41
|
+
def load_custom_variation_from_file(filepath)
|
42
|
+
variation_name = File.basename filepath
|
43
|
+
variations[variation_name] = CouchPhoto::CustomVariation.new self, variation_name
|
44
|
+
variations[variation_name].create_attachment File.read(filepath)
|
26
45
|
end
|
27
46
|
|
28
|
-
def
|
29
|
-
variation_name
|
47
|
+
def load_custom_variation(variation_name, blob)
|
48
|
+
variations[variation_name] = CouchPhoto::CustomVariation.new self, variation_name
|
49
|
+
variations[variation_name].create_attachment blob
|
30
50
|
end
|
31
51
|
|
32
|
-
def original
|
33
|
-
|
34
|
-
|
35
|
-
else
|
36
|
-
filepath = args[0]
|
37
|
-
blob = nil
|
38
|
-
end
|
39
|
-
image_format = filetype(filepath)
|
40
|
-
filename = File.basename filepath
|
41
|
-
basename = File.basename(filepath, "." + "#{image_format}")
|
42
|
-
|
43
|
-
if self.class.xmp_metadata?
|
44
|
-
if blob
|
45
|
-
File.open("/tmp/#{filename}", 'w') { |f| f.write blob } if !blob.nil?
|
46
|
-
`convert /tmp/#{filename} /tmp/#{basename}.xmp`
|
47
|
-
else
|
48
|
-
`convert #{filepath} /tmp/#{basename}.xmp`
|
49
|
-
end
|
50
|
-
self.metadata = Hash.from_xml File.read("/tmp/#{basename}.xmp")
|
51
|
-
File.delete("/tmp/#{basename}.xmp")
|
52
|
-
end
|
52
|
+
def original
|
53
|
+
@original ||= CouchPhoto::Original.new self
|
54
|
+
end
|
53
55
|
|
54
|
-
|
55
|
-
|
56
|
-
blob ||= File.read filepath
|
57
|
-
attachment_name = "original.#{image_format}"
|
58
|
-
attachment = {:name => attachment_name, :file => Attachment.new(blob, attachment_name)}
|
59
|
-
update_or_create_attachment attachment
|
60
|
-
self.class.variation_definitions.run_variations(blob).each do |variation_name, variation_blob|
|
61
|
-
update_or_create_attachment :name => "variations/#{variation_name}.#{image_format}", :file => Attachment.new(variation_blob, attachment_name)
|
62
|
-
end
|
56
|
+
def variations
|
57
|
+
@variations ||= load_variations
|
63
58
|
end
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
59
|
+
|
60
|
+
private
|
61
|
+
def load_variations
|
62
|
+
all_variations = {}
|
63
|
+
|
64
|
+
# setup defined variations
|
65
|
+
defined_variation_names.each do |defined_variation_name|
|
66
|
+
all_variations[defined_variation_name.to_sym] = CouchPhoto::Variation.new self, defined_variation_name
|
67
|
+
end
|
68
|
+
|
69
|
+
# setup custom variations
|
70
|
+
custom_variation_names.each do |custom_variation_name|
|
71
|
+
all_variations[custom_variation_name] = CouchPhoto::CustomVariation.new self, custom_variation_name
|
70
72
|
end
|
71
|
-
|
72
|
-
|
73
|
+
|
74
|
+
all_variations
|
73
75
|
end
|
74
76
|
|
75
|
-
def
|
76
|
-
|
77
|
+
def defined_variation_names
|
78
|
+
self.class.variations.variation_definitions.keys.map(&:to_s)
|
77
79
|
end
|
78
80
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
variation_definitions.instance_eval(&block)
|
83
|
-
end
|
84
|
-
|
85
|
-
def override_id!
|
86
|
-
@override_id = true
|
87
|
-
end
|
88
|
-
|
89
|
-
def xmp_metadata!
|
90
|
-
@xmp_metadata = true
|
91
|
-
self.property :metadata, Hash
|
92
|
-
end
|
93
|
-
|
94
|
-
def xmp_metadata?
|
95
|
-
@xmp_metadata
|
96
|
-
end
|
97
|
-
|
98
|
-
def override_id?
|
99
|
-
@override_id
|
81
|
+
def custom_variation_names
|
82
|
+
all_variation_names.select do |variation_name|
|
83
|
+
!defined_variation_names.include?(strip_extension(variation_name))
|
100
84
|
end
|
85
|
+
end
|
101
86
|
|
102
|
-
|
103
|
-
|
87
|
+
def all_variation_names
|
88
|
+
if self["_attachments"]
|
89
|
+
self["_attachments"].keys.
|
90
|
+
select {|attachment_name| attachment_name.match /variations\/.*/}.
|
91
|
+
map {|attachment_name| attachment_name.gsub(/^variations\/(.*)$/) { $1 }}
|
92
|
+
else
|
93
|
+
[]
|
104
94
|
end
|
105
95
|
end
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
variation_path.gsub(/(?:variations\/)?(.+)\.[^\.]+/) {$1}
|
96
|
+
|
97
|
+
def strip_extension(filename)
|
98
|
+
filename.gsub(/\.[^.]*/, '')
|
110
99
|
end
|
111
|
-
|
112
|
-
def
|
113
|
-
|
100
|
+
|
101
|
+
def set_id_to_original_filename
|
102
|
+
if self.original_filename.blank?
|
103
|
+
self.errors.add :original, "must be set before create"
|
104
|
+
else
|
105
|
+
self.id = self.original_filename
|
106
|
+
end
|
114
107
|
end
|
115
|
-
|
116
|
-
def
|
117
|
-
|
108
|
+
|
109
|
+
def generate_variations
|
110
|
+
self.class.variations.variation_definitions.each do |variation_name, variation_definition|
|
111
|
+
variation_definition.generate_variation self
|
112
|
+
end
|
118
113
|
end
|
119
114
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module CouchPhoto
|
2
|
+
class CustomVariation
|
3
|
+
include VariationMetadata
|
4
|
+
|
5
|
+
def initialize(document, variation_name)
|
6
|
+
@document = document
|
7
|
+
@variation_name = variation_name
|
8
|
+
end
|
9
|
+
|
10
|
+
def create_attachment(blob)
|
11
|
+
@data = blob
|
12
|
+
@document.create_attachment :name => attachment_name, :file => FakeFile.new(@data)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def attachment_name
|
17
|
+
"variations/#{@variation_name}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module CouchPhoto
|
2
|
+
class Original
|
3
|
+
include VariationMetadata
|
4
|
+
|
5
|
+
def initialize(document)
|
6
|
+
@document = document
|
7
|
+
end
|
8
|
+
|
9
|
+
def create_attachment(filename, data)
|
10
|
+
extension = File.extname filename
|
11
|
+
@data = data
|
12
|
+
@attachment_name = "variations/original#{extension}"
|
13
|
+
@document.create_attachment :name => @attachment_name, :file => FakeFile.new(@data)
|
14
|
+
@document.original_filename = File.basename(filename)
|
15
|
+
@document.xmp_metadata = xmp_metadata if @document.class.extract_xmp_metadata?
|
16
|
+
end
|
17
|
+
|
18
|
+
def original_filename
|
19
|
+
if exists?
|
20
|
+
@document.original_filename
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def attachment_name
|
26
|
+
@attachment_name ||=
|
27
|
+
@document["_attachments"].keys.select do |attachment_name|
|
28
|
+
attachment_name.match /variations\/original\.[^.]*/
|
29
|
+
end.first
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CouchPhoto
|
2
|
+
class VariationConfig
|
3
|
+
attr_reader :variation_definitions
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@variation_definitions = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(variation_name, *args, &block)
|
10
|
+
variation_name = variation_name.to_sym
|
11
|
+
resize_definition = args.first
|
12
|
+
@variation_definitions[variation_name.to_sym] = CouchPhoto::VariationDefinition.new(
|
13
|
+
variation_name,
|
14
|
+
resize_definition,
|
15
|
+
&block
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module CouchPhoto
|
2
|
+
class VariationDefinition
|
3
|
+
def initialize(variation_name, resize_definition=nil, &custom_definition)
|
4
|
+
@variation_name = variation_name
|
5
|
+
@resize_definition = resize_definition
|
6
|
+
@custom_definition = custom_definition
|
7
|
+
end
|
8
|
+
|
9
|
+
def generate_variation(document)
|
10
|
+
attachment_name = "variations/#{@variation_name}.#{document.original.extension}"
|
11
|
+
variation = manipulate document.original.data
|
12
|
+
|
13
|
+
if document.has_attachment?(attachment_name)
|
14
|
+
document.update_attachment :name => attachment_name, :file => FakeFile.new(variation)
|
15
|
+
else
|
16
|
+
document.create_attachment :name => attachment_name, :file => FakeFile.new(variation)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def manipulate(image_blob)
|
23
|
+
mini_magick_image = MiniMagick::Image.read(image_blob)
|
24
|
+
|
25
|
+
if @resize_definition
|
26
|
+
mini_magick_image.resize @resize_definition
|
27
|
+
elsif @custom_definition
|
28
|
+
@custom_definition.call mini_magick_image
|
29
|
+
end
|
30
|
+
|
31
|
+
mini_magick_image.to_blob
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module CouchPhoto
|
2
|
+
module VariationMetadata
|
3
|
+
def path
|
4
|
+
if exists?
|
5
|
+
"/#{@document.database.name}/#{@document.id}/#{attachment_name}"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def extension
|
10
|
+
if exists?
|
11
|
+
@extension ||= File.extname(attachment_name).gsub(/\./, '')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def mime_type
|
16
|
+
if exists?
|
17
|
+
@mime_type ||= @document["_attachments"][attachment_name]["content_type"]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def data
|
22
|
+
if exists?
|
23
|
+
@data ||= @document.read_attachment attachment_name
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def width
|
28
|
+
mini_magick[:width]
|
29
|
+
end
|
30
|
+
|
31
|
+
def metadata
|
32
|
+
@document.variation_metadata[@variation_name.to_s] ||= {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def height
|
36
|
+
mini_magick[:height]
|
37
|
+
end
|
38
|
+
|
39
|
+
def xmp_metadata
|
40
|
+
tmp_image_uuid = `uuidgen`.strip
|
41
|
+
tmp_image_file_name = "/tmp/#{tmp_image_uuid}.#{extension}"
|
42
|
+
tmp_xmp_file_name = "/tmp/#{tmp_image_uuid}.xmp"
|
43
|
+
mini_magick.write tmp_image_file_name
|
44
|
+
`convert #{tmp_image_file_name} #{tmp_xmp_file_name} 2> /dev/null`
|
45
|
+
Hash.from_xml File.read(tmp_xmp_file_name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def url
|
49
|
+
if exists?
|
50
|
+
"#{@document.database.root}/#{@document.id}/#{attachment_name}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def exists?
|
56
|
+
!@document["_attachments"][attachment_name].nil?
|
57
|
+
end
|
58
|
+
|
59
|
+
def mini_magick
|
60
|
+
if exists?
|
61
|
+
@mini_magick ||= MiniMagick::Image.read(self.data)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def attachment_name
|
66
|
+
"variations/#{@variation_name}.#{@document.original.extension}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
metadata
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: couch_photo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 62196353
|
5
|
+
prerelease: 6
|
6
6
|
segments:
|
7
|
+
- 1
|
7
8
|
- 0
|
8
9
|
- 0
|
9
|
-
-
|
10
|
-
|
10
|
+
- beta
|
11
|
+
- 1
|
12
|
+
version: 1.0.0.beta.1
|
11
13
|
platform: ruby
|
12
14
|
authors:
|
13
15
|
- Matt Parker
|
@@ -15,7 +17,7 @@ autorequire:
|
|
15
17
|
bindir: bin
|
16
18
|
cert_chain: []
|
17
19
|
|
18
|
-
date: 2011-09-
|
20
|
+
date: 2011-09-25 00:00:00 Z
|
19
21
|
dependencies:
|
20
22
|
- !ruby/object:Gem::Dependency
|
21
23
|
name: cucumber
|
@@ -105,6 +107,22 @@ dependencies:
|
|
105
107
|
version: 1.0.0
|
106
108
|
type: :runtime
|
107
109
|
version_requirements: *id006
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: activesupport
|
112
|
+
prerelease: false
|
113
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ~>
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
hash: 7
|
119
|
+
segments:
|
120
|
+
- 3
|
121
|
+
- 0
|
122
|
+
- 0
|
123
|
+
version: 3.0.0
|
124
|
+
type: :runtime
|
125
|
+
version_requirements: *id007
|
108
126
|
description: Manage an image and all of its variations and xmp metadata in a single document.
|
109
127
|
email: moonmaster9000@gmail.com
|
110
128
|
executables: []
|
@@ -114,9 +132,14 @@ extensions: []
|
|
114
132
|
extra_rdoc_files: []
|
115
133
|
|
116
134
|
files:
|
117
|
-
- lib/couch_photo/attachment.rb
|
118
135
|
- lib/couch_photo/couch_photo.rb
|
119
|
-
- lib/couch_photo/
|
136
|
+
- lib/couch_photo/custom_variation.rb
|
137
|
+
- lib/couch_photo/fake_file.rb
|
138
|
+
- lib/couch_photo/original.rb
|
139
|
+
- lib/couch_photo/variation.rb
|
140
|
+
- lib/couch_photo/variation_config.rb
|
141
|
+
- lib/couch_photo/variation_definition.rb
|
142
|
+
- lib/couch_photo/variation_metadata.rb
|
120
143
|
- lib/couch_photo.rb
|
121
144
|
- VERSION
|
122
145
|
- README.markdown
|
@@ -141,12 +164,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
141
164
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
142
165
|
none: false
|
143
166
|
requirements:
|
144
|
-
- - "
|
167
|
+
- - ">"
|
145
168
|
- !ruby/object:Gem::Version
|
146
|
-
hash:
|
169
|
+
hash: 25
|
147
170
|
segments:
|
148
|
-
-
|
149
|
-
|
171
|
+
- 1
|
172
|
+
- 3
|
173
|
+
- 1
|
174
|
+
version: 1.3.1
|
150
175
|
requirements: []
|
151
176
|
|
152
177
|
rubyforge_project:
|
@@ -1,103 +0,0 @@
|
|
1
|
-
module CouchPhoto
|
2
|
-
|
3
|
-
|
4
|
-
class VariationDefinitions
|
5
|
-
attr_reader :variations
|
6
|
-
|
7
|
-
def initialize
|
8
|
-
@variations = {}
|
9
|
-
end
|
10
|
-
|
11
|
-
def method_missing(method_name, *args, &block)
|
12
|
-
if block
|
13
|
-
@variations[method_name.to_sym] = VariationDefinition.new method_name, *args, &block
|
14
|
-
else
|
15
|
-
@variations[method_name.to_sym] = VariationDefinition.new method_name, *args
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def run_variations(blob)
|
20
|
-
results = {}
|
21
|
-
@variations.each do |variation_name, variation|
|
22
|
-
results[variation_name] = variation.create_variation blob
|
23
|
-
end
|
24
|
-
results
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
class VariationDefinition
|
29
|
-
def initialize(name, format=nil, &block)
|
30
|
-
raise "A variation can not have both a format and a block. Choose one." if format and block
|
31
|
-
raise "A variation must have either a format (e.g., '20x20>') or a block." if !format and !block
|
32
|
-
@name = name
|
33
|
-
@format = format
|
34
|
-
@block = block
|
35
|
-
end
|
36
|
-
|
37
|
-
def create_variation(blob)
|
38
|
-
image = MiniMagick::Image.read(blob)
|
39
|
-
@format ? image.resize(@format) : @block.call(image)
|
40
|
-
image.to_blob
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
class Variations
|
45
|
-
attr_reader :variations, :document
|
46
|
-
|
47
|
-
def initialize(document)
|
48
|
-
@document = document
|
49
|
-
@variations = {}
|
50
|
-
attachments = document["_attachments"] || {}
|
51
|
-
attachments.keys.select {|name| name.match /variations\//}.each do |variation_name|
|
52
|
-
if document.class.variation_definitions.variations.keys.include?(CouchPhoto.variation_short_name(variation_name).to_sym)
|
53
|
-
variation_key = CouchPhoto.variation_short_name(variation_name)
|
54
|
-
else
|
55
|
-
variation_key = CouchPhoto.variation_short_name_from_path(variation_name)
|
56
|
-
end
|
57
|
-
@variations[variation_key] = Variation.new document, variation_name
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def each(&block)
|
62
|
-
@variations.each &block
|
63
|
-
end
|
64
|
-
|
65
|
-
def count
|
66
|
-
attachments = self.document["_attachments"] || {}
|
67
|
-
attachments.keys.count {|name| name.match /variations\//}
|
68
|
-
end
|
69
|
-
|
70
|
-
def method_missing(method_name, *args, &block)
|
71
|
-
raise "Unknown variation '#{method_name}'" unless @variations[method_name.to_s]
|
72
|
-
@variations[method_name.to_s]
|
73
|
-
end
|
74
|
-
|
75
|
-
def add_variation(variation_name)
|
76
|
-
@variations[variation_name] = Variation.new document, "variations/#{variation_name}"
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
class Variation
|
81
|
-
attr_reader :name, :url, :path, :filename, :basename, :filetype, :mimetype
|
82
|
-
|
83
|
-
def initialize(document, attachment_name)
|
84
|
-
@path = "/" + [document.database.name, document.id, attachment_name].join("/")
|
85
|
-
@url = [document.database.to_s, document.id, attachment_name].join "/"
|
86
|
-
@attachment_name = attachment_name
|
87
|
-
@name = CouchPhoto.variation_short_name attachment_name
|
88
|
-
@filename = attachment_name
|
89
|
-
@basename = File.basename attachment_name
|
90
|
-
@document = document
|
91
|
-
@filetype = CouchPhoto.variation_file_extension attachment_name
|
92
|
-
@mimetype = document["_attachments"][attachment_name]["content_type"]
|
93
|
-
end
|
94
|
-
|
95
|
-
def data
|
96
|
-
@document.read_attachment @attachment_name
|
97
|
-
end
|
98
|
-
|
99
|
-
def original_filename
|
100
|
-
@document.original_filename
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|