astro-ghostcms/.pnpm-store/v3/files/6a/21ead882aa5ff88d2a64501bca1...

271 lines
6.3 KiB
Plaintext

/* eslint-disable no-bitwise */
/* eslint-disable consistent-return */
'use strict';
//////////////////////////////////////////////////////////////////////////
// Helpers
//
function error(message, code) {
var err = new Error(message);
err.code = code;
return err;
}
function utf8_decode(str) {
try {
return decodeURIComponent(escape(str));
} catch (_) {
return str;
}
}
//////////////////////////////////////////////////////////////////////////
// Exif parser
//
// Input:
// - jpeg_bin: Uint8Array - jpeg file
// - exif_start: Number - start of TIFF header (after Exif\0\0)
// - exif_end: Number - end of Exif segment
// - on_entry: Number - callback
//
function ExifParser(jpeg_bin, exif_start, exif_end) {
// Uint8Array, exif without signature (which isn't included in offsets)
this.input = jpeg_bin.subarray(exif_start, exif_end);
// offset correction for `on_entry` callback
this.start = exif_start;
// Check TIFF header (includes byte alignment and first IFD offset)
var sig = String.fromCharCode.apply(null, this.input.subarray(0, 4));
if (sig !== 'II\x2A\0' && sig !== 'MM\0\x2A') {
throw error('invalid TIFF signature', 'EBADDATA');
}
// true if motorola (big endian) byte alignment, false if intel
this.big_endian = sig[0] === 'M';
}
ExifParser.prototype.each = function (on_entry) {
// allow premature exit
this.aborted = false;
var offset = this.read_uint32(4);
this.ifds_to_read = [ {
id: 0,
offset: offset
} ];
while (this.ifds_to_read.length > 0 && !this.aborted) {
var i = this.ifds_to_read.shift();
if (!i.offset) continue;
this.scan_ifd(i.id, i.offset, on_entry);
}
};
ExifParser.prototype.read_uint16 = function (offset) {
var d = this.input;
if (offset + 2 > d.length) throw error('unexpected EOF', 'EBADDATA');
return this.big_endian ?
d[offset] * 0x100 + d[offset + 1] :
d[offset] + d[offset + 1] * 0x100;
};
ExifParser.prototype.read_uint32 = function (offset) {
var d = this.input;
if (offset + 4 > d.length) throw error('unexpected EOF', 'EBADDATA');
return this.big_endian ?
d[offset] * 0x1000000 + d[offset + 1] * 0x10000 + d[offset + 2] * 0x100 + d[offset + 3] :
d[offset] + d[offset + 1] * 0x100 + d[offset + 2] * 0x10000 + d[offset + 3] * 0x1000000;
};
ExifParser.prototype.is_subifd_link = function (ifd, tag) {
return (ifd === 0 && tag === 0x8769) || // SubIFD
(ifd === 0 && tag === 0x8825) || // GPS Info
(ifd === 0x8769 && tag === 0xA005); // Interop IFD
};
// Returns byte length of a single component of a given format
//
ExifParser.prototype.exif_format_length = function (format) {
switch (format) {
case 1: // byte
case 2: // ascii
case 6: // sbyte
case 7: // undefined
return 1;
case 3: // short
case 8: // sshort
return 2;
case 4: // long
case 9: // slong
case 11: // float
return 4;
case 5: // rational
case 10: // srational
case 12: // double
return 8;
default:
// unknown type
return 0;
}
};
// Reads Exif data
//
ExifParser.prototype.exif_format_read = function (format, offset) {
var v;
switch (format) {
case 1: // byte
case 2: // ascii
v = this.input[offset];
return v;
case 6: // sbyte
v = this.input[offset];
return v | (v & 0x80) * 0x1fffffe;
case 3: // short
v = this.read_uint16(offset);
return v;
case 8: // sshort
v = this.read_uint16(offset);
return v | (v & 0x8000) * 0x1fffe;
case 4: // long
v = this.read_uint32(offset);
return v;
case 9: // slong
v = this.read_uint32(offset);
return v | 0;
case 5: // rational
case 10: // srational
case 11: // float
case 12: // double
return null; // not implemented
case 7: // undefined
return null; // blob
default:
// unknown type
return null;
}
};
ExifParser.prototype.scan_ifd = function (ifd_no, offset, on_entry) {
var entry_count = this.read_uint16(offset);
offset += 2;
for (var i = 0; i < entry_count; i++) {
var tag = this.read_uint16(offset);
var format = this.read_uint16(offset + 2);
var count = this.read_uint32(offset + 4);
var comp_length = this.exif_format_length(format);
var data_length = count * comp_length;
var data_offset = data_length <= 4 ? offset + 8 : this.read_uint32(offset + 8);
var is_subifd_link = false;
if (data_offset + data_length > this.input.length) {
throw error('unexpected EOF', 'EBADDATA');
}
var value = [];
var comp_offset = data_offset;
for (var j = 0; j < count; j++, comp_offset += comp_length) {
var item = this.exif_format_read(format, comp_offset);
if (item === null) {
value = null;
break;
}
value.push(item);
}
if (Array.isArray(value) && format === 2) {
value = utf8_decode(String.fromCharCode.apply(null, value));
if (value && value[value.length - 1] === '\0') value = value.slice(0, -1);
}
if (this.is_subifd_link(ifd_no, tag)) {
if (Array.isArray(value) && Number.isInteger(value[0]) && value[0] > 0) {
this.ifds_to_read.push({
id: tag,
offset: value[0]
});
is_subifd_link = true;
}
}
var entry = {
is_big_endian: this.big_endian,
ifd: ifd_no,
tag: tag,
format: format,
count: count,
entry_offset: offset + this.start,
data_length: data_length,
data_offset: data_offset + this.start,
value: value,
is_subifd_link: is_subifd_link
};
if (on_entry(entry) === false) {
this.aborted = true;
return;
}
offset += 12;
}
if (ifd_no === 0) {
this.ifds_to_read.push({
id: 1,
offset: this.read_uint32(offset)
});
}
};
module.exports.ExifParser = ExifParser;
// returns orientation stored in Exif (1-8), 0 if none was found, -1 if error
module.exports.get_orientation = function (data) {
var orientation = 0;
try {
new ExifParser(data, 0, data.length).each(function (entry) {
if (entry.ifd === 0 && entry.tag === 0x112 && Array.isArray(entry.value)) {
orientation = entry.value[0];
return false;
}
});
return orientation;
} catch (err) {
return -1;
}
};