267 lines
6.0 KiB
JavaScript
267 lines
6.0 KiB
JavaScript
'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
|
|
}
|
|
}
|