carrierwave-meta 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5eb83f3482998f4a6d77e2f0eee81aac037cc2b4
4
+ data.tar.gz: c01a5c5d1c24f5ac61b7d7f02f082c37fa9ee22f
5
+ SHA512:
6
+ metadata.gz: f3030e8b0b781f98cd64b6ab5bde51d70f80ad1551e7fadb6d8a508893df233c708ff72a03dcd64fe3d7ef506c6c7a83848872ccc0478857d76864a799715a3d
7
+ data.tar.gz: 80276a9f29cec556543907344b0b0d7f87ad8e5f53c651880a60f67da3545cad6b963af7323602a5d7443d38903301a7e9065980b1312bb122749ef1c9e3aac2
data/.gitignore CHANGED
@@ -2,4 +2,5 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
- tmp/*
5
+ tmp/*
6
+ coverage
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0
4
+ - 1.9.3
5
+ before_install:
6
+ - uname -a
7
+ - sudo apt-get install libvips-dev
@@ -1,5 +1,8 @@
1
1
  = carrierwave-meta
2
2
 
3
+ {<img src="https://secure.travis-ci.org/gzigzigzeo/carrierwave-meta.png" alt="Build Status" />}[http://travis-ci.org/gzigzigzeo/carrierwave-meta]
4
+ {<img src="https://codeclimate.com/github/gzigzigzeo/carrierwave-meta.png" />}[https://codeclimate.com/github/gzigzigzeo/carrierwave-meta]
5
+
3
6
  = Installation
4
7
 
5
8
  Add the following line to your Gemfile:
@@ -10,56 +13,59 @@ Add the following line to your Gemfile:
10
13
  class TestUploader < CarrierWave::Uploader::Base
11
14
  include CarrierWave::RMagick
12
15
  include CarrierWave::Meta
13
-
14
- storage :file
15
16
 
16
- process :store_meta
17
+ process :store_meta => [{md5sum: true}]
17
18
  version :version do
18
19
  process :resize_to_fill => [200, 200]
19
20
  process :store_meta
20
21
  end
21
22
  end
22
-
23
+
23
24
  file = File.open('test.jpg') # JPEG 500x300, 20000 bytes
24
25
  uploader = TestUploader.new
25
26
  uploader.store!(file)
26
-
27
+
27
28
  uploader.width # 500
28
29
  uploader.height # 300
29
30
  uploader.image_size # [500, 300]
30
- uploader.file_zie # 20000
31
+ uploader.file_size # 20000
31
32
  uploader.content_type # "image/jpeg"
32
-
33
+ uploader.md5sum # "fuuaasdfasdf...."
34
+
33
35
  uploader.version.width # 200
34
- uploader.version.height # 200
35
- uploader.version.image_size # [200, 200]
36
+ uploader.version.height # 200
37
+ uploader.version.image_size # [200, 200]
36
38
  uploader.version.file_zie # less than 20000
37
39
  uploader.version.content_type # "image/jpeg"
40
+ uploader.version.md5sum # nil
38
41
 
39
42
  = Saving values to database
40
43
 
41
- Simply create database columns to hold metadata in your model's table. Currently gem supports width, height,
42
- image_size ([width, height]), content_type and file_size fields. Versions are supported too.
44
+ Simply create database columns to hold metadata in your model's table. Currently
45
+ gem supports width, height, image_size ([width, height]), content_type,
46
+ file_size and MD5 fields. Versions are supported too.
43
47
 
44
48
  class TestModel
45
- attr_accessor :image_width
49
+ attr_accessor :image_width
46
50
  attr_accessor :image_height
47
- attr_accessor :image_image_size
51
+ attr_accessor :image_image_size
48
52
  attr_accessor :image_content_type
49
53
  attr_accessor :image_file_size
54
+ attr_accessor :image_md5sum
50
55
 
51
- attr_accessor :image_version_width
56
+ attr_accessor :image_version_width
52
57
  attr_accessor :image_version_height
53
58
  attr_accessor :image_version_image_size
54
59
  attr_accessor :image_version_content_type
55
- attr_accessor :image_version_file_size
60
+ attr_accessor :image_version_file_size
61
+ attr_accessor :image_version_md5sum
56
62
  end
57
63
 
58
64
  file = File.open('test.jpg')
59
65
  model = TestModel.new
60
66
  uploader = TestUploader.new(model, :image)
61
67
  uploader.store!(file)
62
-
68
+
63
69
  uploader.width # 500
64
70
  model.image_width # 500
65
71
  model.image_height # 300
@@ -67,38 +73,66 @@ image_size ([width, height]), content_type and file_size fields. Versions are su
67
73
 
68
74
  When columns are available in the model instance, metadata is stored in that columns.
69
75
 
76
+ = Saving values into single column
77
+
78
+ For now, works only with ActiveRecord.
79
+
80
+ class TestModel < ActiveRecord::Base
81
+ extend CarrierWave::Meta::ActiveRecord
82
+
83
+ mount_uploader :image, TestUploader
84
+ serialize :image_meta, OpenStruct
85
+ carrierwave_meta_composed :image_meta,
86
+ :image, image_version: [:width, :height, :md5sum]
87
+ end
88
+
89
+ model = TestModel.new
90
+ model.image.store!('test.jpg')
91
+ model.image_width # 200
92
+ model.image_version_width # 200
93
+ model.image_meta # {image_width: 200, image_height: 200, ...}
94
+
95
+ All you need is image_meta column, all other attributes are virtual. Note
96
+ that carrierwave_meta_composed should be called after mounting uploader.
97
+
70
98
  = Behind the scenes
71
99
 
72
- After the file is retrieved from store or cache metadata is recalculated unless uploader has attached
73
- model instance. If uploader has attached model instance values are read from that instance.
100
+ After the file is retrieved from store or cache metadata is recalculated
101
+ unless uploader has attached model instance. If uploader has attached
102
+ model instance values are read from that instance.
74
103
 
75
104
  uploader = TestUploader.new
76
105
  uploader.retrieve_from_store!('test.jpg')
77
-
78
106
  uploader.version.width # 200
79
107
 
108
+ model = TestModel.new
109
+ model.image.store!('test.jpg')
110
+ model.image_width # 200
111
+ model.image.width # 200, actually read from image_width
112
+
80
113
  = model_delegate_attribute
81
114
 
82
- Used to synchronize data between uploader and mounted model instance. Model's instance is used like value cache.
115
+ Is used to synchronize data between uploader and mounted model instance.
116
+ Model's instance is used like value cache.
83
117
 
84
118
  class DelegateTestModel
85
119
  attr_accessor :processed
86
120
  attr_accessor :a_processed
87
121
  attr_accessor :a_b_processed
88
122
  end
89
-
123
+
90
124
  class DelegateTestUploader < CarrierWave::Uploader::Base
91
125
  model_delegate_attribute :uploaded
92
-
126
+
93
127
  set_processed
94
-
128
+
95
129
  version :a do
96
130
  set_processed
97
131
  version :b do
98
132
  set_processed
99
133
  end
100
134
  end
101
-
135
+
102
136
  def set_processed
103
137
  self.processed = true
104
138
  end
@@ -107,23 +141,25 @@ Used to synchronize data between uploader and mounted model instance. Model's in
107
141
  model = DelegateTestModel.new
108
142
  uploader = DelegateTestUploader.new(model, :image)
109
143
  file = File.open('test.jpg')
110
-
144
+
111
145
  uploader.store!(file)
112
-
146
+
113
147
  model.processed # true
114
- model.a_processed # true
148
+ model.a_processed # true
115
149
  model.a_b_processed # true
116
-
150
+
117
151
  model.a_processed = false
118
-
152
+
119
153
  uploader.processed # true
120
154
  uploader.a_processed # false
121
155
  uploader.a_b_processed # true
122
156
 
123
157
  When model is mounted to uploader:
124
158
 
125
- 1. If attribute is assigned inside uploader then corresponding property in model is also assigned.
126
- 2. If attribute is retrieved from uploader instance first checks that value is defined in model and returns it. Otherwise returns uploader's instance variable.
159
+ 1. If attribute is assigned inside uploader then corresponding property
160
+ in model is also assigned.
161
+ 2. If attribute is retrieved from uploader, uploader checks that value is
162
+ defined in model and returns it. Otherwise returns uploader's instance variable.
127
163
  3. If file is deleted, value becomes nil.
128
164
 
129
165
  Otherwise acts as regular uploader's instance variables.
@@ -143,7 +179,7 @@ The uploader:
143
179
  version :crop_source do
144
180
  process :resize_to_fit => [300, 300]
145
181
  process :store_meta
146
-
182
+
147
183
  # This is the cropped version of parent image. Let crop to 50x50 square.
148
184
  version :crop do
149
185
  process :crop_to => [50, 50]
@@ -151,7 +187,7 @@ The uploader:
151
187
  end
152
188
 
153
189
  # Defines crop area dimensions.
154
- # This should be assigned before #store! and #cache! called and should be saved in the model's instance.
190
+ # This should be assigned before #store! and #cache! called and should be saved in the model's instance.
155
191
  # Otherwise cropped image would be lost after #recreate_versions! is called.
156
192
  # If crop area dimensions are'nt assigned, uploader calculates crop area dimensions inside the
157
193
  # parent image and creates the default image.
@@ -177,13 +213,13 @@ The uploader:
177
213
  cropped_img = img.crop(x, y, width, height)
178
214
  new_img = cropped_img.resize_to_fill(new_width, new_height)
179
215
  destroy_image(cropped_img)
180
- destroy_image(img)
216
+ destroy_image(img)
181
217
  new_img
182
218
  end
183
219
  end
184
220
 
185
221
  # Creates the default crop image.
186
- # Here the original crop area dimensions are restored and assigned to the model's instance.
222
+ # Here the original crop area dimensions are restored and assigned to the model's instance.
187
223
  def resize_to_fill_and_save_dimensions(new_width, new_height)
188
224
  manipulate! do |img|
189
225
  width, height = img.columns, img.rows
@@ -194,20 +230,20 @@ The uploader:
194
230
  h_ratio = height.to_f / new_height.to_f
195
231
 
196
232
  ratio = [w_ratio, h_ratio].min
197
-
233
+
198
234
  self.w = ratio * new_width
199
235
  self.h = ratio * new_height
200
236
  self.x = (width - self.w) / 2
201
237
  self.y = (height - self.h) / 2
202
-
238
+
203
239
  new_img
204
240
  end
205
241
  end
206
-
242
+
207
243
  private
208
244
  def crop_args
209
245
  %w(x y w h).map { |accessor| send(accessor).to_i }
210
- end
246
+ end
211
247
  end
212
248
 
213
249
  # Post should have :crop_source_version_x, :crop_source_version_y, :crop_source_version_h, :crop_source_version_w columns
@@ -221,14 +257,14 @@ The uploader:
221
257
  post.save!
222
258
 
223
259
  post.image.crop_source.width # 300
224
- post.image.crop_source.height # 200
260
+ post.image.crop_source.height # 200
225
261
  post.image.crop_source.crop.width # 50
226
262
  post.image.crop_source.crop.height # 50
227
-
263
+
228
264
  # Default crop area coordinates within the limits of big image dimensions: square at the center of an image
229
265
  post.image.crop_source.crop.x # 50
230
266
  post.image.crop_source.crop.y # 50
231
- post.image.crop_source.crop.w # 200
267
+ post.image.crop_source.crop.w # 200
232
268
  post.image.crop_source.crop.h # 200
233
269
 
234
270
  # Let user change the crop area with JCrop script. Pass new crop area parameters to the model.
@@ -238,19 +274,51 @@ The uploader:
238
274
  post.crop_source_crop_h = 100
239
275
 
240
276
  post.save! # Crop image is reprocessed
241
-
277
+
242
278
  post.image.crop_source.crop.width # 50
243
279
  post.image.crop_source.crop.height # 50
244
280
 
281
+ = PDF/GhostScript support
282
+
283
+ If you want to use this plugin with PDF/PostScript files than you should install
284
+ GhostScript and rebuild ImageMagick with GhostScript support:
285
+
286
+ brew install ghostscript
287
+ brew install imagemagick --with-ghostscript
288
+ gem uninstall rmagick && gem install rmagick
289
+
290
+ To switch on PDF/EPS processing you should enable GhostScript somewhere in your
291
+ app's initializer:
292
+
293
+ CarrierWave::Meta.ghostscript_enabled = true
294
+
245
295
  = A note about testing
246
296
 
297
+ @SergeyKishenin added specs for EPS/GhostScript files. They run for image_magick
298
+ or mini_magick processor by default. To make specs work please install
299
+ GhostScript as described above. To run specs WITHOUT PDF/EPS do:
300
+
301
+ PDF_EPS=false bundle exec rspec
302
+
247
303
  @fschwahn added support for mini-magick. To run tests with mini-magick do:
248
304
 
249
- bundle exec rake spec PROCESSOR=mini_magick
305
+ PROCESSOR=mini_magick bundle exec rspec
306
+
307
+ @skord added support for ImageSorcery. To run specs do:
308
+
309
+ PROCESSOR=image_sorcery bundle exec rspec
310
+
311
+ To run specs against VIPS processor do:
312
+
313
+ PROCESSOR=vips bundle exec rspec
314
+
315
+ To run specs against with Fog (Amazon S3) simulation:
316
+
317
+ STORAGE=fog bundle exec rspec
250
318
 
251
319
  = TODO
252
320
 
253
321
  1. I do not know how it would work with S3 and other remote storages. Should be tested.
254
322
  2. Write integration guide for JCrop.
255
323
  3. A notice about content-type.
256
- 4. Improve my english skills.
324
+
@@ -26,4 +26,9 @@ Gem::Specification.new do |s|
26
26
  s.add_development_dependency(%q<rmagick>)
27
27
  s.add_development_dependency(%q<mini_magick>)
28
28
  s.add_development_dependency(%q<mime-types>)
29
+ s.add_development_dependency(%q<carrierwave-imagesorcery>)
30
+ s.add_development_dependency(%q<carrierwave-vips>)
31
+ s.add_development_dependency(%q<fog>, '~> 1.3.1')
32
+ s.add_development_dependency(%q<simplecov>)
33
+ s.add_development_dependency(%q<activerecord>, '>= 3.0')
29
34
  end
@@ -1,5 +1,7 @@
1
1
  require 'active_support/concern'
2
+ require 'active_support/core_ext/module/attribute_accessors'
2
3
  require 'mime/types'
3
- require "carrierwave-meta/version"
4
+ require 'carrierwave-meta/version'
4
5
  require 'carrierwave-meta/model_delegate_attribute'
5
6
  require 'carrierwave-meta/meta'
7
+ require 'carrierwave-meta/active_record'
@@ -0,0 +1,22 @@
1
+ module CarrierWave
2
+ module Meta
3
+ module ActiveRecord
4
+ ALLOWED = %w(width height md5sum image_size file_size content_type)
5
+
6
+ def carrierwave_meta_composed(single_attribute, *args)
7
+ defined_attrs = args.map do |arg|
8
+ name, to_define = if arg.is_a?(Symbol)
9
+ [arg, ALLOWED]
10
+ elsif arg.is_a?(Hash)
11
+ [arg.keys.first, arg.values.first]
12
+ end
13
+
14
+ to_define.map do |attr|
15
+ delegate :"#{name}_#{attr}", to: single_attribute, allow_nil: true
16
+ delegate :"#{name}_#{attr}=", to: single_attribute, allow_nil: true
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -2,6 +2,9 @@ module CarrierWave
2
2
  module Meta
3
3
  extend ActiveSupport::Concern
4
4
 
5
+ mattr_accessor :ghostscript_enabled
6
+ self.ghostscript_enabled = false
7
+
5
8
  included do
6
9
  include CarrierWave::ModelDelegateAttribute
7
10
  include CarrierWave::MimeTypes
@@ -10,7 +13,7 @@ module CarrierWave
10
13
 
11
14
  after :retrieve_from_cache, :set_content_type
12
15
  after :retrieve_from_cache, :call_store_meta
13
- after :retrieve_from_store, :set_content_type
16
+ after :retrieve_from_store, :set_content_type unless storage.name =~ /Fog/
14
17
  after :retrieve_from_store, :call_store_meta
15
18
 
16
19
  model_delegate_attribute :content_type, ''
@@ -18,9 +21,10 @@ module CarrierWave
18
21
  model_delegate_attribute :image_size, []
19
22
  model_delegate_attribute :width, 0
20
23
  model_delegate_attribute :height, 0
24
+ model_delegate_attribute :md5sum, ''
21
25
  end
22
26
 
23
- def store_meta
27
+ def store_meta(options = {})
24
28
  if self.file.present?
25
29
  dimensions = get_dimensions
26
30
  width, height = dimensions
@@ -29,41 +33,71 @@ module CarrierWave
29
33
  self.image_size = dimensions
30
34
  self.width = width
31
35
  self.height = height
36
+ if options[:md5sum]
37
+ self.md5sum = Digest::MD5.hexdigest(File.read(self.file.path))
38
+ end
32
39
  end
33
40
  end
34
41
 
35
- def set_content_type(file = nil)
36
- set_content_type(true)
37
- end
38
-
39
42
  def image_size_s
40
43
  image_size.join('x')
41
44
  end
42
45
 
43
46
  private
44
47
  def call_store_meta(file = nil)
45
- # Re-retrieve metadata for a file only if model is not present OR model is not saved.
46
- store_meta if self.model.nil? || (self.model.respond_to?(:new_record?) && self.model.new_record?)
48
+ # Re-retrieve metadata for a file only if model is not present OR
49
+ # model is not saved.
50
+ if model.nil? || (model.respond_to?(:new_record?) && model.new_record?)
51
+ processor_options = processors.
52
+ find { |p| p.first == :store_meta }.
53
+ try(:[], 1)
54
+
55
+ store_meta(*processor_options)
56
+ end
47
57
  end
48
58
 
49
59
  def get_dimensions
50
60
  [].tap do |size|
51
- if self.file.content_type =~ /image/
52
- manipulate! do |img|
53
- if defined?(::Magick::Image) && img.is_a?(::Magick::Image)
54
- size << img.columns
55
- size << img.rows
56
- elsif defined?(::MiniMagick::Image) && img.is_a?(::MiniMagick::Image)
57
- size << img["width"]
58
- size << img["height"]
59
- else
60
- raise "Only RMagick & MiniMagick are supported yet. Fork and update it."
61
- end
62
- img
61
+ is_image = file.content_type =~ /image/
62
+ is_pdf =
63
+ file.content_type =~ /postscript|pdf/ &&
64
+ CarrierWave::Meta.ghostscript_enabled
65
+
66
+ is_dimensionable = is_image || is_pdf
67
+
68
+ manipulate! do |img|
69
+ if processor?(:rmagick, img) && is_dimensionable
70
+ size << img.columns
71
+ size << img.rows
72
+ elsif processor?(:mini_magick, img) && is_dimensionable
73
+ size << img['width']
74
+ size << img['height']
75
+ elsif processor?(:socrecy, img) && is_image
76
+ size << img.dimensions[:x].to_i
77
+ size << img.dimensions[:y].to_i
78
+ elsif processor?(:vips, img) && is_image
79
+ size << img.x_size
80
+ size << img.y_size
81
+ else
82
+ raise "Unsupported file type/image processor (use RMagick, MiniMagick, ImageSorcery, VIPS)"
63
83
  end
84
+ img
64
85
  end
65
86
  end
66
87
  rescue CarrierWave::ProcessingError
67
88
  end
89
+
90
+ def processor?(processor, img)
91
+ processor = PROCESSORS[processor]
92
+ processor_class = processor.constantize rescue nil
93
+ processor_class.present? && img.is_a?(processor_class)
94
+ end
95
+
96
+ PROCESSORS = {
97
+ rmagick: 'Magick::Image',
98
+ mini_magick: 'MiniMagick::Image',
99
+ socrecy: 'ImageSorcery',
100
+ vips: 'VIPS::Image'
101
+ }
68
102
  end
69
- end
103
+ end