/* * JavaScript Load Image IPTC Parser * https://github.com/blueimp/JavaScript-Load-Image * * Copyright 2013, Sebastian Tschan * Copyright 2018, Dave Bevan * https://blueimp.net * * Licensed under the MIT license: * https://opensource.org/licenses/MIT */ /* global define, module, require, DataView */ ;(function (factory) { 'use strict' if (typeof define === 'function' && define.amd) { // Register as an anonymous AMD module: define(['./load-image', './load-image-meta'], factory) } else if (typeof module === 'object' && module.exports) { factory(require('./load-image'), require('./load-image-meta')) } else { // Browser globals: factory(window.loadImage) } })(function (loadImage) { 'use strict' /** * IPTC tag map * * @name IptcMap * @class */ function IptcMap() {} IptcMap.prototype.map = { ObjectName: 5 } IptcMap.prototype.types = { 0: 'Uint16', // ApplicationRecordVersion 200: 'Uint16', // ObjectPreviewFileFormat 201: 'Uint16', // ObjectPreviewFileVersion 202: 'binary' // ObjectPreviewData } /** * Retrieves IPTC tag value * * @param {number|string} id IPTC tag code or name * @returns {object} IPTC tag value */ IptcMap.prototype.get = function (id) { return this[id] || this[this.map[id]] } /** * Retrieves string for the given DataView and range * * @param {DataView} dataView Data view interface * @param {number} offset Offset start * @param {number} length Offset length * @returns {string} String value */ function getStringValue(dataView, offset, length) { var outstr = '' var end = offset + length for (var n = offset; n < end; n += 1) { outstr += String.fromCharCode(dataView.getUint8(n)) } return outstr } /** * Retrieves tag value for the given DataView and range * * @param {number} tagCode tag code * @param {IptcMap} map IPTC tag map * @param {DataView} dataView Data view interface * @param {number} offset Range start * @param {number} length Range length * @returns {object} Tag value */ function getTagValue(tagCode, map, dataView, offset, length) { if (map.types[tagCode] === 'binary') { return new Blob([dataView.buffer.slice(offset, offset + length)]) } if (map.types[tagCode] === 'Uint16') { return dataView.getUint16(offset) } return getStringValue(dataView, offset, length) } /** * Combines IPTC value with existing ones. * * @param {object} value Existing IPTC field value * @param {object} newValue New IPTC field value * @returns {object} Resulting IPTC field value */ function combineTagValues(value, newValue) { if (value === undefined) return newValue if (value instanceof Array) { value.push(newValue) return value } return [value, newValue] } /** * Parses IPTC tags. * * @param {DataView} dataView Data view interface * @param {number} segmentOffset Segment offset * @param {number} segmentLength Segment length * @param {object} data Data export object * @param {object} includeTags Map of tags to include * @param {object} excludeTags Map of tags to exclude */ function parseIptcTags( dataView, segmentOffset, segmentLength, data, includeTags, excludeTags ) { var value, tagSize, tagCode var segmentEnd = segmentOffset + segmentLength var offset = segmentOffset while (offset < segmentEnd) { if ( dataView.getUint8(offset) === 0x1c && // tag marker dataView.getUint8(offset + 1) === 0x02 // record number, only handles v2 ) { tagCode = dataView.getUint8(offset + 2) if ( (!includeTags || includeTags[tagCode]) && (!excludeTags || !excludeTags[tagCode]) ) { tagSize = dataView.getInt16(offset + 3) value = getTagValue(tagCode, data.iptc, dataView, offset + 5, tagSize) data.iptc[tagCode] = combineTagValues(data.iptc[tagCode], value) if (data.iptcOffsets) { data.iptcOffsets[tagCode] = offset } } } offset += 1 } } /** * Tests if field segment starts at offset. * * @param {DataView} dataView Data view interface * @param {number} offset Segment offset * @returns {boolean} True if '8BIM' exists at offset */ function isSegmentStart(dataView, offset) { return ( dataView.getUint32(offset) === 0x3842494d && // Photoshop segment start dataView.getUint16(offset + 4) === 0x0404 // IPTC segment start ) } /** * Returns header length. * * @param {DataView} dataView Data view interface * @param {number} offset Segment offset * @returns {number} Header length */ function getHeaderLength(dataView, offset) { var length = dataView.getUint8(offset + 7) if (length % 2 !== 0) length += 1 // Check for pre photoshop 6 format if (length === 0) { // Always 4 length = 4 } return length } loadImage.parseIptcData = function (dataView, offset, length, data, options) { if (options.disableIptc) { return } var markerLength = offset + length while (offset + 8 < markerLength) { if (isSegmentStart(dataView, offset)) { var headerLength = getHeaderLength(dataView, offset) var segmentOffset = offset + 8 + headerLength if (segmentOffset > markerLength) { // eslint-disable-next-line no-console console.log('Invalid IPTC data: Invalid segment offset.') break } var segmentLength = dataView.getUint16(offset + 6 + headerLength) if (offset + segmentLength > markerLength) { // eslint-disable-next-line no-console console.log('Invalid IPTC data: Invalid segment size.') break } // Create the iptc object to store the tags: data.iptc = new IptcMap() if (!options.disableIptcOffsets) { data.iptcOffsets = new IptcMap() } parseIptcTags( dataView, segmentOffset, segmentLength, data, options.includeIptcTags, options.excludeIptcTags || { 202: true } // ObjectPreviewData ) return } // eslint-disable-next-line no-param-reassign offset += 1 } } // Registers this IPTC parser for the APP13 JPEG metadata segment: loadImage.metaDataParsers.jpeg[0xffed].push(loadImage.parseIptcData) loadImage.IptcMap = IptcMap // Adds the following properties to the parseMetaData callback data: // - iptc: The iptc tags, parsed by the parseIptcData method // Adds the following options to the parseMetaData method: // - disableIptc: Disables IPTC parsing when true. // - disableIptcOffsets: Disables storing IPTC tag offsets when true. // - includeIptcTags: A map of IPTC tags to include for parsing. // - excludeIptcTags: A map of IPTC tags to exclude from parsing. })