123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- /**
- * Module dependencies.
- */
- var exec = require('child_process').exec;
- var spawn = require('child_process').spawn;
- var utils = require('./utils');
- var debug = require('debug')('gm');
- var series = require('array-series');
- var streamToBuffer = require('stream-to-buffer');
- /*
- * Creates a pass through stream.
- * We need to fallback to the `through` lib for node 0.8 support
- * as PassThrough was added in node 0.10.
- */
- var PassThrough = require('stream').PassThrough || require('through');
- /**
- * Error messaging.
- */
- var noBufferConcat = 'gm v1.9.0+ required node v0.8+. Please update your version of node, downgrade gm < 1.9, or do not use `bufferStream`.';
- /**
- * Extend proto
- */
- module.exports = function (proto) {
- function args (prop) {
- return function args () {
- var len = arguments.length;
- var a = [];
- var i = 0;
- for (; i < len; ++i) {
- a.push(arguments[i]);
- }
- this[prop] = this[prop].concat(a);
- return this;
- }
- }
- proto.in = args('_in');
- proto.out = args('_out');
- proto._preprocessor = [];
- proto.preprocessor = args('_preprocessor');
- /**
- * Execute the command and write the image to the specified file name.
- *
- * @param {String} name
- * @param {Function} callback
- * @return {Object} gm
- */
- proto.write = function write (name, callback) {
- if (!callback) callback = name, name = null;
- if ("function" !== typeof callback) {
- throw new TypeError("gm().write() expects a callback function")
- }
- if (!name) {
- return callback(TypeError("gm().write() expects a filename when writing new files"));
- }
- this.outname = name;
- var self = this;
- this._preprocess(function (err) {
- if (err) return callback(err);
- self._spawn(self.args(), true, callback);
- });
- }
- /**
- * Execute the command and return stdin and stderr
- * ReadableStreams providing the image data.
- * If no callback is passed, a "through" stream will be returned,
- * and stdout will be piped through, otherwise the error will be passed.
- *
- * @param {String} format (optional)
- * @param {Function} callback (optional)
- * @return {Stream}
- */
- proto.stream = function stream (format, callback) {
- if (!callback && typeof format === 'function') {
- callback = format;
- format = null;
- }
- var throughStream;
- if ("function" !== typeof callback) {
- throughStream = new PassThrough();
- callback = function (err, stdout, stderr) {
- if (err) throughStream.emit('error', err);
- else stdout.pipe(throughStream);
- }
- }
- if (format) {
- format = format.split('.').pop();
- this.outname = format + ":-";
- }
- var self = this;
- this._preprocess(function (err) {
- if (err) return callback(err);
- return self._spawn(self.args(), false, callback);
- });
- return throughStream || this;
- }
- /**
- * Convenience function for `proto.stream`.
- * Simply returns the buffer instead of the stream.
- *
- * @param {String} format (optional)
- * @param {Function} callback
- * @return {null}
- */
- proto.toBuffer = function toBuffer (format, callback) {
- if (!callback) callback = format, format = null;
- if ("function" !== typeof callback) {
- throw new Error('gm().toBuffer() expects a callback.');
- }
- return this.stream(format, function (err, stdout) {
- if (err) return callback(err);
- streamToBuffer(stdout, callback);
- })
- }
- /**
- * Run any preProcessor functions in series. Used by autoOrient.
- *
- * @param {Function} callback
- * @return {Object} gm
- */
- proto._preprocess = function _preprocess (callback) {
- series(this._preprocessor, this, callback);
- }
- /**
- * Execute the command, buffer input and output, return stdout and stderr buffers.
- *
- * @param {String} bin
- * @param {Array} args
- * @param {Function} callback
- * @return {Object} gm
- */
- proto._exec = function _exec (args, callback) {
- return this._spawn(args, true, callback);
- }
- /**
- * Execute the command with stdin, returning stdout and stderr streams or buffers.
- * @param {String} bin
- * @param {Array} args
- * @param {ReadableStream} stream
- * @param {Boolean} shouldBuffer
- * @param {Function} callback
- * @return {Object} gm
- * @TODO refactor this mess
- */
- proto._spawn = function _spawn (args, bufferOutput, callback) {
- var appPath = this._options.appPath || '';
- var bin = this._options.imageMagick
- ? appPath + args.shift()
- : 'gm'
- var proc = spawn(bin, args)
- , cmd = bin + ' ' + args.map(utils.escape).join(' ')
- , self = this
- , err;
- debug(cmd);
- if (self.sourceBuffer) {
- proc.stdin.write(this.sourceBuffer);
- proc.stdin.end();
- } else if (self.sourceStream) {
- if (!self.sourceStream.readable) {
- err = new Error("gm().stream() or gm().write() with a non-readable stream.");
- return cb(err);
- }
- self.sourceStream.pipe(proc.stdin);
- // bufferStream
- // We convert the input source from a stream to a buffer.
- if (self.bufferStream && !this._buffering) {
- if (!Buffer.concat) {
- throw new Error(noBufferConcat);
- }
- // Incase there are multiple processes in parallel,
- // we only need one
- self._buffering = true;
- streamToBuffer(self.sourceStream, function (err, buffer) {
- self.sourceBuffer = buffer;
- self.sourceStream = null; // The stream is now dead
- })
- }
- }
- // for _exec operations (identify() mostly), we also
- // need to buffer the output stream before returning
- if (bufferOutput) {
- var stdout = ''
- , stderr = ''
- , onOut
- , onErr
- , onExit
- proc.stdout.on('data', onOut = function (data) {
- stdout += data;
- });
- proc.stderr.on('data', onErr = function (data) {
- stderr += data;
- });
- proc.on('close', onExit = function (code, signal) {
- if (code !== 0 || signal !== null) {
- err = new Error('Command failed: ' + stderr);
- err.code = code;
- err.signal = signal;
- };
- cb(err, stdout, stderr, cmd);
- stdout = stderr = onOut = onErr = onExit = null;
- });
- proc.on('error', cb);
- } else {
- cb(null, proc.stdout, proc.stderr, cmd);
- }
- return self;
- function cb (err, stdout, stderr, cmd) {
- if (cb.called) return;
- cb.called = 1;
- if (args[0] !== 'identify' && bin !== 'identify') {
- self._in = [];
- self._out = [];
- }
- callback.call(self, err, stdout, stderr, cmd);
- }
- }
- /**
- * Returns arguments to be used in the command.
- *
- * @return {Array}
- */
- proto.args = function args () {
- var outname = this.outname || "-";
- if (this._outputFormat) outname = this._outputFormat + ':' + outname;
- return [].concat(
- this._subCommand
- , this._in
- , this.src()
- , this._out
- , outname
- ).filter(Boolean); // remove falsey
- }
- /**
- * Adds an img source formatter.
- *
- * `formatters` are passed an array of images which will be
- * used as 'input' images for the command. Useful for methods
- * like `.append()` where multiple source images may be used.
- *
- * @param {Function} formatter
- * @return {gm} this
- */
- proto.addSrcFormatter = function addSrcFormatter (formatter) {
- if ('function' != typeof formatter)
- throw new TypeError('sourceFormatter must be a function');
- this._sourceFormatters || (this._sourceFormatters = []);
- this._sourceFormatters.push(formatter);
- return this;
- }
- /**
- * Applies all _sourceFormatters
- *
- * @return {Array}
- */
- proto.src = function src () {
- var arr = [];
- for (var i = 0; i < this._sourceFormatters.length; ++i) {
- this._sourceFormatters[i].call(this, arr);
- }
- return arr;
- }
- /**
- * Image types.
- */
- var types = {
- 'jpg': /\.jpe?g$/i
- , 'png' : /\.png$/i
- , 'gif' : /\.gif$/i
- , 'tiff': /\.tif?f$/i
- , 'bmp' : /(?:\.bmp|\.dib)$/i
- , 'webp': /\.webp$/i
- };
- types.jpeg = types.jpg;
- types.tif = types.tiff;
- types.dib = types.bmp;
- /**
- * Determine the type of source image.
- *
- * @param {String} type
- * @return {Boolean}
- * @example
- * if (this.inputIs('png')) ...
- */
- proto.inputIs = function inputIs (type) {
- if (!type) return false;
- var rgx = types[type];
- if (!rgx) {
- if ('.' !== type[0]) type = '.' + type;
- rgx = new RegExp('\\' + type + '$', 'i');
- }
- return rgx.test(this.source);
- }
- }
|