UNPKG

6.77 kBJavaScriptView Raw
1/**
2 * Extend proto.
3 */
4
5module.exports = function (gm) {
6
7 var proto = gm.prototype;
8
9 /**
10 * `identify` states
11 */
12
13 const IDENTIFYING = 1;
14 const IDENTIFIED = 2;
15
16 /**
17 * Map getter functions to output names.
18 *
19 * - format: specifying the -format argument (see man gm)
20 * - verbose: use -verbose instead of -format (only if necessary b/c its slow)
21 * - helper: use the conversion helper
22 */
23
24 var map = {
25 'format': { key: 'format', format: '%m ', helper: 'Format' }
26 , 'depth': { key: 'depth', format: '%q' }
27 , 'filesize': { key: 'Filesize', format: '%b' }
28 , 'size': { key: 'size', format: '%wx%h ', helper: 'Geometry' }
29 , 'color': { key: 'color', format: '%k', helper: 'Colors' }
30 , 'orientation': { key: 'Orientation', verbose: true }
31 , 'res': { key: 'Resolution', verbose: true }
32 }
33
34 /**
35 * Getter functions
36 */
37
38 Object.keys(map).forEach(function (getter) {
39 proto[getter] = function (opts, callback) {
40 if (!callback) callback = opts, opts = {};
41 if (!callback) return this;
42
43 var val = map[getter]
44 , key = val.key
45 , self = this;
46
47 if (self.data[key]) {
48 callback.call(self, null, self.data[key]);
49 return self;
50 }
51
52 self.on(getter, callback);
53
54 self.bufferStream = !!opts.bufferStream;
55
56 if (val.verbose) {
57 self.identify(opts, function (err, stdout, stderr, cmd) {
58 if (err) {
59 self.emit(getter, err, self.data[key], stdout, stderr, cmd);
60 } else {
61 self.emit(getter, err, self.data[key]);
62 }
63 });
64 return self;
65 }
66
67 var args = makeArgs(self, val);
68 self._exec(args, function (err, stdout, stderr, cmd) {
69 if (err) {
70 self.emit(getter, err, self.data[key], stdout, stderr, cmd);
71 return;
72 }
73
74 var result = (stdout||'').trim();
75
76 if (val.helper in helper) {
77 helper[val.helper](self.data, result);
78 } else {
79 self.data[key] = result;
80 }
81
82 self.emit(getter, err, self.data[key]);
83 });
84
85 return self;
86 }
87 });
88
89 /**
90 * identify command
91 *
92 * Overwrites all internal data with the parsed output
93 * which is more accurate than the fast shortcut
94 * getters.
95 */
96
97 proto.identify = function identify (opts, callback) {
98 // identify with pattern
99 if (typeof(opts) === 'string') {
100 opts = {
101 format: opts
102 }
103 }
104 if (!callback) callback = opts, opts = {};
105 if (!callback) return this;
106 if (opts && opts.format) return identifyPattern.call(this, opts, callback);
107
108 var self = this;
109
110 if (IDENTIFIED === self._identifyState) {
111 callback.call(self, null, self.data);
112 return self;
113 }
114
115 self.on('identify', callback);
116
117 if (IDENTIFYING === self._identifyState) {
118 return self;
119 }
120
121 self._identifyState = IDENTIFYING;
122
123 self.bufferStream = !!opts.bufferStream;
124
125 var args = makeArgs(self, { verbose: true });
126
127 self._exec(args, function (err, stdout, stderr, cmd) {
128 if (err) {
129 self.emit('identify', err, self.data, stdout, stderr, cmd);
130 return;
131 }
132
133 err = parse(stdout, self);
134
135 if (err) {
136 self.emit('identify', err, self.data, stdout, stderr, cmd);
137 return;
138 }
139
140 self.data.path = self.source;
141
142 self.emit('identify', null, self.data);
143 self._identifyState = IDENTIFIED;
144 });
145
146 return self;
147 }
148
149
150 /**
151 * identify with pattern
152 *
153 * Execute `identify -format` with custom pattern
154 */
155
156 function identifyPattern (opts, callback) {
157 var self = this;
158
159 self.bufferStream = !!opts.bufferStream;
160
161 var args = makeArgs(self, opts);
162 self._exec(args, function (err, stdout, stderr, cmd) {
163 if (err) {
164 return callback.call(self, err, undefined, stdout, stderr, cmd);
165 }
166
167 callback.call(self, err, (stdout||'').trim());
168 });
169
170 return self;
171 }
172
173
174 /**
175 * Parses `identify` responses.
176 *
177 * @param {String} stdout
178 * @param {Gm} self
179 * @return {Error} [optionally]
180 */
181
182 function parse (stdout, self) {
183 // normalize
184 var parts = (stdout||"").trim().replace(/\r\n|\r/g, "\n").split("\n");
185
186 // skip the first line (its just the filename)
187 parts.shift();
188
189 try {
190 var len = parts.length
191 , rgx1 = /^( *)(.+?): (.*)$/ // key: val
192 , rgx2 = /^( *)(.+?):$/ // key: begin nested object
193 , out = { indent: {} }
194 , level = null
195 , lastkey
196 , i = 0
197 , res
198 , o
199
200 for (; i < len; ++i) {
201 res = rgx1.exec(parts[i]) || rgx2.exec(parts[i]);
202 if (!res) continue;
203
204 var indent = res[1].length
205 , key = res[2] ? res[2].trim() : '';
206
207 if ('Image' == key) continue;
208
209 var val = res[3] ? res[3].trim() : null;
210
211 // first iteration?
212 if (null === level) {
213 level = indent;
214 o = out.root = out.indent[level] = self.data;
215 } else if (indent < level) {
216 // outdent
217 if (!(indent in out.indent)) {
218 continue;
219 }
220 o = out.indent[indent];
221 } else if (indent > level) {
222 // dropping into a nested object
223 out.indent[level] = o;
224 // wierd format, key/val pair with nested children. discard the val
225 o = o[lastkey] = {};
226 }
227
228 level = indent;
229
230 if (val) {
231 o[key] = val;
232
233 if (key in helper) {
234 helper[key](o, val);
235 }
236 }
237
238 lastkey = key;
239 }
240
241 } catch (err) {
242 err.message = err.message + "\n\n Identify stdout:\n " + stdout;
243 return err;
244 }
245 }
246
247 /**
248 * Create an argument array for the identify command.
249 *
250 * @param {gm} self
251 * @param {Object} val
252 * @return {Array}
253 */
254
255 function makeArgs (self, val) {
256 var args = [
257 'identify'
258 , '-ping'
259 ];
260
261 if (val.format) {
262 args.push('-format', val.format);
263 }
264
265 if (val.verbose) {
266 args.push('-verbose');
267 }
268
269 args = args.concat(self.src());
270 return args;
271 }
272
273 /**
274 * identify -verbose helpers
275 */
276
277 var helper = gm.identifyHelpers = {};
278
279 helper.Geometry = function Geometry (o, val) {
280 // We only want the size of the first frame.
281 // Each frame is separated by a space.
282 var split = val.split(" ").shift().split("x");
283 o.size = {
284 width: parseInt(split[0], 10)
285 , height: parseInt(split[1], 10)
286 }
287 };
288
289 helper.Format = function Format (o, val) {
290 o.format = val.split(" ")[0];
291 };
292
293 helper.Depth = function Depth (o, val) {
294 o.depth = parseInt(val, 10);
295 };
296
297 helper.Colors = function Colors (o, val) {
298 o.color = parseInt(val, 10);
299 };
300}