Add raw EXIF data to metadata response

Copy metadata input buffer to match pipeline

Prevents possible metadata segfault under load
This commit is contained in:
Lovell Fuller 2015-06-28 23:35:40 +01:00
parent 86490bedfb
commit 6ac47c1ef8
7 changed files with 58 additions and 17 deletions

View File

@ -343,6 +343,7 @@ Fast access to image metadata without decoding any compressed image data.
* `hasProfile`: Boolean indicating the presence of an embedded ICC profile * `hasProfile`: Boolean indicating the presence of an embedded ICC profile
* `hasAlpha`: Boolean indicating the presence of an alpha transparency channel * `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
* `orientation`: Number value of the EXIF Orientation header, if present * `orientation`: Number value of the EXIF Orientation header, if present
* `exif`: Buffer containing raw EXIF data, if present
A Promises/A+ promise is returned when `callback` is not provided. A Promises/A+ promise is returned when `callback` is not provided.

View File

@ -737,22 +737,22 @@ Sharp.prototype.metadata = function(callback) {
if (this.options.streamIn) { if (this.options.streamIn) {
return new BluebirdPromise(function(resolve, reject) { return new BluebirdPromise(function(resolve, reject) {
that.on('finish', function() { that.on('finish', function() {
sharp.metadata(that.options, function(err, data) { sharp.metadata(that.options, function(err, metadata) {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
resolve(data); resolve(metadata);
} }
}); });
}); });
}); });
} else { } else {
return new BluebirdPromise(function(resolve, reject) { return new BluebirdPromise(function(resolve, reject) {
sharp.metadata(that.options, function(err, data) { sharp.metadata(that.options, function(err, metadata) {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
resolve(data); resolve(metadata);
} }
}); });
}); });

View File

@ -45,19 +45,20 @@
"vips" "vips"
], ],
"dependencies": { "dependencies": {
"bluebird": "^2.9.27", "bluebird": "^2.9.30",
"color": "^0.8.0", "color": "^0.9.0",
"nan": "^1.8.4", "nan": "^1.8.4",
"semver": "^4.3.6" "semver": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"async": "^1.1.0", "async": "^1.2.1",
"coveralls": "^2.11.2", "coveralls": "^2.11.2",
"istanbul": "^0.3.14", "exif-reader": "1.0.0",
"istanbul": "^0.3.17",
"mocha": "^2.2.5", "mocha": "^2.2.5",
"mocha-jshint": "^2.2.3", "mocha-jshint": "^2.2.3",
"node-cpplint": "^0.4.0", "node-cpplint": "^0.4.0",
"rimraf": "^2.3.4" "rimraf": "^2.4.0"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {

View File

@ -27,7 +27,7 @@ using sharp::counterQueue;
struct MetadataBaton { struct MetadataBaton {
// Input // Input
std::string fileIn; std::string fileIn;
void* bufferIn; char *bufferIn;
size_t bufferInLength; size_t bufferInLength;
// Output // Output
std::string format; std::string format;
@ -38,13 +38,26 @@ struct MetadataBaton {
bool hasProfile; bool hasProfile;
bool hasAlpha; bool hasAlpha;
int orientation; int orientation;
char *exif;
size_t exifLength;
std::string err; std::string err;
MetadataBaton(): MetadataBaton():
bufferInLength(0), bufferInLength(0),
orientation(0) {} orientation(0),
exifLength(0) {}
}; };
/*
Delete input char[] buffer and notify V8 of memory deallocation
Used as the callback function for the "postclose" signal
*/
static void DeleteBuffer(VipsObject *object, char *buffer) {
if (buffer != NULL) {
delete[] buffer;
}
}
class MetadataWorker : public NanAsyncWorker { class MetadataWorker : public NanAsyncWorker {
public: public:
@ -57,17 +70,22 @@ class MetadataWorker : public NanAsyncWorker {
ImageType imageType = ImageType::UNKNOWN; ImageType imageType = ImageType::UNKNOWN;
VipsImage *image = NULL; VipsImage *image = NULL;
if (baton->bufferInLength > 1) { if (baton->bufferInLength > 0) {
// From buffer // From buffer
imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength); imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
if (imageType != ImageType::UNKNOWN) { if (imageType != ImageType::UNKNOWN) {
image = InitImage(baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM); image = InitImage(baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM);
if (image == NULL) { if (image != NULL) {
// Listen for "postclose" signal to delete input buffer
g_signal_connect(image, "postclose", G_CALLBACK(DeleteBuffer), baton->bufferIn);
} else {
(baton->err).append("Input buffer has corrupt header"); (baton->err).append("Input buffer has corrupt header");
imageType = ImageType::UNKNOWN; imageType = ImageType::UNKNOWN;
DeleteBuffer(NULL, baton->bufferIn);
} }
} else { } else {
(baton->err).append("Input buffer contains unsupported image format"); (baton->err).append("Input buffer contains unsupported image format");
DeleteBuffer(NULL, baton->bufferIn);
} }
} else { } else {
// From file // From file
@ -102,6 +120,16 @@ class MetadataWorker : public NanAsyncWorker {
// Derived attributes // Derived attributes
baton->hasAlpha = HasAlpha(image); baton->hasAlpha = HasAlpha(image);
baton->orientation = ExifOrientation(image); baton->orientation = ExifOrientation(image);
// EXIF
if (vips_image_get_typeof(image, VIPS_META_EXIF_NAME) == VIPS_TYPE_BLOB) {
void* exif;
size_t exifLength;
if (!vips_image_get_blob(image, VIPS_META_EXIF_NAME, &exif, &exifLength)) {
baton->exifLength = exifLength;
baton->exif = new char[exifLength];
memcpy(baton->exif, exif, exifLength);
}
}
// Drop image reference // Drop image reference
g_object_unref(image); g_object_unref(image);
} }
@ -130,6 +158,9 @@ class MetadataWorker : public NanAsyncWorker {
if (baton->orientation > 0) { if (baton->orientation > 0) {
info->Set(NanNew<String>("orientation"), NanNew<Number>(baton->orientation)); info->Set(NanNew<String>("orientation"), NanNew<Number>(baton->orientation));
} }
if (baton->exifLength > 0) {
info->Set(NanNew<String>("exif"), NanBufferUse(baton->exif, baton->exifLength));
}
argv[1] = info; argv[1] = info;
} }
delete baton; delete baton;
@ -157,8 +188,10 @@ NAN_METHOD(metadata) {
// Input Buffer object // Input Buffer object
if (options->Get(NanNew<String>("bufferIn"))->IsObject()) { if (options->Get(NanNew<String>("bufferIn"))->IsObject()) {
Local<Object> buffer = options->Get(NanNew<String>("bufferIn"))->ToObject(); Local<Object> buffer = options->Get(NanNew<String>("bufferIn"))->ToObject();
// Take a copy of the input Buffer to avoid problems with V8 heap compaction
baton->bufferInLength = node::Buffer::Length(buffer); baton->bufferInLength = node::Buffer::Length(buffer);
baton->bufferIn = node::Buffer::Data(buffer); baton->bufferIn = new char[baton->bufferInLength];
memcpy(baton->bufferIn, node::Buffer::Data(buffer), baton->bufferInLength);
} }
// Join queue for worker thread // Join queue for worker thread

View File

@ -184,7 +184,7 @@ class PipelineWorker : public NanAsyncWorker {
// Input // Input
ImageType inputImageType = ImageType::UNKNOWN; ImageType inputImageType = ImageType::UNKNOWN;
VipsImage *image = NULL; VipsImage *image = NULL;
if (baton->bufferInLength > 1) { if (baton->bufferInLength > 0) {
// From buffer // From buffer
inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength); inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
if (inputImageType != ImageType::UNKNOWN) { if (inputImageType != ImageType::UNKNOWN) {

View File

@ -2,6 +2,7 @@
var fs = require('fs'); var fs = require('fs');
var assert = require('assert'); var assert = require('assert');
var exifReader = require('exif-reader');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
@ -21,6 +22,7 @@ describe('Image metadata', function() {
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation); assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif);
done(); done();
}); });
}); });
@ -36,6 +38,12 @@ describe('Image metadata', function() {
assert.strictEqual(true, metadata.hasProfile); assert.strictEqual(true, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual(8, metadata.orientation); assert.strictEqual(8, metadata.orientation);
assert.strictEqual('object', typeof metadata.exif);
assert.strictEqual(true, metadata.exif instanceof Buffer);
var exif = exifReader(metadata.exif);
assert.strictEqual('object', typeof exif);
assert.strictEqual('object', typeof exif.image);
assert.strictEqual('number', typeof exif.image.XResolution);
done(); done();
}); });
}); });

2
test/unit/overlay.js Normal file → Executable file
View File

@ -147,7 +147,6 @@ describe('Overlays', function() {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.overlayWith(fixtures.inputPngWithGreyAlpha) .overlayWith(fixtures.inputPngWithGreyAlpha)
.toBuffer(function(error) { .toBuffer(function(error) {
console.dir(error);
assert.strictEqual(true, error instanceof Error); assert.strictEqual(true, error instanceof Error);
done(); done();
}); });
@ -157,7 +156,6 @@ describe('Overlays', function() {
sharp(fixtures.inputPngOverlayLayer1) sharp(fixtures.inputPngOverlayLayer1)
.overlayWith(fixtures.inputJpg) .overlayWith(fixtures.inputJpg)
.toBuffer(function(error) { .toBuffer(function(error) {
console.dir(error);
assert.strictEqual(true, error instanceof Error); assert.strictEqual(true, error instanceof Error);
done(); done();
}); });