getters.js 6.6 KB

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