mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
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:
parent
86490bedfb
commit
6ac47c1ef8
@ -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.
|
||||||
|
|
||||||
|
8
index.js
8
index.js
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
11
package.json
11
package.json
@ -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": {
|
||||||
|
@ -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
|
||||||
|
@ -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) {
|
||||||
|
@ -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
2
test/unit/overlay.js
Normal file → Executable 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();
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user