UNPKG

xmp-reader

Version:

Extracts basic XMP/RDF metadata from JPEG files

125 lines (112 loc) 3.55 kB
'use strict'; 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);