123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- /**
- * Extend proto.
- */
- module.exports = function (gm) {
- var proto = gm.prototype;
- /**
- * `identify` states
- */
- const IDENTIFYING = 1;
- const IDENTIFIED = 2;
- /**
- * Map getter functions to output names.
- *
- * - format: specifying the -format argument (see man gm)
- * - verbose: use -verbose instead of -format (only if necessary b/c its slow)
- * - helper: use the conversion helper
- */
- var map = {
- 'format': { key: 'format', format: '%m ', helper: 'Format' }
- , 'depth': { key: 'depth', format: '%q' }
- , 'filesize': { key: 'Filesize', format: '%b' }
- , 'size': { key: 'size', format: '%wx%h ', helper: 'Geometry' }
- , 'color': { key: 'color', format: '%k', helper: 'Colors' }
- , 'orientation': { key: 'Orientation', verbose: true }
- , 'res': { key: 'Resolution', verbose: true }
- }
- /**
- * Getter functions
- */
- Object.keys(map).forEach(function (getter) {
- proto[getter] = function (opts, callback) {
- if (!callback) callback = opts, opts = {};
- if (!callback) return this;
- var val = map[getter]
- , key = val.key
- , self = this;
- if (self.data[key]) {
- callback.call(self, null, self.data[key]);
- return self;
- }
- self.on(getter, callback);
- self.bufferStream = !!opts.bufferStream;
- if (val.verbose) {
- self.identify(opts, function (err, stdout, stderr, cmd) {
- if (err) {
- self.emit(getter, err, self.data[key], stdout, stderr, cmd);
- } else {
- self.emit(getter, err, self.data[key]);
- }
- });
- return self;
- }
- var args = makeArgs(self, val);
- self._exec(args, function (err, stdout, stderr, cmd) {
- if (err) {
- self.emit(getter, err, self.data[key], stdout, stderr, cmd);
- return;
- }
- var result = (stdout||'').trim();
- if (val.helper in helper) {
- helper[val.helper](self.data, result);
- } else {
- self.data[key] = result;
- }
- self.emit(getter, err, self.data[key]);
- });
- return self;
- }
- });
- /**
- * identify command
- *
- * Overwrites all internal data with the parsed output
- * which is more accurate than the fast shortcut
- * getters.
- */
- proto.identify = function identify (opts, callback) {
- // identify with pattern
- if (typeof(opts) === 'string') {
- opts = {
- format: opts
- }
- }
- if (!callback) callback = opts, opts = {};
- if (!callback) return this;
- if (opts && opts.format) return identifyPattern.call(this, opts, callback);
- var self = this;
- if (IDENTIFIED === self._identifyState) {
- callback.call(self, null, self.data);
- return self;
- }
- self.on('identify', callback);
- if (IDENTIFYING === self._identifyState) {
- return self;
- }
- self._identifyState = IDENTIFYING;
- self.bufferStream = !!opts.bufferStream;
- var args = makeArgs(self, { verbose: true });
- self._exec(args, function (err, stdout, stderr, cmd) {
- if (err) {
- self.emit('identify', err, self.data, stdout, stderr, cmd);
- return;
- }
- err = parse(stdout, self);
- if (err) {
- self.emit('identify', err, self.data, stdout, stderr, cmd);
- return;
- }
- self.data.path = self.source;
- self.emit('identify', null, self.data);
- self._identifyState = IDENTIFIED;
- });
- return self;
- }
- /**
- * identify with pattern
- *
- * Execute `identify -format` with custom pattern
- */
- function identifyPattern (opts, callback) {
- var self = this;
- self.bufferStream = !!opts.bufferStream;
- var args = makeArgs(self, opts);
- self._exec(args, function (err, stdout, stderr, cmd) {
- if (err) {
- return callback.call(self, err, undefined, stdout, stderr, cmd);
- }
- callback.call(self, err, (stdout||'').trim());
- });
- return self;
- }
- /**
- * Parses `identify` responses.
- *
- * @param {String} stdout
- * @param {Gm} self
- * @return {Error} [optionally]
- */
- function parse (stdout, self) {
- // normalize
- var parts = (stdout||"").trim().replace(/\r\n|\r/g, "\n").split("\n");
- // skip the first line (its just the filename)
- parts.shift();
- try {
- var len = parts.length
- , rgx1 = /^( *)(.+?): (.*)$/ // key: val
- , rgx2 = /^( *)(.+?):$/ // key: begin nested object
- , out = { indent: {} }
- , level = null
- , lastkey
- , i = 0
- , res
- , o
- for (; i < len; ++i) {
- res = rgx1.exec(parts[i]) || rgx2.exec(parts[i]);
- if (!res) continue;
- var indent = res[1].length
- , key = res[2] ? res[2].trim() : '';
- if ('Image' == key) continue;
- var val = res[3] ? res[3].trim() : null;
- // first iteration?
- if (null === level) {
- level = indent;
- o = out.root = out.indent[level] = self.data;
- } else if (indent < level) {
- // outdent
- if (!(indent in out.indent)) {
- continue;
- }
- o = out.indent[indent];
- } else if (indent > level) {
- // dropping into a nested object
- out.indent[level] = o;
- // wierd format, key/val pair with nested children. discard the val
- o = o[lastkey] = {};
- }
- level = indent;
- if (val) {
- o[key] = val;
- if (key in helper) {
- helper[key](o, val);
- }
- }
- lastkey = key;
- }
- } catch (err) {
- err.message = err.message + "\n\n Identify stdout:\n " + stdout;
- return err;
- }
- }
- /**
- * Create an argument array for the identify command.
- *
- * @param {gm} self
- * @param {Object} val
- * @return {Array}
- */
- function makeArgs (self, val) {
- var args = [
- 'identify'
- , '-ping'
- ];
- if (val.format) {
- args.push('-format', val.format);
- }
- if (val.verbose) {
- args.push('-verbose');
- }
- args = args.concat(self.src());
- return args;
- }
- /**
- * identify -verbose helpers
- */
- var helper = gm.identifyHelpers = {};
- helper.Geometry = function Geometry (o, val) {
- // We only want the size of the first frame.
- // Each frame is separated by a space.
- var split = val.split(" ").shift().split("x");
- o.size = {
- width: parseInt(split[0], 10)
- , height: parseInt(split[1], 10)
- }
- };
- helper.Format = function Format (o, val) {
- o.format = val.split(" ")[0];
- };
- helper.Depth = function Depth (o, val) {
- o.depth = parseInt(val, 10);
- };
- helper.Colors = function Colors (o, val) {
- o.color = parseInt(val, 10);
- };
- }
|