-
Notifications
You must be signed in to change notification settings - Fork 105
/
image.js
201 lines (151 loc) · 5.37 KB
/
image.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
'use strict';
var _, Logger, env, modifiers, stream, util, imgType;
_ = require('lodash');
Logger = require('./utils/logger');
env = require('./config/environment_vars');
modifiers = require('./lib/modifiers');
stream = require('stream');
util = require('util');
imgType = require('image-type');
// Simple stream to represent an error at an early stage, for instance a
// request to an excluded source.
function ErrorStream(image){
stream.Readable.call(this, { objectMode : true });
this.image = image;
}
util.inherits(ErrorStream, stream.Readable);
ErrorStream.prototype._read = function(){
this.push(this.image);
this.push(null);
};
function Image(request){
// placeholder for any error objects
this.error = null;
// set a mark for the start of the process
this.mark = Date.now();
// determine the name and format (mime) of the requested image
this.parseImage(request);
// determine the requested modifications
this.modifiers = modifiers.parse(request.path);
// pull the various parts needed from the request params
this.parseUrl(request);
// placeholder for the buffer/stream coming from s3, will hold the image
this.contents = null;
// placeholder for the size of the original image
this.originalContentLength = 0;
// set the default expiry length, can be altered by a source file
this.expiry = env.IMAGE_EXPIRY;
// all logging strings will be queued here to be written on response
this.log = new Logger();
}
Image.validInputFormats = ['jpeg', 'jpg', 'gif', 'png', 'webp'];
Image.validOutputFormats = ['jpeg', 'png', 'webp'];
// Determine the name and format of the requested image
Image.prototype.parseImage = function(request){
var fileStr = _.last(request.path.split('/'));
var exts = fileStr.split('.').map( function (item) {
return item.toLowerCase();
});
// clean out any metadata format
if (exts[exts.length - 1] === 'json') {
this.format = exts[exts.length - 2];
exts.pop();
fileStr = exts.join('.');
}
// if path contains valid output format, remove it from path
if (exts.length >= 3) {
var inputFormat = exts[exts.length - 2];
var outputFormat = exts.pop();
if (_.indexOf(Image.validInputFormats, inputFormat) > -1 &&
_.indexOf(Image.validOutputFormats, outputFormat) > -1) {
this.outputFormat = outputFormat;
fileStr = exts.join('.');
}
}
this.image = fileStr;
};
// Determine the file path for the requested image
Image.prototype.parseUrl = function(request){
var parts = request.path.replace(/^\//,'').split('/');
// overwrite the image name with the parsed version so metadata requests do
// not mess things up
parts[parts.length - 1] = this.image;
// if there is a modifier string remove it
if (this.modifiers.hasModStr) {
parts.shift();
}
this.path = parts.join('/');
// account for any spaces in the path
this.path = decodeURI(this.path);
};
Image.prototype.isError = function(){ return this.error !== null; };
Image.prototype.isStream = function(){
var Stream = require('stream').Stream;
return !!this.contents && this.contents instanceof Stream;
};
Image.prototype.isBuffer = function(){
return !!this.contents && Buffer.isBuffer(this.contents);
};
Image.prototype.getFile = function(){
var sources = require('./streams/sources'),
excludes = env.EXCLUDE_SOURCES ? env.EXCLUDE_SOURCES.split(',') : [],
streamType = env.DEFAULT_SOURCE,
Stream = null;
// look to see if the request has a specified source
if (_.has(this.modifiers, 'external')){
if (_.has(sources, this.modifiers.external)){
streamType = this.modifiers.external;
} else if (_.has(env.externalSources, this.modifiers.external)) {
Stream = sources.external;
return new Stream(this, this.modifiers.external, env.externalSources[this.modifiers.external]);
}
}
// if this request is for an excluded source create an ErrorStream
if (excludes.indexOf(streamType) > -1){
this.error = new Error(streamType + ' is an excluded source');
Stream = ErrorStream;
}
// if all is well find the appropriate stream
else {
this.log.log('new stream created!');
Stream = sources[streamType];
}
return new Stream(this);
};
Image.prototype.sizeReduction = function(){
var size = this.contents.length;
return (this.originalContentLength - size)/1000;
};
Image.prototype.sizeSaving = function(){
var oCnt = this.originalContentLength,
size = this.contents.length;
return ((oCnt - size)/oCnt * 100).toFixed(2);
};
Image.prototype.isFormatValid = function () {
if (Image.validInputFormats.indexOf(this.format) === -1) {
this.error = new Error(
'The listed format (' + this.format + ') is not valid.'
);
}
};
// Setter/getter for image format that normalizes jpeg formats
Object.defineProperty(Image.prototype, 'format', {
get: function () { return this._format; },
set: function (value) {
this._format = value.toLowerCase();
if (this._format === 'jpg') { this._format = 'jpeg'; }
}
});
// Setter/getter for image contents that determines the format from the content
// of the image to be processed.
Object.defineProperty(Image.prototype, 'contents', {
get: function () { return this._contents; },
set: function (data) {
this._contents = data;
if (this.isBuffer()) {
this.format = imgType(data).ext;
this.isFormatValid();
}
}
});
module.exports = Image;