UNPKG

@aidin36/xmp

Version:

Read and write XMP metadata from/to various media formats

86 lines (85 loc) 3.76 kB
"use strict"; /* * This file is part of @aidin36/xmp Javascript package. * * @aidin36/xmp is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) any * later version. * * @aidin36/xmp is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details * * You should have received a copy of the GNU Lesser General Public License * along with @aidin26/xmp. If not, see <https://www.gnu.org/licenses/>. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.jpegExtractXMP = void 0; const utils_1 = require("./utils"); const FF = 0xff; // Start Of Image (FF D8) const SOI = 0xd8; // Start of APP segments are FF + a marker const APP1 = 0xe1; // End Of Image (FF D9) // const EOI = 0xd9 const isJpeg = (image) => image[0] === FF && image[1] === SOI; /** * Finds and returns APP segments that starts with the 'marker'. * There can be more than one segment of each APP. * Returns an empty array if nothing found. */ const extractAppSegments = (image, marker) => { // See: https://en.wikipedia.org/wiki/JPEG_File_Interchange_Format // https://www.w3.org/Graphics/JPEG/jfif3.pdf const isStartOfAppSegment = (element, i, imageData) => element === FF && imageData[i + 1] === marker; const result = []; let remainderData = image; while (remainderData.length > 0) { const segmentStartIndex = remainderData.findIndex(isStartOfAppSegment); if (segmentStartIndex === -1) { break; } // The next two bytes are the size of the APP segment. const segmentSize = (0, utils_1.bytes2Uint16)(remainderData.subarray(segmentStartIndex + 2, segmentStartIndex + 4)); // We want the bytes from after the 'size' marker until the end of data. So we start from // four (two for the APP marker and two for the size.) // 'size' excludes the APP marker, but includes the 'size' bytes. So we add 2 bytes to it. result.push(remainderData.subarray(segmentStartIndex + 4, segmentStartIndex + 2 + segmentSize)); remainderData = remainderData.subarray(segmentStartIndex + segmentSize + 1); } return result; }; /** * @internal * Finds and returns XMP data as string. * Returns 'undefined' if nothing found. */ const jpegExtractXMP = (image) => { if (image.length < 3) { throw Error('The file is empty or is not an image'); } if (!isJpeg(image)) { throw Error('The file is not a JPEG file.'); } // Finding an APP1 segment that starts with the XMP identifier const foundSegments = extractAppSegments(image, APP1).filter((segment) => { // The structure of the segment is: // | Marker (0xFFE1) | Length (2 bytes) | Identifier | Null byte (0x00) const nullIndex = segment.findIndex((element) => element === 0x00); const Identifier = (0, utils_1.binArray2String)(segment.subarray(0, nullIndex)); return Identifier === 'http://ns.adobe.com/xap/1.0/'; }); if (foundSegments.length === 0) { return undefined; } if (foundSegments.length > 1) { throw Error('Found more than one XMP segment inside the image.'); } const xmpSegment = foundSegments[0]; // XXX: Can we change the algorithm so we don't have to search for the 'null' twice? const nullIndex = xmpSegment.findIndex((element) => element === 0x00); return xmpSegment.subarray(nullIndex + 1); }; exports.jpegExtractXMP = jpegExtractXMP;