impro
Version:
Image processing engine
160 lines (154 loc) • 5.06 kB
JavaScript
const Stream = require('stream');
const exifReader = require('exif-reader');
const icc = require('icc');
const mime = require('../mime');
const requireOr = require('../requireOr');
const createAnimatedGifDetector = requireOr('animated-gif-detector');
const sharp = requireOr('sharp');
function defaultProperties(obj, source) {
for (const key in source) {
if (typeof obj[key] === 'undefined') {
obj[key] = source[key];
}
}
return obj;
}
module.exports = {
name: 'metadata',
unavailable: !sharp,
inputTypes: ['*'],
defaultOutputType: 'json',
outputTypes: ['json'],
validateOperation: function (name, args) {
return (
(name === 'metadata' && args.length === 0) ||
(args.length === 1 && args[0] === true)
);
},
execute: function (pipeline, operations, options) {
options = options || {};
const impro = pipeline.impro;
const cache = pipeline.options.sharpCache || options.cache;
// Would make sense to move the _sharpCacheSet property to the type, but that breaks some test scenarios:
if (cache !== 'undefined' && !impro._sharpCacheSet) {
sharp.cache(cache);
impro._sharpCacheSet = true;
}
const sharpInstance = sharp();
const duplexStream = new Stream.Duplex();
let animatedGifDetector;
let isAnimated;
if (
(pipeline.targetType === 'gif' || !pipeline.targetType) &&
createAnimatedGifDetector
) {
animatedGifDetector = createAnimatedGifDetector();
animatedGifDetector.on('animated', function () {
isAnimated = true;
this.emit('decided');
animatedGifDetector = null;
});
duplexStream.on('finish', () => {
if (typeof isAnimated === 'undefined') {
isAnimated = false;
if (animatedGifDetector) {
animatedGifDetector.emit('decided', false);
animatedGifDetector = null;
}
}
});
}
duplexStream._write = (chunk, encoding, cb) => {
if (animatedGifDetector) {
animatedGifDetector.write(chunk);
}
if (
sharpInstance.write(chunk, encoding) === false &&
!animatedGifDetector
) {
sharpInstance.once('drain', cb);
} else {
cb();
}
};
const alreadyKnownMetadata = {
format: pipeline.targetType,
contentType:
pipeline.targetContentType || mime.getType(pipeline.targetType),
};
if (pipeline._streams.length === 0) {
Object.assign(alreadyKnownMetadata, pipeline.sourceMetadata);
}
duplexStream._read = (size) => {
sharpInstance.metadata((err, metadata) => {
if (err) {
metadata = { error: err.message };
}
if (metadata.format === 'magick') {
// https://github.com/lovell/sharp/issues/377
metadata.format = undefined;
} else if (metadata.format) {
// metadata.format is one of 'jpeg', 'png', 'webp' so this should be safe:
metadata.contentType = 'image/' + metadata.format;
}
defaultProperties(metadata, alreadyKnownMetadata);
if (metadata.exif) {
let exifData;
try {
exifData = exifReader(metadata.exif);
} catch (e) {
// Error: Invalid EXIF
}
metadata.exif = undefined;
if (exifData) {
const orientation = exifData.image && exifData.image.Orientation;
// Check if the image.Orientation EXIF tag specifies says that the
// width and height are to be flipped
// http://sylvana.net/jpegcrop/exif_orientation.html
if (
typeof orientation === 'number' &&
orientation >= 5 &&
orientation <= 8
) {
metadata.orientedWidth = metadata.height;
metadata.orientedHeight = metadata.width;
} else {
metadata.orientedWidth = metadata.width;
metadata.orientedHeight = metadata.height;
}
defaultProperties(metadata, exifData);
}
}
if (metadata.icc) {
try {
metadata.icc = icc.parse(metadata.icc);
} catch (e) {
// Error: Error: Invalid ICC profile, remove the Buffer
metadata.icc = undefined;
}
}
function proceed() {
duplexStream.push(JSON.stringify(metadata));
duplexStream.push(null);
}
if (typeof isAnimated === 'boolean') {
metadata.animated = isAnimated;
proceed();
} else if (animatedGifDetector) {
animatedGifDetector.on('decided', (isAnimated) => {
metadata.animated = isAnimated;
proceed();
});
} else {
proceed();
}
});
};
duplexStream.on('finish', () => {
sharpInstance.end();
});
pipeline._attach(duplexStream);
pipeline.targetType = 'json';
pipeline.targetContentType = 'application/json; charset=utf-8';
},
};