corrade-nucleus-nucleons – Blame information for rev 36
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
36 | office | 1 | /** |
2 | * Extend proto. |
||
3 | */ |
||
4 | |||
5 | module.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', format: '%[EXIF:Orientation]', helper: 'Orientation' } |
||
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 || 'Warning' == 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 | // weird format, key/val pair with nested children. discard the val |
||
225 | o = o[lastkey] = {}; |
||
226 | } |
||
227 | |||
228 | level = indent; |
||
229 | |||
230 | if (val) { |
||
231 | // if previous key was exist and we got the same key |
||
232 | // cast it to an array. |
||
233 | if(o.hasOwnProperty(key)){ |
||
234 | // cast it to an array and dont forget the previous value |
||
235 | if(!Array.isArray(o[key])){ |
||
236 | var tmp = o[key]; |
||
237 | o[key] = [tmp]; |
||
238 | } |
||
239 | |||
240 | // set value |
||
241 | o[key].push(val); |
||
242 | } else { |
||
243 | o[key] = val; |
||
244 | } |
||
245 | |||
246 | if (key in helper) { |
||
247 | helper[key](o, val); |
||
248 | } |
||
249 | } |
||
250 | |||
251 | lastkey = key; |
||
252 | } |
||
253 | |||
254 | } catch (err) { |
||
255 | err.message = err.message + "\n\n Identify stdout:\n " + stdout; |
||
256 | return err; |
||
257 | } |
||
258 | } |
||
259 | |||
260 | /** |
||
261 | * Create an argument array for the identify command. |
||
262 | * |
||
263 | * @param {gm} self |
||
264 | * @param {Object} val |
||
265 | * @return {Array} |
||
266 | */ |
||
267 | |||
268 | function makeArgs (self, val) { |
||
269 | var args = [ |
||
270 | 'identify' |
||
271 | , '-ping' |
||
272 | ]; |
||
273 | |||
274 | if (val.format) { |
||
275 | args.push('-format', val.format); |
||
276 | } |
||
277 | |||
278 | if (val.verbose) { |
||
279 | args.push('-verbose'); |
||
280 | } |
||
281 | |||
282 | args = args.concat(self.src()); |
||
283 | return args; |
||
284 | } |
||
285 | |||
286 | /** |
||
287 | * Map exif orientation codes to orientation names. |
||
288 | */ |
||
289 | |||
290 | var orientations = { |
||
291 | '1': 'TopLeft' |
||
292 | , '2': 'TopRight' |
||
293 | , '3': 'BottomRight' |
||
294 | , '4': 'BottomLeft' |
||
295 | , '5': 'LeftTop' |
||
296 | , '6': 'RightTop' |
||
297 | , '7': 'RightBottom' |
||
298 | , '8': 'LeftBottom' |
||
299 | } |
||
300 | |||
301 | /** |
||
302 | * identify -verbose helpers |
||
303 | */ |
||
304 | |||
305 | var helper = gm.identifyHelpers = {}; |
||
306 | |||
307 | helper.Geometry = function Geometry (o, val) { |
||
308 | // We only want the size of the first frame. |
||
309 | // Each frame is separated by a space. |
||
310 | var split = val.split(" ").shift().split("x"); |
||
311 | var width = parseInt(split[0], 10); |
||
312 | var height = parseInt(split[1], 10); |
||
313 | if (o.size && o.size.width && o.size.height) { |
||
314 | if (width > o.size.width) o.size.width = width; |
||
315 | if (height > o.size.height) o.size.height = height; |
||
316 | } else { |
||
317 | o.size = { |
||
318 | width: width, |
||
319 | height: height |
||
320 | } |
||
321 | } |
||
322 | }; |
||
323 | |||
324 | helper.Format = function Format (o, val) { |
||
325 | o.format = val.split(" ")[0]; |
||
326 | }; |
||
327 | |||
328 | helper.Depth = function Depth (o, val) { |
||
329 | o.depth = parseInt(val, 10); |
||
330 | }; |
||
331 | |||
332 | helper.Colors = function Colors (o, val) { |
||
333 | o.color = parseInt(val, 10); |
||
334 | }; |
||
335 | |||
336 | helper.Orientation = function Orientation (o, val) { |
||
337 | if (val in orientations) { |
||
338 | o['Profile-EXIF'] || (o['Profile-EXIF'] = {}); |
||
339 | o['Profile-EXIF'].Orientation = val; |
||
340 | o.Orientation = orientations[val]; |
||
341 | } else { |
||
342 | o.Orientation = val || 'Unknown'; |
||
343 | } |
||
344 | }; |
||
345 | } |
||
346 |