UNPKG

7.04 kBJavaScriptView Raw
1/*
2 * JavaScript Load Image IPTC Parser
3 * https://github.com/blueimp/JavaScript-Load-Image
4 *
5 * Copyright 2013, Sebastian Tschan
6 * Copyright 2018, Dave Bevan
7 * https://blueimp.net
8 *
9 * Licensed under the MIT license:
10 * https://opensource.org/licenses/MIT
11 */
12
13/* global define, module, require, DataView */
14
15;(function (factory) {
16 'use strict'
17 if (typeof define === 'function' && define.amd) {
18 // Register as an anonymous AMD module:
19 define(['./load-image', './load-image-meta'], factory)
20 } else if (typeof module === 'object' && module.exports) {
21 factory(require('./load-image'), require('./load-image-meta'))
22 } else {
23 // Browser globals:
24 factory(window.loadImage)
25 }
26})(function (loadImage) {
27 'use strict'
28
29 /**
30 * IPTC tag map
31 *
32 * @name IptcMap
33 * @class
34 */
35 function IptcMap() {}
36
37 IptcMap.prototype.map = {
38 ObjectName: 5
39 }
40
41 IptcMap.prototype.types = {
42 0: 'Uint16', // ApplicationRecordVersion
43 200: 'Uint16', // ObjectPreviewFileFormat
44 201: 'Uint16', // ObjectPreviewFileVersion
45 202: 'binary' // ObjectPreviewData
46 }
47
48 /**
49 * Retrieves IPTC tag value
50 *
51 * @param {number|string} id IPTC tag code or name
52 * @returns {object} IPTC tag value
53 */
54 IptcMap.prototype.get = function (id) {
55 return this[id] || this[this.map[id]]
56 }
57
58 /**
59 * Retrieves string for the given DataView and range
60 *
61 * @param {DataView} dataView Data view interface
62 * @param {number} offset Offset start
63 * @param {number} length Offset length
64 * @returns {string} String value
65 */
66 function getStringValue(dataView, offset, length) {
67 var outstr = ''
68 var end = offset + length
69 for (var n = offset; n < end; n += 1) {
70 outstr += String.fromCharCode(dataView.getUint8(n))
71 }
72 return outstr
73 }
74
75 /**
76 * Retrieves tag value for the given DataView and range
77 *
78 * @param {number} tagCode tag code
79 * @param {IptcMap} map IPTC tag map
80 * @param {DataView} dataView Data view interface
81 * @param {number} offset Range start
82 * @param {number} length Range length
83 * @returns {object} Tag value
84 */
85 function getTagValue(tagCode, map, dataView, offset, length) {
86 if (map.types[tagCode] === 'binary') {
87 return new Blob([dataView.buffer.slice(offset, offset + length)])
88 }
89 if (map.types[tagCode] === 'Uint16') {
90 return dataView.getUint16(offset)
91 }
92 return getStringValue(dataView, offset, length)
93 }
94
95 /**
96 * Combines IPTC value with existing ones.
97 *
98 * @param {object} value Existing IPTC field value
99 * @param {object} newValue New IPTC field value
100 * @returns {object} Resulting IPTC field value
101 */
102 function combineTagValues(value, newValue) {
103 if (value === undefined) return newValue
104 if (value instanceof Array) {
105 value.push(newValue)
106 return value
107 }
108 return [value, newValue]
109 }
110
111 /**
112 * Parses IPTC tags.
113 *
114 * @param {DataView} dataView Data view interface
115 * @param {number} segmentOffset Segment offset
116 * @param {number} segmentLength Segment length
117 * @param {object} data Data export object
118 * @param {object} includeTags Map of tags to include
119 * @param {object} excludeTags Map of tags to exclude
120 */
121 function parseIptcTags(
122 dataView,
123 segmentOffset,
124 segmentLength,
125 data,
126 includeTags,
127 excludeTags
128 ) {
129 var value, tagSize, tagCode
130 var segmentEnd = segmentOffset + segmentLength
131 var offset = segmentOffset
132 while (offset < segmentEnd) {
133 if (
134 dataView.getUint8(offset) === 0x1c && // tag marker
135 dataView.getUint8(offset + 1) === 0x02 // record number, only handles v2
136 ) {
137 tagCode = dataView.getUint8(offset + 2)
138 if (
139 (!includeTags || includeTags[tagCode]) &&
140 (!excludeTags || !excludeTags[tagCode])
141 ) {
142 tagSize = dataView.getInt16(offset + 3)
143 value = getTagValue(tagCode, data.iptc, dataView, offset + 5, tagSize)
144 data.iptc[tagCode] = combineTagValues(data.iptc[tagCode], value)
145 if (data.iptcOffsets) {
146 data.iptcOffsets[tagCode] = offset
147 }
148 }
149 }
150 offset += 1
151 }
152 }
153
154 /**
155 * Tests if field segment starts at offset.
156 *
157 * @param {DataView} dataView Data view interface
158 * @param {number} offset Segment offset
159 * @returns {boolean} True if '8BIM<EOT><EOT>' exists at offset
160 */
161 function isSegmentStart(dataView, offset) {
162 return (
163 dataView.getUint32(offset) === 0x3842494d && // Photoshop segment start
164 dataView.getUint16(offset + 4) === 0x0404 // IPTC segment start
165 )
166 }
167
168 /**
169 * Returns header length.
170 *
171 * @param {DataView} dataView Data view interface
172 * @param {number} offset Segment offset
173 * @returns {number} Header length
174 */
175 function getHeaderLength(dataView, offset) {
176 var length = dataView.getUint8(offset + 7)
177 if (length % 2 !== 0) length += 1
178 // Check for pre photoshop 6 format
179 if (length === 0) {
180 // Always 4
181 length = 4
182 }
183 return length
184 }
185
186 loadImage.parseIptcData = function (dataView, offset, length, data, options) {
187 if (options.disableIptc) {
188 return
189 }
190 var markerLength = offset + length
191 while (offset + 8 < markerLength) {
192 if (isSegmentStart(dataView, offset)) {
193 var headerLength = getHeaderLength(dataView, offset)
194 var segmentOffset = offset + 8 + headerLength
195 if (segmentOffset > markerLength) {
196 // eslint-disable-next-line no-console
197 console.log('Invalid IPTC data: Invalid segment offset.')
198 break
199 }
200 var segmentLength = dataView.getUint16(offset + 6 + headerLength)
201 if (offset + segmentLength > markerLength) {
202 // eslint-disable-next-line no-console
203 console.log('Invalid IPTC data: Invalid segment size.')
204 break
205 }
206 // Create the iptc object to store the tags:
207 data.iptc = new IptcMap()
208 if (!options.disableIptcOffsets) {
209 data.iptcOffsets = new IptcMap()
210 }
211 parseIptcTags(
212 dataView,
213 segmentOffset,
214 segmentLength,
215 data,
216 options.includeIptcTags,
217 options.excludeIptcTags || { 202: true } // ObjectPreviewData
218 )
219 return
220 }
221 // eslint-disable-next-line no-param-reassign
222 offset += 1
223 }
224 }
225
226 // Registers this IPTC parser for the APP13 JPEG metadata segment:
227 loadImage.metaDataParsers.jpeg[0xffed].push(loadImage.parseIptcData)
228
229 loadImage.IptcMap = IptcMap
230
231 // Adds the following properties to the parseMetaData callback data:
232 // - iptc: The iptc tags, parsed by the parseIptcData method
233
234 // Adds the following options to the parseMetaData method:
235 // - disableIptc: Disables IPTC parsing when true.
236 // - disableIptcOffsets: Disables storing IPTC tag offsets when true.
237 // - includeIptcTags: A map of IPTC tags to include for parsing.
238 // - excludeIptcTags: A map of IPTC tags to exclude from parsing.
239})