couch_photo 0.0.7 → 1.0.0.beta.1
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.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
|