Skip to content

Instantly share code, notes, and snippets.

@gotvitch
Last active September 11, 2015 21:21
Show Gist options
  • Save gotvitch/4c9f918e836d6c6b16f0 to your computer and use it in GitHub Desktop.
Save gotvitch/4c9f918e836d6c6b16f0 to your computer and use it in GitHub Desktop.
# Save a image
image_options =
styles:
# Will resize to 250x250 by cropping in the center
# 2000x1000 => 250x250
small:
size: '250x250'
resize: '250x250^'
gravity: 'Center'
crop: '250x250'
# Will resize without crop, keep the ratio, max height and width will be 800
# The `>` is to not enlarge the image
# 2000x1000 => 800x400
medium:
size: '800x800'
resize: '800x800>'
# Will resize without crop, keep the ratio, max height and width will be 800
# The `>` is to not enlarge the image
# 2000x1000 => 1600x800
normal:
size: '1600x1600'
resize: '1600x1600>'
original: {}
_storage.saveImage user_object_id.toString(), file_path, image_options, ( err, image ) ->
return callback err if err?
doc =
user_id: user_object_id
created_at: new Date()
file_id: image.id
_.extend photo_doc, _.pick image, [
'ext'
'format'
'height'
'orientation'
'width'
]
# Save in mongo
# Set the url in fromDocument or toJSON
photo.urls = _storage.getImageUrls photo.user_id, photo.file_id, photo.ext, Photo.styles
debug = (require 'debug') 'helpers:storage'
_ = require 'lodash'
async = require 'async'
aws = require 'aws-sdk'
gm = require 'gm'
moment = require 'moment-timezone'
path = require 'path'
module.exports = ( _config, _geo, _utils ) ->
debug "load"
s3Client = new aws.S3
accessKeyId: _config.aws.access_key
secretAccessKey: _config.aws.secret_key
# See: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html#constructor-property
_storage =
# @todo Parse EXIF info
# @method saveImage
# @description
#
# @param {String} file_path:
# @param {Object} options:
# @param {Function(err, image)} callback:
# - {Error} err: Error in case of failure
# - {Object} image: Image information with the following format:
# {
# id: String,
# format: String,
# taken_at: String,
# orientation: String,
# iso: String,
# aperture: String,
# shutter_speed: String,
# focal_length: String,
# camera: String
# }
#
saveImage: ( user_id, file_path, options, callback ) ->
options ?= {}
keys = _.keys( options.styles )
gm( file_path ).identify '%m\n%wx%h\n%[EXIF:*]', ( err, info ) ->
return callback err if err?
lines = info.split '\n'
[ format, size ] = lines
size = size.split 'x'
size =
width: parseInt size[ 0 ]
height: parseInt size[ 1 ]
exif_info = {}
for i in [2...lines.length] by 1
line = lines[ i ]
line_split = line.split '='
exif_info[ line_split[ 0 ] ] = line_split[ 1 ]
image =
# Generate a unique file ID
id: _utils.generateId()
format: format.toLowerCase()
ext: path.extname( file_path ).toLowerCase().substring( 1 )
# By default use the size detect by Graphick Magic
# But try to get the size from exif for orientation detection
width: size.width
height: size.height
# See http://owl.phy.queensu.ca/~phil/exiftool/TagNames/EXIF.html
if !_.isEmpty( exif_info )
fields =
taken_at: 'DateTimeOriginal'
orientation: 'Orientation'
iso: 'ISOSpeedRatings'
aperture: 'FNumber'
shutter_speed: 'ExposureTime'
focal_length: 'FocalLength'
_utils.copyFields exif_info, image, fields
if exif_info['ExifImageWidth']? && exif_info['ExifImageLength']?
image.width = parseInt exif_info['ExifImageWidth']
image.height = parseInt exif_info['ExifImageLength']
# Switch the width and height
if exif_info['Orientation'] in [ '6', '8' ]
height = image.width
image.width = image.height
image.height = height
async.each(
_.keys( options.styles )
, ( style_name, callback ) ->
style = options.styles[ style_name ]
gmObject = gm file_path
gmObject.out '-auto-orient'
_.each style, (value, key) ->
if key == 'size'
gmObject.in "-#{ key }", value
else
gmObject.out "-#{ key }", value
folder = "#{ user_id }/"
filename = "#{ folder }#{ image.id }_#{ style_name }.#{ image.ext }"
gmObject.stream ( err, stdout, stderr ) ->
return callback err if err?
object =
Bucket: _config.aws.bucket
Key: filename
Body: stdout
ACL: 'public-read'
ContentType: "image/#{ image.format }"
# see http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property
s3Client.upload object, ( err, data ) ->
return callback err if err?
return callback null
, ( err ) ->
return callback null, image
)
deleteImage: ( user_id, file_id, ext, styles, callback ) ->
folder = "#{ user_id }/"
params =
Bucket: _config.aws.bucket
Delete:
Objects: _.map _.keys( styles ), ( style_name ) -> return { Key: "#{ folder }#{ file_id }_#{ style_name }.#{ ext }" }
# see http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property
s3Client.deleteObjects params, ( err, data ) ->
return callback err if err?
debug "data", data
return callback null
deleteImages: ( user_id, files, styles, callback ) ->
folder = "#{ user_id }/"
objects = []
for file in files
for style_name, style of styles
objects.push( { Key: "#{ folder }#{ file.id }_#{ style_name }.#{ file.ext }" } )
chunked_objects = _.chunk( objects, 300 )
async.eachSeries(
chunked_objects
, ( objects ) ->
params =
Bucket: _config.aws.bucket
Delete:
Objects: objects
# see http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property
s3Client.deleteObjects params, ( err, data ) ->
return callback err if err?
debug "deleteObjects result", data
return callback null
, callback
)
getImageUrls: ( user_id, file_id, ext, styles ) ->
urls = {}
for style_name in _.keys styles
urls[ style_name ] = "#{ _config.aws.cdn }/#{ user_id }/#{ file_id }_#{ style_name }.#{ ext }"
return urls
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment