240 lines
6.9 KiB
JavaScript
240 lines
6.9 KiB
JavaScript
/*
|
|
* 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<EOT><EOT>' 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.
|
|
})
|