= carrierwave-meta
{}[http://travis-ci.org/gzigzigzeo/carrierwave-meta] {}[https://codeclimate.com/github/gzigzigzeo/carrierwave-meta]
= Installation
Add the following line to your Gemfile: gem 'carrierwave-meta'
= Usage example
class TestUploader < CarrierWave::Uploader::Base include CarrierWave::RMagick include CarrierWave::Meta
process :store_meta => [{md5sum: true}]
version :version do
process :resize_to_fill => [200, 200]
process :store_meta
end
end
file = File.open('test.jpg') # JPEG 500x300, 20000 bytes uploader = TestUploader.new uploader.store!(file)
uploader.width # 500 uploader.height # 300 uploader.image_size # [500, 300] uploader.file_size # 20000 uploader.content_type # "image/jpeg" uploader.md5sum # "fuuaasdfasdf...."
uploader.version.width # 200 uploader.version.height # 200 uploader.version.image_size # [200, 200] uploader.version.file_zie # less than 20000 uploader.version.content_type # "image/jpeg" uploader.version.md5sum # nil
= Saving values to database
Simply create database columns to hold metadata in your model's table. Currently gem supports width, height, image_size ([width, height]), content_type, file_size and MD5 fields. Versions are supported too.
class TestModel attr_accessor :image_width attr_accessor :image_height attr_accessor :image_image_size attr_accessor :image_content_type attr_accessor :image_file_size attr_accessor :image_md5sum
attr_accessor :image_version_width
attr_accessor :image_version_height
attr_accessor :image_version_image_size
attr_accessor :image_version_content_type
attr_accessor :image_version_file_size
attr_accessor :image_version_md5sum
end
file = File.open('test.jpg') model = TestModel.new uploader = TestUploader.new(model, :image) uploader.store!(file)
uploader.width # 500 model.image_width # 500 model.image_height # 300 ...
When columns are available in the model instance, metadata is stored in that columns.
= Saving values into single column
For now, works only with ActiveRecord.
class TestModel < ActiveRecord::Base extend CarrierWave::Meta::ActiveRecord
mount_uploader :image, TestUploader
serialize :image_meta, OpenStruct
carrierwave_meta_composed :image_meta,
:image, image_version: [:width, :height, :md5sum]
end
model = TestModel.new model.image.store!('test.jpg') model.image_width # 200 model.image_version_width # 200 model.image_meta # {image_width: 200, image_height: 200, ...}
All you need is image_meta column, all other attributes are virtual. Note that carrierwave_meta_composed should be called after mounting uploader.
= Behind the scenes
After the file is retrieved from store or cache metadata is recalculated unless uploader has attached model instance. If uploader has attached model instance values are read from that instance.
uploader = TestUploader.new uploader.retrieve_from_store!('test.jpg') uploader.version.width # 200
model = TestModel.new model.image.store!('test.jpg') model.image_width # 200 model.image.width # 200, actually read from image_width
= model_delegate_attribute
Is used to synchronize data between uploader and mounted model instance. Model's instance is used like value cache.
class DelegateTestModel attr_accessor :processed attr_accessor :a_processed attr_accessor :a_b_processed end
class DelegateTestUploader < CarrierWave::Uploader::Base model_delegate_attribute :uploaded
set_processed
version :a do
set_processed
version :b do
set_processed
end
end
def set_processed
self.processed = true
end
end
model = DelegateTestModel.new uploader = DelegateTestUploader.new(model, :image) file = File.open('test.jpg')
uploader.store!(file)
model.processed # true model.a_processed # true model.a_b_processed # true
model.a_processed = false
uploader.processed # true uploader.a_processed # false uploader.a_b_processed # true
When model is mounted to uploader:
Otherwise acts as regular uploader's instance variables.
This is very useful for:
= Integrating CarrierWave with JCrop
Let implement the behavior like at this demo: http://deepliquid.com/projects/Jcrop/demos.php?demo=thumbnail
The uploader:
class CropUploader < SobakaUploader include CarrierWave::Meta
# Crop source is a source image converted from original which could be bigger than source area (left image in the example).
version :crop_source do
process :resize_to_fit => [300, 300]
process :store_meta
# This is the cropped version of parent image. Let crop to 50x50 square.
version :crop do
process :crop_to => [50, 50]
end
end
# Defines crop area dimensions.
# This should be assigned before #store! and #cache! called and should be saved in the model's instance.
# Otherwise cropped image would be lost after #recreate_versions! is called.
# If crop area dimensions are'nt assigned, uploader calculates crop area dimensions inside the
# parent image and creates the default image.
model_delegate_attribute :x
model_delegate_attribute :y
model_delegate_attribute :w
model_delegate_attribute :h
# Crop processor
def crop_to(width, height)
# Checks that crop area is defined and crop should be done.
if ((crop_args[0] == crop_args[2]) || (crop_args[1] == crop_args[3]))
# If not creates default image and saves it's dimensions.
resize_to_fill_and_save_dimensions(width, height)
else
args = crop_args + [width, height]
crop_and_resize(*args)
end
end
def crop_and_resize(x, y, width, height, new_width, new_height)
manipulate! do |img|
cropped_img = img.crop(x, y, width, height)
new_img = cropped_img.resize_to_fill(new_width, new_height)
destroy_image(cropped_img)
destroy_image(img)
new_img
end
end
# Creates the default crop image.
# Here the original crop area dimensions are restored and assigned to the model's instance.
def resize_to_fill_and_save_dimensions(new_width, new_height)
manipulate! do |img|
width, height = img.columns, img.rows
new_img = img.resize_to_fill(new_width, new_height)
destroy_image(img)
w_ratio = width.to_f / new_width.to_f
h_ratio = height.to_f / new_height.to_f
ratio = [w_ratio, h_ratio].min
self.w = ratio * new_width
self.h = ratio * new_height
self.x = (width - self.w) / 2
self.y = (height - self.h) / 2
new_img
end
end
private
def crop_args
%w(x y w h).map { |accessor| send(accessor).to_i }
end
end
class Post < ActiveRecord::Base mount_uploader CropUploader, :image end
post = Post.new post.image = params[:image] # Let the uploaded file is 800x600 JPEG post.save!
post.image.crop_source.width # 300 post.image.crop_source.height # 200 post.image.crop_source.crop.width # 50 post.image.crop_source.crop.height # 50
post.image.crop_source.crop.x # 50 post.image.crop_source.crop.y # 50 post.image.crop_source.crop.w # 200 post.image.crop_source.crop.h # 200
post.crop_source_crop_x = 100 post.crop_source_crop_y = 100 post.crop_source_crop_w = 100 post.crop_source_crop_h = 100
post.save! # Crop image is reprocessed
post.image.crop_source.crop.width # 50 post.image.crop_source.crop.height # 50
= PDF/GhostScript support
If you want to use this plugin with PDF/PostScript files than you should install GhostScript and rebuild ImageMagick with GhostScript support:
brew install ghostscript brew install imagemagick --with-ghostscript gem uninstall rmagick && gem install rmagick
To switch on PDF/EPS processing you should enable GhostScript somewhere in your app's initializer:
CarrierWave::Meta.ghostscript_enabled = true
= A note about testing
@SergeyKishenin added specs for EPS/GhostScript files. They run for image_magick or mini_magick processor by default. To make specs work please install GhostScript as described above. To run specs WITHOUT PDF/EPS do:
PDF_EPS=false bundle exec rspec
@fschwahn added support for mini-magick. To run tests with mini-magick do:
PROCESSOR=mini_magick bundle exec rspec
@skord added support for ImageSorcery. To run specs do:
PROCESSOR=image_sorcery bundle exec rspec
To run specs against VIPS processor do:
PROCESSOR=vips bundle exec rspec
To run specs against with Fog (Amazon S3) simulation:
STORAGE=fog bundle exec rspec
= TODO