API Docs for: 2.0.0

src/node-exiftool.js

// SAGE2 is available for use under the SAGE2 Software License
//
// University of Illinois at Chicago's Electronic Visualization Laboratory (EVL)
// and University of Hawai'i at Manoa's Laboratory for Advanced Visualization and
// Applications (LAVA)
//
// See full text, terms and conditions in the LICENSE.txt included file
//
// Copyright (c) 2014

/**
 * Metadata processing using ExifTool
 *   ExifTool by Phil Harvey: http://www.sno.phy.queensu.ca/~phil/exiftool/
 *
 * @module server
 * @submodule exiftool
 * @class exiftool
 */

// Inspired by: https://github.com/nathanpeck/exiftool

// require variables to be declared
'use strict';

var ChildProcess = require('child_process');

// try to use spawnSync (node >= v12 ) or emulation
var spawnSync    = ChildProcess.spawnSync; // || require('spawn-sync');

/**
 * Process a file, using spawn method
 *
 * @method fileSpawn
 * @param filename {String} name of the file to be tested
 * @param done {Function} executed when done, done(error, metadata)
 */
function fileSpawn(filename, done) {
	// The dash specifies to read data from stdin
	var exif = ChildProcess.spawn('exiftool', ['-json', '-m', '-filesize#', '-all', filename]);

	// Check for error because of the child process not being found / launched
	exif.on('error', function(err) {
		done('Fatal Error: Unable to load exiftool. ' + err);
	});

	// Read the binary data back
	var response = '';
	var errorMessage = '';
	exif.stdout.on("data", function(data) {
		response += data;
	});

	// Read an error response back and deal with it.
	exif.stderr.on("data", function(data) {
		errorMessage += data.toString();
	});

	// Handle the response to the callback to hand the metadata back.
	exif.on("close", function() {
		if (errorMessage) {
			done(errorMessage);
		} else {
			var metadata = JSON.parse(response);
			done(null, metadata[0]);
		}
	});
}

/**
 * Process a file, using exec method
 *
 * @method file
 * @param filename {String} name of the file to be tested
 * @param done {Function} executed when done, done(error, metadata)
 */
function file(filename, done) {
	// Spawn an exiftool process
	var exif = ChildProcess.spawn('exiftool', ['-m', '-json', '-filesize#', '-all', filename]);

	// Check for error because of the child process not being found / launched
	exif.on('error', function(err) {
		done('Fatal Error: Unable to load exiftool. ' + err);
	});

	// Read the binary data back
	var response = '';
	var errorMessage = '';
	exif.stdout.on("data", function(data) {
		response += data;
	});

	// Read an error response back and deal with it.
	exif.stderr.on("data", function(data) {
		errorMessage += data.toString();
	});

	exif.on("close", function() {
		if (errorMessage) {
			done(errorMessage);
		} else {
			try {
				var metadata = JSON.parse(response);
				if ('SourceFile' in metadata[0]) {
					if (metadata[0].Error) {
						// if there was an error because unknown file type, delete it
						delete metadata[0].Error;
					}
					// Add a dummy type if needed
					if (!metadata[0].MIMEType) {
						metadata[0].MIMEType = 'text/plain';
					}
					if (!metadata[0].FileType) {
						metadata[0].FileType = 'text/plain';
					}
					done(null, metadata[0]);
				} else {
					// unknown data
					done('EXIF: Error parsing JSON for ' + filename);
				}
			} catch (e) {
				done('EXIF: Error parsing JSON for ' + filename);
			}
		}
	});

	return exif;
}


/**
 * Process a file synchronously
 *   watch for non-escaped filename when using spawn-sync.spawnSync
 *   node v12 is good
 *
 * @method fileSync
 * @param filename {String} name of the file to be tested
 * @return {Object} return object as {err:String, metadata:Object)
 */
function fileSync(filename) {
	var result = spawnSync('exiftool', ['-json', '-m', '-filesize#', '-all', filename]);
	// Note, status code will always equal 0 if using busy waiting fallback
	if (result.statusCode && result.statusCode !== 0) {
		return {err: 'Fatal Error: Unable to load exiftool. ' + result.stderr, metadata: null};
	}
	if (result.stdout.length !== 0) {
		var metadata = JSON.parse(result.stdout);
		return {err: null, metadata: metadata[0]};
	}
	return {err: result.stderr.toString(), metadata: null};
}

/**
 * Process a buffer synchronously
 *
 * @method bufferSync
 * @param source {Buffer} file content to be processed
 * @return {Object} return object as {err:String, metadata:Object)
 */
function bufferSync(source) {
	var result = spawnSync('exiftool',
		['-json', '-m', '-filesize#', '-all', '-'],
		{input: source, encoding: null});

	// Note, status code will always equal 0 if using busy waiting fallback
	if (result.statusCode && result.statusCode !== 0) {
		return {err: 'Fatal Error: Unable to load exiftool. ' + result.stderr, metadata: null};
	}
	var metadata = JSON.parse(result.stdout);
	return {err: null, metadata: metadata[0]};
}

/**
 * Process a buffer
 *
 * @method buffer
 * @param source {Buffer} file content to be processed
 * @param callback {Function} executed when done, done(error, metadata)
 */
function buffer(source, callback) {
	// The dash specifies to read data from stdin
	var exif = ChildProcess.spawn('exiftool', ['-json', '-m', '-filesize#', '-all', '-'], {stdin: 'pipe'});

	// Check for error because of the child process not being found / launched
	exif.on('error', function(err) {
		callback('Fatal Error: Unable to load exiftool. ' + err);
	});

	// Read the binary data back
	var response = '';
	var errorMessage = '';
	exif.stdout.on("data", function(data) {
		response += data;
	});

	// Read an error response back and deal with it.
	exif.stderr.on("data", function(data) {
		errorMessage += data.toString();
	});

	exif.on("close", function() {
		if (errorMessage) {
			callback(errorMessage);
		} else {
			var metadata = JSON.parse(response);
			callback(null, metadata[0]);
		}
	});

	exif.stdin.on('error', function(err) {
		console.log('Error in stdin - IGNORED', err);
	});

	var curr = 0;
	var done = false;
	while (!done) {
		// Give the source binary data to the process
		var status = exif.stdin.write(source.slice(curr, Math.min(curr + 16 * 1024, source.length)));
		curr += 16 * 1024;
		done  = status;
		if (curr >= source.length) {
			done = true;
		}
	}

	exif.stdin.end(function() {
		// nothing
	});

	return exif;
}

exports.file       = file;
exports.buffer     = buffer;
exports.fileSync   = fileSync;
exports.fileSpawn  = fileSpawn;
exports.bufferSync = bufferSync;

/*

// testing: fileSync
var fs=require('fs');
var e=require('./src/node-exiftool');
var d = e.fileSync('public_HTTPS/images/screen.png');


// testing: file
var e=require('./src/node-exiftool');
e.file('public_HTTPS/images/screen.png', function(err, data){console.log(data);});

// testing: bufferSync
var fs=require('fs');
var e=require('./src/node-exiftool');
var buf=fs.readFileSync('public_HTTPS/images/screen.png');
var d = e.bufferSync(buf);

// testing: buffer
var fs=require('fs');
var e=require('./src/node-exiftool');
var buf=fs.readFileSync('public_HTTPS/images/screen.png');
var d = e.buffer(buf, function(err, data){console.log(data);});

*/