This commit is contained in:
Xes
2025-08-14 22:44:47 +02:00
parent 8ce45119b6
commit 791cb748ab
39112 changed files with 975901 additions and 0 deletions

View File

@@ -0,0 +1,460 @@
/*
* JavaScript Load Image Exif Parser
* https://github.com/blueimp/JavaScript-Load-Image
*
* Copyright 2013, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* https://opensource.org/licenses/MIT
*/
/* global define, module, require, DataView */
/* eslint-disable no-console */
;(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'
/**
* Exif tag map
*
* @name ExifMap
* @class
* @param {number|string} tagCode IFD tag code
*/
function ExifMap(tagCode) {
if (tagCode) {
Object.defineProperty(this, 'map', {
value: this.ifds[tagCode].map
})
Object.defineProperty(this, 'tags', {
value: (this.tags && this.tags[tagCode]) || {}
})
}
}
ExifMap.prototype.map = {
Orientation: 0x0112,
Thumbnail: 'ifd1',
Blob: 0x0201, // Alias for JPEGInterchangeFormat
Exif: 0x8769,
GPSInfo: 0x8825,
Interoperability: 0xa005
}
ExifMap.prototype.ifds = {
ifd1: { name: 'Thumbnail', map: ExifMap.prototype.map },
0x8769: { name: 'Exif', map: {} },
0x8825: { name: 'GPSInfo', map: {} },
0xa005: { name: 'Interoperability', map: {} }
}
/**
* Retrieves exif tag value
*
* @param {number|string} id Exif tag code or name
* @returns {object} Exif tag value
*/
ExifMap.prototype.get = function (id) {
return this[id] || this[this.map[id]]
}
/**
* Returns the Exif Thumbnail data as Blob.
*
* @param {DataView} dataView Data view interface
* @param {number} offset Thumbnail data offset
* @param {number} length Thumbnail data length
* @returns {undefined|Blob} Returns the Thumbnail Blob or undefined
*/
function getExifThumbnail(dataView, offset, length) {
if (!length) return
if (offset + length > dataView.byteLength) {
console.log('Invalid Exif data: Invalid thumbnail data.')
return
}
return new Blob(
[loadImage.bufferSlice.call(dataView.buffer, offset, offset + length)],
{
type: 'image/jpeg'
}
)
}
var ExifTagTypes = {
// byte, 8-bit unsigned int:
1: {
getValue: function (dataView, dataOffset) {
return dataView.getUint8(dataOffset)
},
size: 1
},
// ascii, 8-bit byte:
2: {
getValue: function (dataView, dataOffset) {
return String.fromCharCode(dataView.getUint8(dataOffset))
},
size: 1,
ascii: true
},
// short, 16 bit int:
3: {
getValue: function (dataView, dataOffset, littleEndian) {
return dataView.getUint16(dataOffset, littleEndian)
},
size: 2
},
// long, 32 bit int:
4: {
getValue: function (dataView, dataOffset, littleEndian) {
return dataView.getUint32(dataOffset, littleEndian)
},
size: 4
},
// rational = two long values, first is numerator, second is denominator:
5: {
getValue: function (dataView, dataOffset, littleEndian) {
return (
dataView.getUint32(dataOffset, littleEndian) /
dataView.getUint32(dataOffset + 4, littleEndian)
)
},
size: 8
},
// slong, 32 bit signed int:
9: {
getValue: function (dataView, dataOffset, littleEndian) {
return dataView.getInt32(dataOffset, littleEndian)
},
size: 4
},
// srational, two slongs, first is numerator, second is denominator:
10: {
getValue: function (dataView, dataOffset, littleEndian) {
return (
dataView.getInt32(dataOffset, littleEndian) /
dataView.getInt32(dataOffset + 4, littleEndian)
)
},
size: 8
}
}
// undefined, 8-bit byte, value depending on field:
ExifTagTypes[7] = ExifTagTypes[1]
/**
* Returns Exif tag value.
*
* @param {DataView} dataView Data view interface
* @param {number} tiffOffset TIFF offset
* @param {number} offset Tag offset
* @param {number} type Tag type
* @param {number} length Tag length
* @param {boolean} littleEndian Little endian encoding
* @returns {object} Tag value
*/
function getExifValue(
dataView,
tiffOffset,
offset,
type,
length,
littleEndian
) {
var tagType = ExifTagTypes[type]
var tagSize
var dataOffset
var values
var i
var str
var c
if (!tagType) {
console.log('Invalid Exif data: Invalid tag type.')
return
}
tagSize = tagType.size * length
// Determine if the value is contained in the dataOffset bytes,
// or if the value at the dataOffset is a pointer to the actual data:
dataOffset =
tagSize > 4
? tiffOffset + dataView.getUint32(offset + 8, littleEndian)
: offset + 8
if (dataOffset + tagSize > dataView.byteLength) {
console.log('Invalid Exif data: Invalid data offset.')
return
}
if (length === 1) {
return tagType.getValue(dataView, dataOffset, littleEndian)
}
values = []
for (i = 0; i < length; i += 1) {
values[i] = tagType.getValue(
dataView,
dataOffset + i * tagType.size,
littleEndian
)
}
if (tagType.ascii) {
str = ''
// Concatenate the chars:
for (i = 0; i < values.length; i += 1) {
c = values[i]
// Ignore the terminating NULL byte(s):
if (c === '\u0000') {
break
}
str += c
}
return str
}
return values
}
/**
* Determines if the given tag should be included.
*
* @param {object} includeTags Map of tags to include
* @param {object} excludeTags Map of tags to exclude
* @param {number|string} tagCode Tag code to check
* @returns {boolean} True if the tag should be included
*/
function shouldIncludeTag(includeTags, excludeTags, tagCode) {
return (
(!includeTags || includeTags[tagCode]) &&
(!excludeTags || excludeTags[tagCode] !== true)
)
}
/**
* Parses Exif tags.
*
* @param {DataView} dataView Data view interface
* @param {number} tiffOffset TIFF offset
* @param {number} dirOffset Directory offset
* @param {boolean} littleEndian Little endian encoding
* @param {ExifMap} tags Map to store parsed exif tags
* @param {ExifMap} tagOffsets Map to store parsed exif tag offsets
* @param {object} includeTags Map of tags to include
* @param {object} excludeTags Map of tags to exclude
* @returns {number} Next directory offset
*/
function parseExifTags(
dataView,
tiffOffset,
dirOffset,
littleEndian,
tags,
tagOffsets,
includeTags,
excludeTags
) {
var tagsNumber, dirEndOffset, i, tagOffset, tagNumber, tagValue
if (dirOffset + 6 > dataView.byteLength) {
console.log('Invalid Exif data: Invalid directory offset.')
return
}
tagsNumber = dataView.getUint16(dirOffset, littleEndian)
dirEndOffset = dirOffset + 2 + 12 * tagsNumber
if (dirEndOffset + 4 > dataView.byteLength) {
console.log('Invalid Exif data: Invalid directory size.')
return
}
for (i = 0; i < tagsNumber; i += 1) {
tagOffset = dirOffset + 2 + 12 * i
tagNumber = dataView.getUint16(tagOffset, littleEndian)
if (!shouldIncludeTag(includeTags, excludeTags, tagNumber)) continue
tagValue = getExifValue(
dataView,
tiffOffset,
tagOffset,
dataView.getUint16(tagOffset + 2, littleEndian), // tag type
dataView.getUint32(tagOffset + 4, littleEndian), // tag length
littleEndian
)
tags[tagNumber] = tagValue
if (tagOffsets) {
tagOffsets[tagNumber] = tagOffset
}
}
// Return the offset to the next directory:
return dataView.getUint32(dirEndOffset, littleEndian)
}
/**
* Parses tags in a given IFD (Image File Directory).
*
* @param {object} data Data object to store exif tags and offsets
* @param {number|string} tagCode IFD tag code
* @param {DataView} dataView Data view interface
* @param {number} tiffOffset TIFF offset
* @param {boolean} littleEndian Little endian encoding
* @param {object} includeTags Map of tags to include
* @param {object} excludeTags Map of tags to exclude
*/
function parseExifIFD(
data,
tagCode,
dataView,
tiffOffset,
littleEndian,
includeTags,
excludeTags
) {
var dirOffset = data.exif[tagCode]
if (dirOffset) {
data.exif[tagCode] = new ExifMap(tagCode)
if (data.exifOffsets) {
data.exifOffsets[tagCode] = new ExifMap(tagCode)
}
parseExifTags(
dataView,
tiffOffset,
tiffOffset + dirOffset,
littleEndian,
data.exif[tagCode],
data.exifOffsets && data.exifOffsets[tagCode],
includeTags && includeTags[tagCode],
excludeTags && excludeTags[tagCode]
)
}
}
loadImage.parseExifData = function (dataView, offset, length, data, options) {
if (options.disableExif) {
return
}
var includeTags = options.includeExifTags
var excludeTags = options.excludeExifTags || {
0x8769: {
// ExifIFDPointer
0x927c: true // MakerNote
}
}
var tiffOffset = offset + 10
var littleEndian
var dirOffset
var thumbnailIFD
// Check for the ASCII code for "Exif" (0x45786966):
if (dataView.getUint32(offset + 4) !== 0x45786966) {
// No Exif data, might be XMP data instead
return
}
if (tiffOffset + 8 > dataView.byteLength) {
console.log('Invalid Exif data: Invalid segment size.')
return
}
// Check for the two null bytes:
if (dataView.getUint16(offset + 8) !== 0x0000) {
console.log('Invalid Exif data: Missing byte alignment offset.')
return
}
// Check the byte alignment:
switch (dataView.getUint16(tiffOffset)) {
case 0x4949:
littleEndian = true
break
case 0x4d4d:
littleEndian = false
break
default:
console.log('Invalid Exif data: Invalid byte alignment marker.')
return
}
// Check for the TIFF tag marker (0x002A):
if (dataView.getUint16(tiffOffset + 2, littleEndian) !== 0x002a) {
console.log('Invalid Exif data: Missing TIFF marker.')
return
}
// Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
dirOffset = dataView.getUint32(tiffOffset + 4, littleEndian)
// Create the exif object to store the tags:
data.exif = new ExifMap()
if (!options.disableExifOffsets) {
data.exifOffsets = new ExifMap()
data.exifTiffOffset = tiffOffset
data.exifLittleEndian = littleEndian
}
// Parse the tags of the main image directory (IFD0) and retrieve the
// offset to the next directory (IFD1), usually the thumbnail directory:
dirOffset = parseExifTags(
dataView,
tiffOffset,
tiffOffset + dirOffset,
littleEndian,
data.exif,
data.exifOffsets,
includeTags,
excludeTags
)
if (dirOffset && shouldIncludeTag(includeTags, excludeTags, 'ifd1')) {
data.exif.ifd1 = dirOffset
if (data.exifOffsets) {
data.exifOffsets.ifd1 = tiffOffset + dirOffset
}
}
Object.keys(data.exif.ifds).forEach(function (tagCode) {
parseExifIFD(
data,
tagCode,
dataView,
tiffOffset,
littleEndian,
includeTags,
excludeTags
)
})
thumbnailIFD = data.exif.ifd1
// Check for JPEG Thumbnail offset and data length:
if (thumbnailIFD && thumbnailIFD[0x0201]) {
thumbnailIFD[0x0201] = getExifThumbnail(
dataView,
tiffOffset + thumbnailIFD[0x0201],
thumbnailIFD[0x0202] // Thumbnail data length
)
}
}
// Registers the Exif parser for the APP1 JPEG metadata segment:
loadImage.metaDataParsers.jpeg[0xffe1].push(loadImage.parseExifData)
loadImage.exifWriters = {
// Orientation writer:
0x0112: function (buffer, data, value) {
var orientationOffset = data.exifOffsets[0x0112]
if (!orientationOffset) return buffer
var view = new DataView(buffer, orientationOffset + 8, 2)
view.setUint16(0, value, data.exifLittleEndian)
return buffer
}
}
loadImage.writeExifData = function (buffer, data, id, value) {
return loadImage.exifWriters[data.exif.map[id]](buffer, data, value)
}
loadImage.ExifMap = ExifMap
// Adds the following properties to the parseMetaData callback data:
// - exif: The parsed Exif tags
// - exifOffsets: The parsed Exif tag offsets
// - exifTiffOffset: TIFF header offset (used for offset pointers)
// - exifLittleEndian: little endian order if true, big endian if false
// Adds the following options to the parseMetaData method:
// - disableExif: Disables Exif parsing when true.
// - disableExifOffsets: Disables storing Exif tag offsets when true.
// - includeExifTags: A map of Exif tags to include for parsing.
// - excludeExifTags: A map of Exif tags to exclude from parsing.
})