xmp-reader
Version:
Extracts basic XMP/RDF metadata from JPEG files
125 lines (112 loc) • 3.55 kB
JavaScript
const markerBegin = '<x:xmpmeta';
const markerEnd = '</x:xmpmeta>';
const bufferLimit = 65536;
const knownTags = [
'MicrosoftPhoto:LastKeywordXMP',
'MicrosoftPhoto:LastKeywordIPTC',
'MicrosoftPhoto:Rating',
'Iptc4xmpCore:Location',
'xmp:Rating',
'dc:title',
'dc:description',
'dc:creator',
'dc:rights',
'cc:attributionName'
];
const envelopeTags = [
'rdf:Bag',
'rdf:Alt',
'rdf:Seq',
'rdf:li'
];
let fs = require('fs');
let bufferToPromise = (buffer) => new Promise((resolve, reject) => {
if (!Buffer.isBuffer(buffer)) reject('Not a Buffer');
else {
let data = {raw: {}};
let offsetBegin = buffer.indexOf(markerBegin);
if (offsetBegin) {
let offsetEnd = buffer.indexOf(markerEnd);
if (offsetEnd) {
let xmlBuffer = buffer.slice(offsetBegin, offsetEnd + markerEnd.length);
let parser = require('sax').parser(true);
let nodeName;
parser.onerror = (err) => reject(err);
parser.onend = () => resolve(data);
parser.onopentag = function (node) {
if (knownTags.indexOf(node.name) != -1) nodeName = node.name;
else if (envelopeTags.indexOf(node.name) == -1) nodeName = null;
};
parser.ontext = function(text) {
if (text.trim() != '') switch (nodeName) {
case 'MicrosoftPhoto:LastKeywordXMP':
case 'MicrosoftPhoto:LastKeywordIPTC':
if (!data.raw[nodeName]) data.raw[nodeName] = [text];
else if (data.raw[nodeName].indexOf(text) == -1) data.raw[nodeName].push(text);
if (!data.keywords) data.keywords = [text];
else if (data.keywords.indexOf(text) == -1) data.keywords.push(text);
break;
case 'dc:title':
data.raw[nodeName] = text;
data.title = text;
break;
case 'dc:description':
data.raw[nodeName] = text;
data.description = text;
break;
case 'xmp:Rating':
data.raw[nodeName] = text;
data.rating = parseInt(text);
break;
case 'MicrosoftPhoto:Rating':
data.raw[nodeName] = text;
data.rating = Math.floor(parseInt(text) + 12 / 25) + 1;
break;
case 'Iptc4xmpCore:Location':
data.raw[nodeName] = text;
data.location = text;
break;
case 'dc:creator':
data.raw[nodeName] = text;
data.creator = text;
break;
case 'cc:attributionName':
data.raw[nodeName] = text;
data.attribution = text;
break;
case 'xmpRights:UsageTerms':
case 'dc:rights':
data.raw[nodeName] = text;
data.terms = text;
break;
}
};
parser.write(xmlBuffer.toString('utf-8', 0, xmlBuffer.length)).close();
}
else resolve(data);
}
else resolve(data);
}
});
let fileToBuffer = (file) => new Promise((resolve, reject) => {
fs.open(file, 'r', (err, fd) => {
if (err) reject(err);
else {
let buffer = new Buffer(bufferLimit);
fs.read(fd, buffer, 0, bufferLimit, 0, (err, bytesRead, buffer) => {
if (err) reject(err);
else resolve(buffer);
});
}
});
});
let promiseToCallback = (promise, callback) => {
if ('function' == typeof callback) promise.then(
(data) => callback(null, data),
(error) => callback(error)
);
return promise;
};
module.exports.fromBuffer = (buffer, callback) => promiseToCallback(bufferToPromise(buffer), callback);
module.exports.fromFile = (filename, callback) => promiseToCallback(fileToBuffer(filename).then(bufferToPromise), callback);
;