exifreader
Advanced tools
Comparing version 4.21.1 to 4.22.0
@@ -7,2 +7,3 @@ /// <reference types="node" /> | ||
interface FileTags { | ||
'FileType'?: 'TIFF' | 'JPEG' | 'PNG' | 'HEIC' | 'AVIF' | 'WebP' | 'GIF', | ||
'Bits Per Sample'?: NumberFileTag, | ||
@@ -9,0 +10,0 @@ 'Image Height'?: NumberFileTag, |
{ | ||
"name": "exifreader", | ||
"version": "4.21.1", | ||
"version": "4.22.0", | ||
"description": "Library that parses Exif metadata in images.", | ||
@@ -39,3 +39,3 @@ "author": "Mattias Wallander <mattias@wallander.eu>", | ||
"eslint-plugin-cypress": "^2.15.1", | ||
"husky": "^8.0.0", | ||
"husky": "^9.0.11", | ||
"mocha": "^10.2.0", | ||
@@ -68,10 +68,10 @@ "npm-run-all": "^4.1.5", | ||
"postinstall": "node bin/build.js --only-with-config", | ||
"prepare": "husky install" | ||
"prepare": "husky" | ||
}, | ||
"nyc": { | ||
"check-coverage": true, | ||
"statements": 95, | ||
"branches": 89, | ||
"statements": 93, | ||
"branches": 88, | ||
"functions": 98, | ||
"lines": 95, | ||
"lines": 93, | ||
"reporter": [ | ||
@@ -78,0 +78,0 @@ "lcov", |
@@ -24,9 +24,11 @@ ExifReader | ||
| JPEG | **yes** | **yes** | **yes** | **yes** | **yes** | **some*** | **yes** | **yes** | | ||
| TIFF | **yes** | **yes** | **yes** | **yes** | ??? | ??? | no | no | | ||
| TIFF | **yes** | **yes** | **yes** | **yes** | ??? | ??? | N/A | N/A | | ||
| PNG | **yes** | **yes** | **yes** | **yes** | ??? | ??? | no | **yes** | | ||
| HEIC/HEIF | **yes** | no | no | **yes** | ??? | ??? | no | no | | ||
| HEIC/HEIF | **yes** | no | **yes** | **yes** | ??? | ??? | no | no | | ||
| AVIF | **yes** | no | **yes** | **yes** | ??? | ??? | no | no | | ||
| WebP | **yes** | no | **yes** | **yes** | ??? | ??? | **yes** | **yes** | | ||
| GIF | no | no | no | no | no | no | no | **yes** | | ||
| GIF | N/A | N/A | N/A | N/A | N/A | N/A | N/A | **yes** | | ||
- `Image details` = image width, height, etc. read from image header. | ||
- `N/A` = The feature is not applicable to this file type. | ||
- `???` = may be supported in any file type using Exif but it has only been tested | ||
@@ -33,0 +35,0 @@ on JPEGs. |
@@ -20,4 +20,5 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
USE_HEIC: true, | ||
USE_AVIF: true, | ||
USE_WEBP: true, | ||
USE_GIF: true | ||
}; |
@@ -416,2 +416,3 @@ /** | ||
} | ||
foundMetaData = true; | ||
} | ||
@@ -418,0 +419,0 @@ |
@@ -5,4 +5,3 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
import {getStringFromDataView} from './utils.js'; | ||
import Constants from './constants.js'; | ||
import {parseBox, findOffsets} from './image-header-iso-bmff.js'; | ||
@@ -14,2 +13,8 @@ export default { | ||
/** | ||
* Checks if the provided data view represents a HEIC/HEIF file. | ||
* | ||
* @param {DataView} dataView - The data view to check. | ||
* @returns {boolean} True if the data view represents a HEIC/HEIF file, false otherwise. | ||
*/ | ||
function isHeicFile(dataView) { | ||
@@ -20,172 +25,20 @@ if (!dataView) { | ||
const HEIC_ID = 'ftyp'; | ||
const HEIC_ID_OFFSET = 4; | ||
const HEIC_MAJOR_BRANDS = ['heic', 'heix', 'hevc', 'hevx', 'heim', 'heis', 'hevm', 'hevs', 'mif1']; | ||
const HEIC_MAJOR_BRAND_LENGTH = 4; | ||
const heicMajorBrand = getStringFromDataView(dataView, HEIC_ID_OFFSET + HEIC_ID.length, HEIC_MAJOR_BRAND_LENGTH); | ||
return (getStringFromDataView(dataView, HEIC_ID_OFFSET, HEIC_ID.length) === HEIC_ID) | ||
&& (HEIC_MAJOR_BRANDS.indexOf(heicMajorBrand) !== -1); | ||
try { | ||
const headerBox = parseBox(dataView, 0); | ||
return headerBox && HEIC_MAJOR_BRANDS.indexOf(headerBox.majorBrand) !== -1; | ||
} catch (error) { | ||
return false; | ||
} | ||
} | ||
/** | ||
* Finds the offsets of a HEIC file in the provided data view. | ||
* | ||
* @param {DataView} dataView - The data view to find offsets in. | ||
* @returns {Object} An object containing the offsets of the TIFF header, XMP chunks, ICC chunks, and a boolean indicating if any of these exist. | ||
*/ | ||
function findHeicOffsets(dataView) { | ||
if (Constants.USE_EXIF || Constants.USE_ICC) { | ||
const {offset: metaOffset, length: metaLength} = findMetaAtom(dataView); | ||
if (metaOffset === undefined) { | ||
return {hasAppMarkers: false}; | ||
} | ||
const metaEndOffset = Math.min(metaOffset + metaLength, dataView.byteLength); | ||
const {exifItemOffset, ilocOffset, colrOffset} = findMetaItems(dataView, metaOffset, metaEndOffset); | ||
const exifOffset = findExifOffset(dataView, exifItemOffset, ilocOffset, metaEndOffset); | ||
const iccChunks = findIccChunks(dataView, colrOffset, metaEndOffset); | ||
return { | ||
hasAppMarkers: (exifOffset !== undefined) || (iccChunks !== undefined), | ||
tiffHeaderOffset: exifOffset, | ||
iccChunks | ||
}; | ||
} | ||
return {hasAppMarkers: false}; | ||
return findOffsets(dataView); | ||
} | ||
function findMetaAtom(dataView) { | ||
const ATOM_LENGTH_SIZE = 4; | ||
const ATOM_TYPE_SIZE = 4; | ||
const ATOM_MIN_LENGTH = 8; | ||
const ATOM_TYPE_OFFSET = 4; | ||
let offset = 0; | ||
while (offset + ATOM_LENGTH_SIZE + ATOM_TYPE_SIZE <= dataView.byteLength) { | ||
const atomLength = getAtomLength(dataView, offset); | ||
if (atomLength >= ATOM_MIN_LENGTH) { | ||
const atomType = getStringFromDataView(dataView, offset + ATOM_TYPE_OFFSET, ATOM_TYPE_SIZE); | ||
if (atomType === 'meta') { | ||
return { | ||
offset, | ||
length: atomLength | ||
}; | ||
} | ||
} | ||
offset += atomLength; | ||
} | ||
return { | ||
offset: undefined, | ||
length: 0 | ||
}; | ||
} | ||
function getAtomLength(dataView, offset) { | ||
const ATOM_EXTENDED_SIZE_LOW_OFFSET = 12; | ||
const atomLength = dataView.getUint32(offset); | ||
if (extendsToEndOfFile(atomLength)) { | ||
return dataView.byteLength - offset; | ||
} | ||
if (hasExtendedSize(atomLength)) { | ||
if (hasEmptyHighBits(dataView, offset)) { | ||
// It's a bit tricky to handle 64 bit numbers in JavaScript. Let's | ||
// wait until there are real-world examples where it is necessary. | ||
return dataView.getUint32(offset + ATOM_EXTENDED_SIZE_LOW_OFFSET); | ||
} | ||
} | ||
return atomLength; | ||
} | ||
function extendsToEndOfFile(atomLength) { | ||
return atomLength === 0; | ||
} | ||
function hasExtendedSize(atomLength) { | ||
return atomLength === 1; | ||
} | ||
function hasEmptyHighBits(dataView, offset) { | ||
const ATOM_EXTENDED_SIZE_OFFSET = 8; | ||
return dataView.getUint32(offset + ATOM_EXTENDED_SIZE_OFFSET) === 0; | ||
} | ||
function findMetaItems(dataView, offset, metaEndOffset) { | ||
const STRING_SIZE = 4; | ||
const ITEM_INDEX_REL_OFFSET = -4; | ||
const offsets = { | ||
ilocOffset: undefined, | ||
exifItemOffset: undefined, | ||
colrOffset: undefined | ||
}; | ||
while ((offset + STRING_SIZE <= metaEndOffset) | ||
&& (!offsets.ilocOffset || !offsets.exifItemOffset || !offsets.colrOffset)) { | ||
const itemName = getStringFromDataView(dataView, offset, STRING_SIZE); | ||
if (Constants.USE_EXIF && (itemName === 'iloc')) { | ||
offsets.ilocOffset = offset; | ||
} else if (Constants.USE_EXIF && (itemName === 'Exif')) { | ||
offsets.exifItemOffset = offset + ITEM_INDEX_REL_OFFSET; | ||
} else if (Constants.USE_ICC && (itemName === 'colr')) { | ||
offsets.colrOffset = offset + ITEM_INDEX_REL_OFFSET; | ||
} | ||
offset++; | ||
} | ||
return offsets; | ||
} | ||
function findExifOffset(dataView, exifItemOffset, offset, metaEndOffset) { | ||
const EXIF_ITEM_OFFSET_SIZE = 2; | ||
const ILOC_DATA_OFFSET = 12; | ||
const EXIF_POINTER_OFFSET = 8; | ||
const EXIF_POINTER_SIZE = 4; | ||
const EXIF_PREFIX_LENGTH_OFFSET = 4; | ||
const ILOC_ITEM_SIZE = 16; | ||
if (!offset || !exifItemOffset || (exifItemOffset + EXIF_ITEM_OFFSET_SIZE > metaEndOffset)) { | ||
return undefined; | ||
} | ||
const exifItemIndex = dataView.getUint16(exifItemOffset); | ||
offset += ILOC_DATA_OFFSET; | ||
while (offset + ILOC_ITEM_SIZE <= metaEndOffset) { | ||
const itemIndex = dataView.getUint16(offset); | ||
if (itemIndex === exifItemIndex) { | ||
const exifPointer = dataView.getUint32(offset + EXIF_POINTER_OFFSET); | ||
if (exifPointer + EXIF_POINTER_SIZE <= dataView.byteLength) { | ||
const exifOffset = dataView.getUint32(exifPointer); | ||
const prefixLength = exifOffset + EXIF_PREFIX_LENGTH_OFFSET; | ||
return exifPointer + prefixLength; | ||
} | ||
} | ||
offset += ILOC_ITEM_SIZE; | ||
} | ||
return undefined; | ||
} | ||
function findIccChunks(dataView, offset, metaEndOffset) { | ||
const ITEM_TYPE_OFFSET = 8; | ||
const ITEM_TYPE_SIZE = 4; | ||
const ITEM_CONTENT_OFFSET = 12; | ||
if (!offset || (offset + ITEM_CONTENT_OFFSET > metaEndOffset)) { | ||
return undefined; | ||
} | ||
const colorType = getStringFromDataView(dataView, offset + ITEM_TYPE_OFFSET, ITEM_TYPE_SIZE); | ||
if ((colorType !== 'prof') && (colorType !== 'rICC')) { | ||
return undefined; | ||
} | ||
return [{ | ||
offset: offset + ITEM_CONTENT_OFFSET, | ||
length: getAtomLength(dataView, offset) - ITEM_CONTENT_OFFSET, | ||
chunkNumber: 1, | ||
chunksTotal: 1 | ||
}]; | ||
} |
@@ -10,2 +10,3 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
import Heic from './image-header-heic.js'; | ||
import Avif from './image-header-avif.js'; | ||
import Webp from './image-header-webp.js'; | ||
@@ -36,2 +37,6 @@ import Gif from './image-header-gif.js'; | ||
if (Constants.USE_AVIF && Avif.isAvifFile(dataView)) { | ||
return addFileType(Avif.findAvifOffsets(dataView), 'avif', 'AVIF'); | ||
} | ||
if (Constants.USE_WEBP && Webp.isWebpFile(dataView)) { | ||
@@ -38,0 +43,0 @@ return addFileType(Webp.findOffsets(dataView), 'webp', 'WebP'); |
@@ -84,3 +84,3 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
{ | ||
test: /\/(exif-reader|image-header-?(tiff|jpeg|png|heic|webp|gif)?|tags|tag-names)\.js$/, | ||
test: /\/(exif-reader|image-header-?(tiff|jpeg|png|heic|avif|iso-bmff|webp|gif)?|tags|tag-names)\.js$/, | ||
loader: 'string-replace-loader', | ||
@@ -110,2 +110,3 @@ options: { | ||
'heic', | ||
'avif', | ||
'webp', | ||
@@ -112,0 +113,0 @@ 'gif' |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
780495
61
8172
624