@eris/exif
Advanced tools
Comparing version 0.4.2-alpha.14 to 0.4.2-alpha.15
/// <reference types="node" /> | ||
import { IIFDEntry, IReader, IFDTagName, Endian } from '../utils/types'; | ||
import { IIFDEntry, IReader, IFDTagName, Endian, IBufferLike } from '../utils/types'; | ||
export declare class IFDEntry implements IIFDEntry { | ||
@@ -12,3 +12,3 @@ startOffset: number; | ||
readonly friendlyTagName: IFDTagName; | ||
getValue(reader?: IReader): string | number; | ||
getValue(reader?: IReader): string | number | IBufferLike; | ||
getReader(reader?: IReader): IReader; | ||
@@ -15,0 +15,0 @@ static read(reader: IReader): IFDEntry; |
@@ -53,3 +53,3 @@ "use strict"; | ||
case types_1.IFDDataType.Unknown: | ||
return ''; | ||
return entryReader.getBuffer(); | ||
default: | ||
@@ -56,0 +56,0 @@ throw new TypeError(`Unsupported data type (${this.dataType}) for tag (${this.tag})`); |
@@ -25,4 +25,5 @@ import { IBufferLike, IGenericMetadata } from '../utils/types'; | ||
static isJPEG(buffer: IBufferLike): boolean; | ||
static isLikelyJPEG(buffer: IBufferLike): boolean; | ||
static injectEXIFMetadata(jpegBuffer: IBufferLike, exifBuffer: IBufferLike): IBufferLike; | ||
static injectXMPMetadata(jpegBuffer: IBufferLike, xmpBuffer: IBufferLike): IBufferLike; | ||
} |
@@ -8,2 +8,4 @@ "use strict"; | ||
const xmp_decoder_1 = require("./xmp-decoder"); | ||
const log_1 = require("../utils/log"); | ||
const log = log_1.createLogger('jpeg-decoder'); | ||
const EXIF_HEADER = 0x45786966; // "Exif" | ||
@@ -155,2 +157,12 @@ const XMP_HEADER = 0x68747470; // The "http" in "http://ns.adobe.com/xap/1.0/" | ||
static isJPEG(buffer) { | ||
try { | ||
new JPEGDecoder(buffer)._readFileMarkers(); | ||
return true; | ||
} | ||
catch (err) { | ||
log(`not a JPEG, decoding failed with ${err.message}`); | ||
return false; | ||
} | ||
} | ||
static isLikelyJPEG(buffer) { | ||
return buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff; | ||
@@ -157,0 +169,0 @@ } |
@@ -15,2 +15,3 @@ /// <reference types="node" /> | ||
private _readStripOffsetsAsJPEG; | ||
private _findJPEGInRange; | ||
/** | ||
@@ -22,2 +23,6 @@ * Panasonic mislabels all of their tags so the names make no sense. | ||
private _readPanasonicJPEG; | ||
/** | ||
* Olympus JPEG preview is usually contained within the makernote | ||
*/ | ||
private _readOlympusJPEG; | ||
private _readLargestJPEG; | ||
@@ -24,0 +29,0 @@ extractJPEG(options?: IJPEGOptions): IBufferLike; |
@@ -34,2 +34,3 @@ "use strict"; | ||
this._reader = new reader_1.Reader(buffer); | ||
this._variant = Variant.Unknown; | ||
} | ||
@@ -60,2 +61,3 @@ _readAndValidateHeader() { | ||
} | ||
log(`detected tiff variant as ${this._variant}`); | ||
} | ||
@@ -123,3 +125,3 @@ _readIFDs() { | ||
}); | ||
if (!jpeg_decoder_1.JPEGDecoder.isJPEG(jpegBuffer)) | ||
if (!jpeg_decoder_1.JPEGDecoder.isLikelyJPEG(jpegBuffer)) | ||
return; | ||
@@ -140,2 +142,45 @@ const jpeg = new jpeg_decoder_1.JPEGDecoder(jpegBuffer); | ||
} | ||
_findJPEGInRange(buffer, searchStartIndex, searchEndIndex) { | ||
const startCandidates = []; | ||
const endCandidates = []; | ||
for (let i = searchStartIndex; i < searchEndIndex; i++) { | ||
if (buffer[i] !== 0xff) | ||
continue; | ||
if (buffer[i + 1] !== 0xd8) | ||
continue; | ||
if (buffer[i + 2] !== 0xff) | ||
continue; | ||
startCandidates.push(i); | ||
} | ||
for (let i = searchEndIndex; i > searchStartIndex; i--) { | ||
if (buffer[i - 1] !== 0xff) | ||
continue; | ||
if (buffer[i] !== 0xd9) | ||
continue; | ||
endCandidates.push(i); | ||
} | ||
let jpeg; | ||
let maxWidth = -Infinity; | ||
for (const startIndex of startCandidates) { | ||
for (const endIndex of endCandidates) { | ||
if (!Number.isFinite(startIndex)) | ||
continue; | ||
if (!Number.isFinite(endIndex)) | ||
continue; | ||
if (endIndex - startIndex < 4000) | ||
continue; | ||
const jpegBuffer = buffer.slice(startIndex, endIndex + 1); | ||
if (!jpeg_decoder_1.JPEGDecoder.isJPEG(jpegBuffer)) | ||
continue; | ||
const metadata = new jpeg_decoder_1.JPEGDecoder(jpegBuffer).extractMetadata(); | ||
if (typeof metadata.ImageWidth !== 'number') | ||
continue; | ||
if (metadata.ImageWidth < maxWidth) | ||
continue; | ||
maxWidth = metadata.ImageWidth; | ||
jpeg = jpegBuffer; | ||
} | ||
} | ||
return jpeg; | ||
} | ||
/** | ||
@@ -157,33 +202,18 @@ * Panasonic mislabels all of their tags so the names make no sense. | ||
return; | ||
let startIndex = Infinity; | ||
let endIndex = Infinity; | ||
const buffer = this._reader.getBuffer(); | ||
for (let i = searchStart; i < endEntryValue; i++) { | ||
if (buffer[i] !== 0xff) | ||
continue; | ||
if (buffer[i + 1] !== 0xd8) | ||
continue; | ||
if (buffer[i + 2] !== 0xff) | ||
continue; | ||
startIndex = i; | ||
break; | ||
} | ||
for (let i = endEntryValue; i > searchStart; i--) { | ||
if (buffer[i - 1] !== 0xff) | ||
continue; | ||
if (buffer[i] !== 0xd9) | ||
continue; | ||
endIndex = i; | ||
break; | ||
} | ||
if (!Number.isFinite(startIndex)) | ||
return this._findJPEGInRange(this._reader.getBuffer(), searchStart, endEntryValue); | ||
} | ||
/** | ||
* Olympus JPEG preview is usually contained within the makernote | ||
*/ | ||
_readOlympusJPEG() { | ||
if (this._variant !== Variant.Olympus) | ||
return; | ||
if (!Number.isFinite(endIndex)) | ||
const ifdEntries = this.extractIFDEntries(); | ||
const makerNoteEntry = ifdEntries.find(entry => entry.tag === 37500); | ||
if (!makerNoteEntry) | ||
return; | ||
if (endIndex - startIndex < 4000) | ||
const makernoteBuffer = makerNoteEntry.getValue(this._reader); | ||
if (typeof makernoteBuffer === 'string' || typeof makernoteBuffer === 'number') | ||
return; | ||
const jpegBuffer = buffer.slice(startIndex, endIndex + 1); | ||
if (!jpeg_decoder_1.JPEGDecoder.isJPEG(jpegBuffer)) | ||
return; | ||
return jpegBuffer; | ||
return this._findJPEGInRange(makernoteBuffer, 0, makernoteBuffer.length); | ||
} | ||
@@ -209,2 +239,5 @@ _readLargestJPEG() { | ||
return panasonicJPEG; | ||
const olympusJPEG = this._readOlympusJPEG(); | ||
if (olympusJPEG) | ||
return olympusJPEG; | ||
// Fail loudly if all else fails | ||
@@ -237,3 +270,8 @@ throw new Error('Could not find an embedded JPEG'); | ||
const value = entry.getValue(this._reader); | ||
log.verbose(`evaluated ${name} (${entry.tag} - ${entry.dataType}) as ${value}`); | ||
const displayValue = typeof value === 'string' || typeof value === 'number' | ||
? value.toString() | ||
: value.slice(0, 32); | ||
log.verbose(`evaluated ${name} (${entry.tag} - ${entry.dataType}) as ${displayValue}`); | ||
if (typeof value !== 'string' && typeof value !== 'number') | ||
return; | ||
target[name] = value; | ||
@@ -240,0 +278,0 @@ const panasonicName = tags_1.panasonicConversionTags[name]; |
@@ -41,3 +41,3 @@ "use strict"; | ||
} | ||
else if (jpeg_decoder_1.JPEGDecoder.isJPEG(bufferOrDecoder)) { | ||
else if (jpeg_decoder_1.JPEGDecoder.isLikelyJPEG(bufferOrDecoder)) { | ||
return new jpeg_decoder_1.JPEGDecoder(bufferOrDecoder); | ||
@@ -44,0 +44,0 @@ } |
@@ -54,3 +54,3 @@ /// <reference types="node" /> | ||
length: number; | ||
getValue(reader?: IReader): string | number; | ||
getValue(reader?: IReader): string | number | IBufferLike; | ||
} | ||
@@ -57,0 +57,0 @@ export interface IIFDOffset { |
@@ -51,2 +51,3 @@ "use strict"; | ||
case IFDDataType.Unknown: // ??? | ||
case IFDDataType.Undefined: // ???, was previously 4 but definitely not true of Olympus | ||
case IFDDataType.Byte: // byte | ||
@@ -62,3 +63,2 @@ case IFDDataType.String: // ASCII-string | ||
case IFDDataType.Float: | ||
case IFDDataType.Undefined: | ||
return 4; | ||
@@ -65,0 +65,0 @@ case IFDDataType.Rational: // rational number |
import {getFriendlyName} from '../utils/tags' | ||
import {IFDDataType, IIFDEntry, IReader, IFDTagName, getDataTypeSize, Endian} from '../utils/types' | ||
import { | ||
IFDDataType, | ||
IIFDEntry, | ||
IReader, | ||
IFDTagName, | ||
getDataTypeSize, | ||
Endian, | ||
IBufferLike, | ||
} from '../utils/types' | ||
import {createLogger} from '../utils/log' | ||
@@ -26,3 +34,3 @@ import {Writer} from '../utils/writer' | ||
public getValue(reader?: IReader): string | number { | ||
public getValue(reader?: IReader): string | number | IBufferLike { | ||
const entryReader = this.getReader(reader) | ||
@@ -59,3 +67,3 @@ switch (this.dataType) { | ||
case IFDDataType.Unknown: | ||
return '' | ||
return entryReader.getBuffer() | ||
default: | ||
@@ -62,0 +70,0 @@ throw new TypeError(`Unsupported data type (${this.dataType}) for tag (${this.tag})`) |
@@ -6,3 +6,6 @@ import {TIFFDecoder} from '../decoder/tiff-decoder' | ||
import {XMPDecoder} from './xmp-decoder' | ||
import {createLogger} from '../utils/log' | ||
const log = createLogger('jpeg-decoder') | ||
const EXIF_HEADER = 0x45786966 // "Exif" | ||
@@ -200,2 +203,12 @@ const XMP_HEADER = 0x68747470 // The "http" in "http://ns.adobe.com/xap/1.0/" | ||
public static isJPEG(buffer: IBufferLike): boolean { | ||
try { | ||
new JPEGDecoder(buffer)._readFileMarkers() | ||
return true | ||
} catch (err) { | ||
log(`not a JPEG, decoding failed with ${err.message}`) | ||
return false | ||
} | ||
} | ||
public static isLikelyJPEG(buffer: IBufferLike): boolean { | ||
return buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff | ||
@@ -202,0 +215,0 @@ } |
@@ -61,2 +61,3 @@ import {IFD} from '../decoder/ifd' | ||
this._reader = new Reader(buffer) | ||
this._variant = Variant.Unknown | ||
} | ||
@@ -84,2 +85,4 @@ | ||
} | ||
log(`detected tiff variant as ${this._variant}`) | ||
} | ||
@@ -159,3 +162,3 @@ | ||
if (!JPEGDecoder.isJPEG(jpegBuffer)) return | ||
if (!JPEGDecoder.isLikelyJPEG(jpegBuffer)) return | ||
@@ -179,2 +182,46 @@ const jpeg = new JPEGDecoder(jpegBuffer) | ||
private _findJPEGInRange( | ||
buffer: IBufferLike, | ||
searchStartIndex: number, | ||
searchEndIndex: number, | ||
): IBufferLike | undefined { | ||
const startCandidates: number[] = [] | ||
const endCandidates: number[] = [] | ||
for (let i = searchStartIndex; i < searchEndIndex; i++) { | ||
if (buffer[i] !== 0xff) continue | ||
if (buffer[i + 1] !== 0xd8) continue | ||
if (buffer[i + 2] !== 0xff) continue | ||
startCandidates.push(i) | ||
} | ||
for (let i = searchEndIndex; i > searchStartIndex; i--) { | ||
if (buffer[i - 1] !== 0xff) continue | ||
if (buffer[i] !== 0xd9) continue | ||
endCandidates.push(i) | ||
} | ||
let jpeg: IBufferLike | undefined | ||
let maxWidth = -Infinity | ||
for (const startIndex of startCandidates) { | ||
for (const endIndex of endCandidates) { | ||
if (!Number.isFinite(startIndex)) continue | ||
if (!Number.isFinite(endIndex)) continue | ||
if (endIndex - startIndex < 4000) continue | ||
const jpegBuffer = buffer.slice(startIndex, endIndex + 1) | ||
if (!JPEGDecoder.isJPEG(jpegBuffer)) continue | ||
const metadata = new JPEGDecoder(jpegBuffer).extractMetadata() | ||
if (typeof metadata.ImageWidth !== 'number') continue | ||
if (metadata.ImageWidth < maxWidth) continue | ||
maxWidth = metadata.ImageWidth | ||
jpeg = jpegBuffer | ||
} | ||
} | ||
return jpeg | ||
} | ||
/** | ||
@@ -195,28 +242,18 @@ * Panasonic mislabels all of their tags so the names make no sense. | ||
let startIndex = Infinity | ||
let endIndex = Infinity | ||
const buffer = this._reader.getBuffer() | ||
return this._findJPEGInRange(this._reader.getBuffer(), searchStart, endEntryValue) | ||
} | ||
for (let i = searchStart; i < endEntryValue; i++) { | ||
if (buffer[i] !== 0xff) continue | ||
if (buffer[i + 1] !== 0xd8) continue | ||
if (buffer[i + 2] !== 0xff) continue | ||
startIndex = i | ||
break | ||
} | ||
/** | ||
* Olympus JPEG preview is usually contained within the makernote | ||
*/ | ||
private _readOlympusJPEG(): IBufferLike | undefined { | ||
if (this._variant !== Variant.Olympus) return | ||
for (let i = endEntryValue; i > searchStart; i--) { | ||
if (buffer[i - 1] !== 0xff) continue | ||
if (buffer[i] !== 0xd9) continue | ||
endIndex = i | ||
break | ||
} | ||
const ifdEntries = this.extractIFDEntries() | ||
const makerNoteEntry = ifdEntries.find(entry => entry.tag === 37500) | ||
if (!makerNoteEntry) return | ||
if (!Number.isFinite(startIndex)) return | ||
if (!Number.isFinite(endIndex)) return | ||
if (endIndex - startIndex < 4000) return | ||
const jpegBuffer = buffer.slice(startIndex, endIndex + 1) | ||
if (!JPEGDecoder.isJPEG(jpegBuffer)) return | ||
return jpegBuffer | ||
const makernoteBuffer = makerNoteEntry.getValue(this._reader) | ||
if (typeof makernoteBuffer === 'string' || typeof makernoteBuffer === 'number') return | ||
return this._findJPEGInRange(makernoteBuffer, 0, makernoteBuffer.length) | ||
} | ||
@@ -241,2 +278,4 @@ | ||
if (panasonicJPEG) return panasonicJPEG | ||
const olympusJPEG = this._readOlympusJPEG() | ||
if (olympusJPEG) return olympusJPEG | ||
@@ -274,3 +313,8 @@ // Fail loudly if all else fails | ||
const value = entry.getValue(this._reader) | ||
log.verbose(`evaluated ${name} (${entry.tag} - ${entry.dataType}) as ${value}`) | ||
const displayValue = | ||
typeof value === 'string' || typeof value === 'number' | ||
? value.toString() | ||
: value.slice(0, 32) | ||
log.verbose(`evaluated ${name} (${entry.tag} - ${entry.dataType}) as ${displayValue}`) | ||
if (typeof value !== 'string' && typeof value !== 'number') return | ||
target[name] = value | ||
@@ -277,0 +321,0 @@ |
@@ -32,3 +32,3 @@ import {JPEGDecoder} from './decoder/jpeg-decoder' | ||
return new TIFFDecoder(bufferOrDecoder) | ||
} else if (JPEGDecoder.isJPEG(bufferOrDecoder)) { | ||
} else if (JPEGDecoder.isLikelyJPEG(bufferOrDecoder)) { | ||
return new JPEGDecoder(bufferOrDecoder) | ||
@@ -35,0 +35,0 @@ } else if (XMPDecoder.isXMP(bufferOrDecoder)) { |
@@ -62,3 +62,3 @@ export interface ILogger { | ||
length: number | ||
getValue(reader?: IReader): string | number | ||
getValue(reader?: IReader): string | number | IBufferLike | ||
} | ||
@@ -166,2 +166,3 @@ | ||
case IFDDataType.Unknown: // ??? | ||
case IFDDataType.Undefined: // ???, was previously 4 but definitely not true of Olympus | ||
case IFDDataType.Byte: // byte | ||
@@ -177,3 +178,2 @@ case IFDDataType.String: // ASCII-string | ||
case IFDDataType.Float: | ||
case IFDDataType.Undefined: | ||
return 4 | ||
@@ -180,0 +180,0 @@ case IFDDataType.Rational: // rational number |
{ | ||
"name": "@eris/exif", | ||
"version": "0.4.2-alpha.14", | ||
"version": "0.4.2-alpha.15", | ||
"description": "Parses EXIF data.", | ||
@@ -41,3 +41,3 @@ "main": "./dist/index.js", | ||
}, | ||
"gitHead": "7ef5d393ef281749cb3ba173abf70056f879dad5" | ||
"gitHead": "61be137dec5c0d3a2dafd1065611f3091dbbd60c" | ||
} |
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
276672
5039