@eris/exif
Advanced tools
Comparing version 0.3.1-alpha.2 to 0.3.1-alpha.3
@@ -23,8 +23,11 @@ "use strict"; | ||
switch (this.dataType) { | ||
// TODO: verify signed versions | ||
case types_1.IFDDataType.Byte: | ||
case types_1.IFDDataType.Short: | ||
case types_1.IFDDataType.Long: | ||
case types_1.IFDDataType.SignedByte: | ||
case types_1.IFDDataType.SignedShort: | ||
case types_1.IFDDataType.SignedLong: | ||
return entryReader.read(this.lengthInBytes); | ||
case types_1.IFDDataType.Rational: | ||
// TODO: fix signed rational repr | ||
case types_1.IFDDataType.SignedRational: | ||
@@ -42,5 +45,5 @@ return entryReader.read(4) / entryReader.read(4); | ||
return chars.join(''); | ||
case types_1.IFDDataType.SingleFloat: | ||
case types_1.IFDDataType.Float: | ||
return new DataView(entryReader.readAsBuffer(4).buffer).getFloat32(0); | ||
case types_1.IFDDataType.DoubleFloat: | ||
case types_1.IFDDataType.Double: | ||
return new DataView(entryReader.readAsBuffer(8).buffer).getFloat64(0); | ||
@@ -47,0 +50,0 @@ case types_1.IFDDataType.Undefined: |
@@ -91,5 +91,15 @@ "use strict"; | ||
const length = stripBytesEntry.getValue(this._reader); | ||
// TODO: throw if there's more than one strip | ||
if (!maxResolutionJPEG || length > maxResolutionJPEG.length) { | ||
maxResolutionJPEG = { offset, length, ifd }; | ||
const jpegBuffer = this._reader.use(() => { | ||
this._reader.seek(offset); | ||
return this._reader.readAsBuffer(length); | ||
}); | ||
if (!jpeg_decoder_1.JPEGDecoder.isJPEG(jpegBuffer)) | ||
return; | ||
const jpeg = new jpeg_decoder_1.JPEGDecoder(jpegBuffer); | ||
const metadata = jpeg.extractMetadata(); | ||
if (!metadata.ImageLength || !metadata.ImageWidth) | ||
return; | ||
if (!maxResolutionJPEG || metadata.ImageWidth > maxResolutionJPEG.metadata.ImageWidth) { | ||
// TODO: throw if there's more than one strip | ||
maxResolutionJPEG = { metadata, buffer: jpegBuffer }; | ||
} | ||
@@ -100,11 +110,21 @@ }); | ||
} | ||
this._reader.seek(maxResolutionJPEG.offset); | ||
return this._reader.readAsBuffer(maxResolutionJPEG.length); | ||
return maxResolutionJPEG.buffer; | ||
} | ||
_readLargestJPEG() { | ||
const maxResolutionJPEG = this._readLargestJPEGThumbnail() || this._readStripOffsetsAsJPEG(); | ||
if (!maxResolutionJPEG) { | ||
throw new Error('Could not find thumbnail or read StripOffsets IFDs'); | ||
} | ||
return maxResolutionJPEG; | ||
// Try to read the JPEG thumbnail first | ||
const maxThumbnailJPEG = this._readLargestJPEGThumbnail(); | ||
const thumbnailSize = (maxThumbnailJPEG && maxThumbnailJPEG.length) || 0; | ||
// Only return it immediately if it seems large enough (>500KB) | ||
if (maxThumbnailJPEG && thumbnailSize > 500 * 1000) | ||
return maxThumbnailJPEG; | ||
// Otherwise we'll fallback to the JPEG strip offsets | ||
const maxStripOffsetJPEG = this._readStripOffsetsAsJPEG(); | ||
// Only return it if it's larger than the thumbnail | ||
if (maxStripOffsetJPEG && maxStripOffsetJPEG.length > thumbnailSize) | ||
return maxStripOffsetJPEG; | ||
// Fallback to the small thumbnail if that's all we found | ||
if (maxThumbnailJPEG) | ||
return maxThumbnailJPEG; | ||
// Fail loudly if all else fails | ||
throw new Error('Could not find thumbnail or read StripOffsets IFDs'); | ||
} | ||
@@ -120,2 +140,6 @@ extractJPEG(options = {}) { | ||
const metadata = this.extractMetadata(); | ||
delete metadata.ImageWidth; | ||
delete metadata.ImageLength; | ||
delete metadata.EXIFImageWidth; | ||
delete metadata.EXIFImageHeight; | ||
const metadataBuffer = tiff_encoder_1.TIFFEncoder.encode(metadata); | ||
@@ -122,0 +146,0 @@ this._cachedJPEG = jpeg_decoder_1.JPEGDecoder.injectEXIFMetadata(jpeg, metadataBuffer); |
@@ -8,2 +8,11 @@ "use strict"; | ||
const log = log_1.createLogger('encoder'); | ||
const BLACKLISTED_TAGS = new Set([ | ||
types_1.IFDTag.SubIFD, | ||
types_1.IFDTag.EXIFOffset, | ||
types_1.IFDTag.StripOffsets, | ||
types_1.IFDTag.StripByteCounts, | ||
types_1.IFDTag.ThumbnailOffset, | ||
types_1.IFDTag.ThumbnailLength, | ||
types_1.IFDTag.RowsPerStrip, | ||
]); | ||
class TIFFEncoder { | ||
@@ -15,2 +24,4 @@ static isSupportedEntry(tag, value) { | ||
return false; | ||
if (BLACKLISTED_TAGS.has(tag.code)) | ||
return false; | ||
if (tag.dataType === types_1.IFDDataType.Short) | ||
@@ -17,0 +28,0 @@ return value < Math.pow(2, 16); |
@@ -10,4 +10,4 @@ "use strict"; | ||
model: ['Model'], | ||
width: ['ImageWidth'], | ||
height: ['ImageLength'], | ||
width: ['EXIFImageWidth', 'ImageWidth'], | ||
height: ['EXIFImageHeight', 'ImageLength'], | ||
xResolution: ['XResolution'], | ||
@@ -14,0 +14,0 @@ yResolution: ['YResolution'], |
@@ -85,7 +85,9 @@ /// <reference types="node" /> | ||
Rational = 5, | ||
SignedByte = 6, | ||
Undefined = 7, | ||
SignedShort = 8, | ||
SignedLong = 9, | ||
SignedRational = 10, | ||
SingleFloat = 11, | ||
DoubleFloat = 12 | ||
Float = 11, | ||
Double = 12 | ||
} | ||
@@ -92,0 +94,0 @@ export interface IJPEGOptions { |
@@ -38,8 +38,10 @@ "use strict"; | ||
IFDDataType[IFDDataType["Rational"] = 5] = "Rational"; | ||
IFDDataType[IFDDataType["SignedByte"] = 6] = "SignedByte"; | ||
IFDDataType[IFDDataType["Undefined"] = 7] = "Undefined"; | ||
IFDDataType[IFDDataType["SignedShort"] = 8] = "SignedShort"; | ||
IFDDataType[IFDDataType["SignedLong"] = 9] = "SignedLong"; | ||
IFDDataType[IFDDataType["SignedRational"] = 10] = "SignedRational"; | ||
// From https://www.media.mit.edu/pia/Research/deepview/exif.html | ||
IFDDataType[IFDDataType["SingleFloat"] = 11] = "SingleFloat"; | ||
IFDDataType[IFDDataType["DoubleFloat"] = 12] = "DoubleFloat"; | ||
IFDDataType[IFDDataType["Float"] = 11] = "Float"; | ||
IFDDataType[IFDDataType["Double"] = 12] = "Double"; | ||
})(IFDDataType = exports.IFDDataType || (exports.IFDDataType = {})); | ||
@@ -51,7 +53,10 @@ function getDataTypeSize(dataType, name) { | ||
case IFDDataType.String: // ASCII-string | ||
case IFDDataType.SignedByte: | ||
return 1; | ||
case IFDDataType.Short: // word | ||
case IFDDataType.SignedShort: | ||
return 2; | ||
case IFDDataType.Long: // double word | ||
case IFDDataType.SingleFloat: | ||
case IFDDataType.SignedLong: | ||
case IFDDataType.Float: | ||
case IFDDataType.Undefined: | ||
@@ -61,3 +66,3 @@ return 4; | ||
case IFDDataType.SignedRational: | ||
case IFDDataType.DoubleFloat: | ||
case IFDDataType.Double: | ||
return 8; | ||
@@ -64,0 +69,0 @@ default: { |
@@ -26,8 +26,11 @@ import {getFriendlyName} from '../utils/tags' | ||
switch (this.dataType) { | ||
// TODO: verify signed versions | ||
case IFDDataType.Byte: | ||
case IFDDataType.Short: | ||
case IFDDataType.Long: | ||
case IFDDataType.SignedByte: | ||
case IFDDataType.SignedShort: | ||
case IFDDataType.SignedLong: | ||
return entryReader.read(this.lengthInBytes) | ||
case IFDDataType.Rational: | ||
// TODO: fix signed rational repr | ||
case IFDDataType.SignedRational: | ||
@@ -47,5 +50,5 @@ return entryReader.read(4) / entryReader.read(4) | ||
return chars.join('') | ||
case IFDDataType.SingleFloat: | ||
case IFDDataType.Float: | ||
return new DataView(entryReader.readAsBuffer(4).buffer).getFloat32(0) | ||
case IFDDataType.DoubleFloat: | ||
case IFDDataType.Double: | ||
return new DataView(entryReader.readAsBuffer(8).buffer).getFloat64(0) | ||
@@ -52,0 +55,0 @@ case IFDDataType.Undefined: |
@@ -110,3 +110,3 @@ import {IFD} from '../decoder/ifd' | ||
private _readStripOffsetsAsJPEG(): IBufferLike | undefined { | ||
let maxResolutionJPEG: IThumbnailLocation | undefined | ||
let maxResolutionJPEG: {buffer: IBufferLike; metadata: IGenericMetadata} | undefined | ||
this._ifds.forEach(ifd => { | ||
@@ -125,6 +125,16 @@ const compressionEntry = ifd.entries.find(entry => entry.tag === IFDTag.Compression) | ||
const length = stripBytesEntry.getValue(this._reader) as number | ||
const jpegBuffer = this._reader.use(() => { | ||
this._reader.seek(offset) | ||
return this._reader.readAsBuffer(length) | ||
}) | ||
// TODO: throw if there's more than one strip | ||
if (!maxResolutionJPEG || length > maxResolutionJPEG.length) { | ||
maxResolutionJPEG = {offset, length, ifd} | ||
if (!JPEGDecoder.isJPEG(jpegBuffer)) return | ||
const jpeg = new JPEGDecoder(jpegBuffer) | ||
const metadata = jpeg.extractMetadata() | ||
if (!metadata.ImageLength || !metadata.ImageWidth) return | ||
if (!maxResolutionJPEG || metadata.ImageWidth > maxResolutionJPEG.metadata.ImageWidth!) { | ||
// TODO: throw if there's more than one strip | ||
maxResolutionJPEG = {metadata, buffer: jpegBuffer} | ||
} | ||
@@ -137,12 +147,20 @@ }) | ||
this._reader.seek(maxResolutionJPEG.offset) | ||
return this._reader.readAsBuffer(maxResolutionJPEG.length) | ||
return maxResolutionJPEG.buffer | ||
} | ||
private _readLargestJPEG(): IBufferLike { | ||
const maxResolutionJPEG = this._readLargestJPEGThumbnail() || this._readStripOffsetsAsJPEG() | ||
if (!maxResolutionJPEG) { | ||
throw new Error('Could not find thumbnail or read StripOffsets IFDs') | ||
} | ||
return maxResolutionJPEG | ||
// Try to read the JPEG thumbnail first | ||
const maxThumbnailJPEG = this._readLargestJPEGThumbnail() | ||
const thumbnailSize = (maxThumbnailJPEG && maxThumbnailJPEG.length) || 0 | ||
// Only return it immediately if it seems large enough (>500KB) | ||
if (maxThumbnailJPEG && thumbnailSize > 500 * 1000) return maxThumbnailJPEG | ||
// Otherwise we'll fallback to the JPEG strip offsets | ||
const maxStripOffsetJPEG = this._readStripOffsetsAsJPEG() | ||
// Only return it if it's larger than the thumbnail | ||
if (maxStripOffsetJPEG && maxStripOffsetJPEG.length > thumbnailSize) return maxStripOffsetJPEG | ||
// Fallback to the small thumbnail if that's all we found | ||
if (maxThumbnailJPEG) return maxThumbnailJPEG | ||
// Fail loudly if all else fails | ||
throw new Error('Could not find thumbnail or read StripOffsets IFDs') | ||
} | ||
@@ -160,2 +178,6 @@ | ||
const metadata = this.extractMetadata() | ||
delete metadata.ImageWidth | ||
delete metadata.ImageLength | ||
delete metadata.EXIFImageWidth | ||
delete metadata.EXIFImageHeight | ||
const metadataBuffer = TIFFEncoder.encode(metadata) | ||
@@ -162,0 +184,0 @@ |
@@ -10,2 +10,3 @@ import { | ||
IIFDTagDefinition, | ||
IFDTag, | ||
} from '../utils/types' | ||
@@ -18,2 +19,12 @@ import {Writer} from '../utils/writer' | ||
const BLACKLISTED_TAGS = new Set([ | ||
IFDTag.SubIFD, | ||
IFDTag.EXIFOffset, | ||
IFDTag.StripOffsets, | ||
IFDTag.StripByteCounts, | ||
IFDTag.ThumbnailOffset, | ||
IFDTag.ThumbnailLength, | ||
IFDTag.RowsPerStrip, | ||
]) | ||
export class TIFFEncoder { | ||
@@ -23,2 +34,3 @@ public static isSupportedEntry(tag: IIFDTagDefinition | undefined, value: any): boolean { | ||
if (tag.group !== IFDGroup.EXIF) return false | ||
if (BLACKLISTED_TAGS.has(tag.code)) return false | ||
if (tag.dataType === IFDDataType.Short) return value < Math.pow(2, 16) | ||
@@ -25,0 +37,0 @@ if (tag.dataType === IFDDataType.Long) return value < Math.pow(2, 32) |
@@ -21,4 +21,4 @@ import {parseDate} from './date-parser' | ||
width: ['ImageWidth'], | ||
height: ['ImageLength'], | ||
width: ['EXIFImageWidth', 'ImageWidth'], | ||
height: ['EXIFImageHeight', 'ImageLength'], | ||
xResolution: ['XResolution'], | ||
@@ -25,0 +25,0 @@ yResolution: ['YResolution'], |
@@ -98,8 +98,10 @@ export interface ILogger { | ||
Rational = 5, | ||
SignedByte = 6, | ||
Undefined = 7, | ||
SignedShort = 8, | ||
SignedLong = 9, | ||
SignedRational = 10, | ||
// From https://www.media.mit.edu/pia/Research/deepview/exif.html | ||
SingleFloat = 11, | ||
DoubleFloat = 12, | ||
Float = 11, | ||
Double = 12, | ||
} | ||
@@ -155,7 +157,10 @@ | ||
case IFDDataType.String: // ASCII-string | ||
case IFDDataType.SignedByte: | ||
return 1 | ||
case IFDDataType.Short: // word | ||
case IFDDataType.SignedShort: | ||
return 2 | ||
case IFDDataType.Long: // double word | ||
case IFDDataType.SingleFloat: | ||
case IFDDataType.SignedLong: | ||
case IFDDataType.Float: | ||
case IFDDataType.Undefined: | ||
@@ -165,3 +170,3 @@ return 4 | ||
case IFDDataType.SignedRational: | ||
case IFDDataType.DoubleFloat: | ||
case IFDDataType.Double: | ||
return 8 | ||
@@ -168,0 +173,0 @@ default: { |
{ | ||
"name": "@eris/exif", | ||
"version": "0.3.1-alpha.2", | ||
"version": "0.3.1-alpha.3", | ||
"description": "Parses EXIF data.", | ||
@@ -49,3 +49,3 @@ "main": "./dist/index.js", | ||
}, | ||
"gitHead": "1be49b5ba3508e530c16a5b221c44d8e4886b3ae" | ||
"gitHead": "736edbccdec6ff9888f6103916578dea219901fa" | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
222992
4116