Permalink
Browse files

Added support for deleting tags

  • Loading branch information...
1 parent ee867d8 commit fed9e907ac4149f04c1663cea4a8735990fe5d5b @dberesford committed Dec 6, 2013
Showing with 191 additions and 14 deletions.
  1. +1 −0 .gitignore
  2. +17 −1 README.md
  3. +19 −4 examples/simple.js
  4. +95 −3 exiv2node.cc
  5. +31 −4 package.json
  6. +28 −2 test/test.js
View
1 .gitignore
@@ -2,3 +2,4 @@
build/
node_modules/
test/copy.jpg
+*~
View
18 README.md
@@ -76,12 +76,28 @@ the tests:
};
ex.setImageTags('./photo.jpg', newTags, function(err){
if (err) {
- console.log(err);
+ console.error(err);
} else {
console.log("setImageTags complete..");
}
});
+### Delete tags:
+
+ var ex = require('exiv2')
+
+ var tagsToDelete = {
+ "Exif.Photo.UserComment" : "Some Comment..",
@drewish
drewish Dec 7, 2013

Is the idea of passing keys and values in that there could be multiple values for a single key and we match them? Other wise I'd think just deleting keys would be the simplest interface.

+ "Exif.Canon.OwnerName" : "My Camera"
+ };
+ ex.deleteImageTags('./photo.jpg', tagsToDelete, function(err){
+ if (err) {
+ console.error(err);
+ } else {
+ console.log("deleteImageTags complete..");
@drewish
drewish Dec 7, 2013

Seems like it should be one period or three... I guess we have the same issue above though.

+ }
+ });
+
Take a look at the `examples/` and `test/` directories for more.
email: dberesford at gmail
View
23 examples/simple.js
@@ -4,7 +4,7 @@ var fs = require('fs')
var dir = __dirname + '/../test/images';
-// Test basic image with exif tags:
+// Example of get image exif tags:
ex.getImageTags(dir + '/books.jpg', function(err, tags) {
console.log(tags);
});
@@ -13,19 +13,34 @@ ex.getImageTags(dir + '/books.jpg', function(err, tags) {
fs.writeFileSync(dir + '/copy.jpg', fs.readFileSync(dir + '/books.jpg'));
// Set some tags on the image:
-var tags = {
+var newtags = {
"Exif.Photo.UserComment" : "Some books..",
"Exif.Canon.OwnerName" : "Damo's camera"
};
-ex.setImageTags(dir + '/copy.jpg', tags, function(err){
+
+ex.setImageTags(dir + '/copy.jpg', newtags, function(err){
+ assert.ok(!err);
+
// Check our tags have been set
ex.getImageTags(dir + '/copy.jpg', function(err, tags) {
+ assert.ok(!err);
assert.equal("Some books..", tags["Exif.Photo.UserComment"]);
assert.equal("Damo's camera", tags["Exif.Canon.OwnerName"]);
+
+ // delete image tags
+ ex.deleteImageTags(dir + '/copy.jpg', newtags, function(err) {
+ assert.ok(!err);
+ ex.getImageTags(dir + '/copy.jpg', function(err, tags) {
+ assert.ok(!err);
+ assert.ok(!tags["Exif.Canon.OwnerName"]);
+ assert.ok(!tags["Exif.Photo.UserComment"]);
+ });
+ });
});
});
-// Load the preview images:
+
+// Example of loading the preview images:
ex.getImagePreviews(dir + '/books.jpg', function(err, previews) {
// Display information about the previews.
console.log("Preview images:");
View
98 exiv2node.cc
@@ -113,6 +113,8 @@ static void SetImageTagsWorker(uv_work_t *req);
static void AfterSetImageTags(uv_work_t* req, int status);
static void GetImagePreviewsWorker(uv_work_t* req);
static void AfterGetImagePreviews(uv_work_t* req, int status);
+static void DeleteImageTagsWorker(uv_work_t *req);
+static void AfterDeleteImageTags(uv_work_t* req, int status);
static Handle<Value> GetImageTags(const Arguments& args) {
HandleScope scope;
@@ -124,7 +126,7 @@ static Handle<Value> GetImageTags(const Arguments& args) {
// Set up our thread data struct, pass off to the libuv thread pool.
Baton *thread_data = new Baton(Local<String>::Cast(args[0]), Local<Function>::Cast(args[1]));
- int status = uv_queue_work(uv_default_loop(), &thread_data->request, GetImageTagsWorker, (uv_after_work_cb)AfterGetImageTags);
@drewish
drewish Dec 7, 2013

Any reason to remove the cast from here?

+ int status = uv_queue_work(uv_default_loop(), &thread_data->request, GetImageTagsWorker, AfterGetImageTags);
assert(status == 0);
return Undefined();
@@ -214,7 +216,7 @@ static Handle<Value> SetImageTags(const Arguments& args) {
);
}
- int status = uv_queue_work(uv_default_loop(), &thread_data->request, SetImageTagsWorker, (uv_after_work_cb)AfterSetImageTags);
+ int status = uv_queue_work(uv_default_loop(), &thread_data->request, SetImageTagsWorker, AfterSetImageTags);
assert(status == 0);
return Undefined();
@@ -255,6 +257,95 @@ static void SetImageTagsWorker(uv_work_t *req) {
}
}
+/* Delete Image Tag support.. */
+static Handle<Value> DeleteImageTags(const Arguments& args) {
+ HandleScope scope;
+
+ /* Usage arguments */
+ if (args.Length() <= 2 || !args[2]->IsFunction())
+ return ThrowException(Exception::TypeError(String::New("Usage: <filename> <tags> <callback function>")));
+
+ // Set up our thread data struct, pass off to the libuv thread pool.
+ Baton *thread_data = new Baton(Local<String>::Cast(args[0]), Local<Function>::Cast(args[2]));
+
+ Local<Object> tags = Local<Object>::Cast(args[1]);
+ Local<Array> keys = tags->GetPropertyNames();
+ for (unsigned i = 0; i < keys->Length(); i++) {
+ Handle<v8::Value> key = keys->Get(i);
+ thread_data->tags->insert(std::pair<std::string, std::string> (
+ *String::AsciiValue(key),
+ *String::AsciiValue(tags->Get(key)))
+ );
+ }
+
+ int status = uv_queue_work(uv_default_loop(), &thread_data->request, DeleteImageTagsWorker, AfterDeleteImageTags);
+ assert(status == 0);
+
+ return Undefined();
+}
+
+static void DeleteImageTagsWorker(uv_work_t *req) {
+ Baton *thread_data = static_cast<Baton*> (req->data);
+
+ try {
+ Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(thread_data->fileName);
+ assert(image.get() != 0);
+
+ image->readMetadata();
+ Exiv2::ExifData &exifData = image->exifData();
+ Exiv2::IptcData &iptcData = image->iptcData();
+ Exiv2::XmpData &xmpData = image->xmpData();
+
+ // Erase the tags.
+ for (tag_map_t::iterator i = thread_data->tags->begin(); i != thread_data->tags->end(); ++i) {
+ if (i->first.compare(0, 5, "Exif.") == 0) {
+ Exiv2::ExifKey *k = new Exiv2::ExifKey(i->first);
+ exifData.erase(exifData.findKey(*k));
@drewish
drewish Dec 7, 2013

It doesn't seem like we're doing anything with the values here. I think we should change the API to just take key names.

+ delete k;
+ } else if (i->first.compare(0, 5, "Iptc.") == 0) {
+ Exiv2::IptcKey *k = new Exiv2::IptcKey(i->first);
+ iptcData.erase(iptcData.findKey(*k));
+ delete k;
+ } else if (i->first.compare(0, 4, "Xmp.") == 0) {
+ Exiv2::XmpKey *k = new Exiv2::XmpKey(i->first);
+ xmpData.erase(xmpData.findKey(*k));
+ delete k;
+ } else {
+ //std::cerr << "skipping unknown tag " << i->first << std::endl;
+ }
+ }
+
+ // Write the tag data the image file.
+ image->setExifData(exifData);
+ image->setIptcData(iptcData);
+ image->setXmpData(xmpData);
+ image->writeMetadata();
+ } catch (std::exception& e) {
+ thread_data->exifException.append(e.what());
+ }
+}
+
+/* Thread complete callback.. */
+static void AfterDeleteImageTags(uv_work_t* req, int status) {
+ HandleScope scope;
+ Baton *thread_data = static_cast<Baton*> (req->data);
+
+ // Create an argument array for any errors.
+ Local<Value> argv[1] = { Local<Value>::New(Null()) };
+ if (!thread_data->exifException.empty()) {
+ argv[0] = Local<String>::New(String::New(thread_data->exifException.c_str()));
+ }
+
+ // Pass the argv array object to our callback function.
+ TryCatch try_catch;
+ thread_data->cb->Call(Context::GetCurrent()->Global(), 1, argv);
+ if (try_catch.HasCaught()) {
+ FatalException(try_catch);
+ }
+
+ delete thread_data;
+}
+
/* Thread complete callback.. */
static void AfterSetImageTags(uv_work_t* req, int status) {
HandleScope scope;
@@ -288,7 +379,7 @@ static Handle<Value> GetImagePreviews(const Arguments& args) {
// Set up our thread data struct, pass off to the libuv thread pool.
GetPreviewBaton *thread_data = new GetPreviewBaton(Local<String>::Cast(args[0]), Local<Function>::Cast(args[1]));
- int status = uv_queue_work(uv_default_loop(), &thread_data->request, GetImagePreviewsWorker, (uv_after_work_cb)AfterGetImagePreviews);
+ int status = uv_queue_work(uv_default_loop(), &thread_data->request, GetImagePreviewsWorker, AfterGetImagePreviews);
assert(status == 0);
return Undefined();
@@ -363,6 +454,7 @@ static void AfterGetImagePreviews(uv_work_t* req, int status) {
void InitAll(Handle<Object> target) {
target->Set(String::NewSymbol("getImageTags"), FunctionTemplate::New(GetImageTags)->GetFunction());
target->Set(String::NewSymbol("setImageTags"), FunctionTemplate::New(SetImageTags)->GetFunction());
+ target->Set(String::NewSymbol("deleteImageTags"), FunctionTemplate::New(DeleteImageTags)->GetFunction());
target->Set(String::NewSymbol("getImagePreviews"), FunctionTemplate::New(GetImagePreviews)->GetFunction());
}
NODE_MODULE(exiv2, InitAll)
View
35 package.json
@@ -1,8 +1,12 @@
{
- "author": "Damian Beresford <dberesford@gmail.com> (http://www.damianberesford.com/)",
+ "author": {
+ "name": "Damian Beresford",
+ "email": "dberesford@gmail.com",
+ "url": "http://www.damianberesford.com/"
+ },
"name": "exiv2",
"description": "A native c++ extension for node.js that provides support for reading & writing image metadata via Exiv2.",
- "version": "0.4.2",
+ "version": "0.4.1",
@drewish
drewish Dec 7, 2013

Did you mean to decrement the version? Seems like your commit might have been from an old version?

"homepage": "https://github.com/dberesford/exiv2node",
"repository": {
"type": "git",
@@ -20,6 +24,29 @@
"main": "exiv2",
"scripts": {
"preuninstall": "rm -rf build/*",
- "test": "mocha"
- }
+ "test": "mocha",
+ "install": "node-gyp rebuild"
+ },
+ "gypfile": true,
+ "contributors": [
+ {
+ "name": "Damian Beresford",
+ "email": "dberesford@gmail.com"
+ },
+ {
+ "name": "Ryan French",
+ "email": "frenchrya@gmail.com"
+ },
+ {
+ "name": "Andrew Morton",
+ "email": "drewish@katherinehouse.com"
+ }
+ ],
+ "readme": "#Exiv2\n\nExiv2 is a native c++ extension for [node.js](http://nodejs.org/) that provides\nsupport for reading and writing image metadata via the [Exiv2 library](http://www.exiv2.org).\n\n## Dependencies\n\nTo build this addon you'll need the Exiv2 library and headers so if you're using\na package manager you might need to install an additional \"-dev\" packages.\n\n### Debian\n\n apt-get install libexiv2 libexiv2-dev\n\n### OS X\n\nYou'll also need to install pkg-config to help locate the library and headers.\n\n[MacPorts](http://macports.org/):\n\n port install pkgconfig exiv2\n\n[Homebrew](http://github.com/mxcl/homebrew/):\n\n brew install pkg-config exiv2\n\n### Other systems\n\nSee the [Exiv2 download page](http://www.exiv2.org/download.html) for more\ninformation.\n\n## Installation Instructions\n\nOnce the dependencies are in place, you can build and install the module using\nnpm:\n\n npm install exiv2\n\nYou can verify that everything is installed and operating correctly by running\nthe tests:\n\n npm test\n\n## Sample Usage\n\n### Read tags:\n\n var ex = require('exiv2');\n\n ex.getImageTags('./photo.jpg', function(err, tags) {\n console.log(\"DateTime: \" + tags[\"Exif.Image.DateTime\"]);\n console.log(\"DateTimeOriginal: \" + tags[\"Exif.Photo.DateTimeOriginal\"]);\n });\n\n### Load preview images:\n\n var ex = require('exiv2')\n , fs = require('fs');\n\n ex.getImagePreviews('./photo.jpg', function(err, previews) {\n // Display information about the previews.\n console.log(previews);\n\n // Or you can save them--though you'll probably want to check the MIME\n // type before picking an extension.\n fs.writeFile('preview.jpg', previews[0].data);\n });\n\n### Write tags:\n\n var ex = require('exiv2')\n\n var newTags = {\n \"Exif.Photo.UserComment\" : \"Some Comment..\",\n \"Exif.Canon.OwnerName\" : \"My Camera\"\n };\n ex.setImageTags('./photo.jpg', newTags, function(err){\n if (err) {\n console.log(err);\n } else {\n console.log(\"setImageTags complete..\");\n }\n });\n\nTake a look at the `examples/` and `test/` directories for more.\n\nemail: dberesford at gmail\ntwitter: @dberesford\n",
+ "readmeFilename": "README.md",
+ "bugs": {
+ "url": "https://github.com/dberesford/exiv2node/issues"
+ },
+ "_id": "exiv2@0.4.1",
+ "_from": "exiv2@*"
}
View
30 test/test.js
@@ -1,3 +1,4 @@
+
var exiv = require('../exiv2')
, fs = require('fs')
, util = require('util')
@@ -9,7 +10,7 @@ describe('exiv2', function(){
it("should callback with image's tags", function(done) {
exiv.getImageTags(dir + '/books.jpg', function(err, tags) {
should.not.exist(err);
- tags.should.be.a('object');
+
tags.should.have.property('Exif.Image.DateTime', '2008:12:16 21:28:36');
tags.should.have.property('Exif.Photo.DateTimeOriginal', '2008:12:16 21:28:36');
done();
@@ -56,7 +57,7 @@ describe('exiv2', function(){
"Exif.Photo.UserComment" : "Some books..",
"Exif.Canon.OwnerName" : "Damo's camera",
"Iptc.Application2.RecordVersion" : "2",
- "Xmp.dc.subject" : "A camera",
+ "Xmp.dc.subject" : "A camera"
};
exiv.setImageTags(temp, tags, function(err){
should.not.exist(err);
@@ -95,6 +96,31 @@ describe('exiv2', function(){
});
});
+ describe('.deleteImageTags()', function(){
+ var temp = dir + '/copy-deltags.jpg';
+
+ before(function() {
+ fs.writeFileSync(temp, fs.readFileSync(dir + '/books.jpg'));
+ });
+ it('should delete tags in image files', function(done) {
+ var tags = {
+ "Exif.Canon.OwnerName" : "Damo's camera"
+ };
+ exiv.deleteImageTags(temp, tags, function(err){
+ should.not.exist(err);
+
+ exiv.getImageTags(temp, function(err, tags) {
+ should.not.exist(err);
+ tags.should.not.have.property('Exif.Canon.OwnerName');
+ done();
+ });
+ });
+ })
+ after(function(done) {
+ fs.unlink(temp, done);
+ });
+ });
+
describe('.getImagePreviews()', function(){
it("should callback with image's previews", function(done) {
exiv.getImagePreviews(dir + '/books.jpg', function(err, previews) {

0 comments on commit fed9e90

Please sign in to comment.