exif-be-gone
Advanced tools
Comparing version
/// <reference types="node" /> | ||
/// <reference types="node" /> | ||
import { Transform, TransformOptions, TransformCallback } from 'stream'; | ||
import { Transform, type TransformOptions, type TransformCallback } from 'stream'; | ||
declare class ExifTransformer extends Transform { | ||
remainingBytes: number | undefined; | ||
remainingScrubBytes: number | undefined; | ||
remainingGoodBytes: number | undefined; | ||
pending: Array<Buffer>; | ||
mode: 'png' | 'other' | undefined; | ||
constructor(options?: TransformOptions); | ||
_transform(chunk: any, _: string, callback: TransformCallback): void; | ||
_transform(chunk: any, _: BufferEncoding, callback: TransformCallback): void; | ||
_final(callback: TransformCallback): void; | ||
_scrub(atEnd: Boolean, chunk?: Buffer): void; | ||
_scrubOther(atEnd: Boolean, chunk?: Buffer): void; | ||
_scrubPNG(atEnd: Boolean, chunk?: Buffer): void; | ||
_processPNGGood(chunk: Buffer): Buffer; | ||
} | ||
export default ExifTransformer; | ||
//# sourceMappingURL=index.d.ts.map |
104
index.js
@@ -30,2 +30,3 @@ "use strict"; | ||
var exifMarker = Buffer.from('457869660000', 'hex'); // Exif\0\0 | ||
var pngMarker = Buffer.from('89504e470d0a1a0a', 'hex'); // 211 P N G \r \n \032 \n | ||
var xmpMarker = Buffer.from('http://ns.adobe.com/xap', 'utf-8'); | ||
@@ -38,3 +39,3 @@ var flirMarker = Buffer.from('FLIR', 'utf-8'); | ||
var _this = _super.call(this, options) || this; | ||
_this.remainingBytes = undefined; | ||
_this.remainingScrubBytes = undefined; | ||
_this.pending = []; | ||
@@ -44,2 +45,9 @@ return _this; | ||
ExifTransformer.prototype._transform = function (chunk, _, callback) { | ||
if (this.mode === undefined) { | ||
this.mode = pngMarker.equals(Uint8Array.prototype.slice.call(chunk, 0, 8)) ? 'png' : 'other'; | ||
if (this.mode === 'png') { | ||
this.push(Uint8Array.prototype.slice.call(chunk, 0, 8)); | ||
chunk = Buffer.from(Uint8Array.prototype.slice.call(chunk, 8)); | ||
} | ||
} | ||
this._scrub(false, chunk); | ||
@@ -55,5 +63,12 @@ callback(); | ||
ExifTransformer.prototype._scrub = function (atEnd, chunk) { | ||
switch (this.mode) { | ||
case 'other': return this._scrubOther(atEnd, chunk); | ||
case 'png': return this._scrubPNG(atEnd, chunk); | ||
default: throw new Error('unknown mode'); | ||
} | ||
}; | ||
ExifTransformer.prototype._scrubOther = function (atEnd, chunk) { | ||
var pendingChunk = chunk ? Buffer.concat(__spreadArray(__spreadArray([], this.pending, true), [chunk], false)) : Buffer.concat(this.pending); | ||
// currently haven't detected an app1 marker | ||
if (this.remainingBytes === undefined) { | ||
if (this.remainingScrubBytes === undefined) { | ||
var app1Start = pendingChunk.indexOf(app1Marker); | ||
@@ -84,8 +99,8 @@ // no app1 in the current pendingChunk | ||
else { | ||
var candidateMarker = pendingChunk.slice(app1Start + 4, app1Start + maxMarkerLength + 4); | ||
var candidateMarker = Uint8Array.prototype.slice.call(pendingChunk, app1Start + 4, app1Start + maxMarkerLength + 4); | ||
if (exifMarker.compare(candidateMarker, 0, exifMarker.length) === 0 || xmpMarker.compare(candidateMarker, 0, xmpMarker.length) === 0 || flirMarker.compare(candidateMarker, 0, flirMarker.length) === 0) { | ||
// we add 2 to the remainingBytes to account for the app1 marker | ||
this.remainingBytes = pendingChunk.readUInt16BE(app1Start + 2) + 2; | ||
this.push(pendingChunk.slice(0, app1Start)); | ||
pendingChunk = pendingChunk.slice(app1Start); | ||
// we add 2 to the remainingScrubBytes to account for the app1 marker | ||
this.remainingScrubBytes = pendingChunk.readUInt16BE(app1Start + 2) + 2; | ||
this.push(Uint8Array.prototype.slice.call(pendingChunk, 0, app1Start)); | ||
pendingChunk = Buffer.from(Uint8Array.prototype.slice.call(pendingChunk, app1Start)); | ||
} | ||
@@ -96,12 +111,12 @@ } | ||
// we have successfully read an app1/exif marker, so we can remove data | ||
if (this.remainingBytes !== undefined && this.remainingBytes !== 0) { | ||
// there is more data than we want to remove, so we only remove up to remainingBytes | ||
if (pendingChunk.length >= this.remainingBytes) { | ||
var remainingBuffer = pendingChunk.slice(this.remainingBytes); | ||
if (this.remainingScrubBytes !== undefined && this.remainingScrubBytes !== 0) { | ||
// there is more data than we want to remove, so we only remove up to remainingScrubBytes | ||
if (pendingChunk.length >= this.remainingScrubBytes) { | ||
var remainingBuffer = Buffer.from(Uint8Array.prototype.slice.call(pendingChunk, this.remainingScrubBytes)); | ||
this.pending = remainingBuffer.length !== 0 ? [remainingBuffer] : []; | ||
this.remainingBytes = undefined; | ||
this.remainingScrubBytes = undefined; | ||
// this chunk is too large, remove everything | ||
} | ||
else { | ||
this.remainingBytes -= pendingChunk.length; | ||
this.remainingScrubBytes -= pendingChunk.length; | ||
this.pending.length = 0; | ||
@@ -113,6 +128,67 @@ } | ||
this.push(pendingChunk); | ||
this.remainingBytes = undefined; | ||
this.remainingScrubBytes = undefined; | ||
this.pending.length = 0; | ||
} | ||
}; | ||
ExifTransformer.prototype._scrubPNG = function (atEnd, chunk) { | ||
var pendingChunk = chunk ? Buffer.concat(__spreadArray(__spreadArray([], this.pending, true), [chunk], false)) : Buffer.concat(this.pending); | ||
while (pendingChunk.length !== 0) { | ||
pendingChunk = this._processPNGGood(pendingChunk); | ||
if (this.remainingScrubBytes !== undefined) { | ||
if (pendingChunk.length >= this.remainingScrubBytes) { | ||
var remainingBuffer = Buffer.from(Uint8Array.prototype.slice.call(pendingChunk, this.remainingScrubBytes)); | ||
this.pending = remainingBuffer.length !== 0 ? [remainingBuffer] : []; | ||
this.remainingScrubBytes = undefined; | ||
// this chunk is too large, remove everything | ||
return; | ||
} | ||
this.remainingScrubBytes -= pendingChunk.length; | ||
this.pending.length = 0; | ||
} | ||
if (pendingChunk.length === 0) | ||
return; | ||
if (pendingChunk.length < 8) { | ||
if (atEnd) { | ||
this.push(pendingChunk); | ||
} | ||
else { | ||
this.pending = [pendingChunk]; | ||
} | ||
return; | ||
} | ||
var size = pendingChunk.readUInt32BE(0); | ||
var chunkType = Uint8Array.prototype.slice.call(pendingChunk, 4, 8).toString(); | ||
switch (chunkType) { | ||
case 'tIME': | ||
case 'iTXt': | ||
case 'tEXt': | ||
case 'zTXt': | ||
case 'eXIf': | ||
case 'dSIG': | ||
this.remainingScrubBytes = size + 12; | ||
continue; | ||
default: | ||
this.remainingGoodBytes = size + 12; | ||
continue; | ||
} | ||
} | ||
}; | ||
ExifTransformer.prototype._processPNGGood = function (chunk) { | ||
if (this.remainingGoodBytes === undefined) { | ||
return chunk; | ||
} | ||
this.pending.length = 0; | ||
// we need all these bytes | ||
if (this.remainingGoodBytes >= chunk.length) { | ||
this.remainingGoodBytes -= chunk.length; | ||
this.push(chunk); | ||
return Buffer.alloc(0); | ||
} | ||
else { | ||
this.push(Uint8Array.prototype.slice.call(chunk, 0, this.remainingGoodBytes)); | ||
var remaining = Buffer.from(Uint8Array.prototype.slice.call(chunk, this.remainingGoodBytes)); | ||
this.remainingGoodBytes = undefined; | ||
return remaining; | ||
} | ||
}; | ||
return ExifTransformer; | ||
@@ -119,0 +195,0 @@ }(stream_1.Transform)); |
115
index.ts
@@ -1,5 +0,6 @@ | ||
import { Transform, TransformOptions, TransformCallback } from 'stream' | ||
import { Transform, type TransformOptions, type TransformCallback } from 'stream' | ||
const app1Marker = Buffer.from('ffe1', 'hex') | ||
const exifMarker = Buffer.from('457869660000', 'hex') // Exif\0\0 | ||
const pngMarker = Buffer.from('89504e470d0a1a0a', 'hex') // 211 P N G \r \n \032 \n | ||
const xmpMarker = Buffer.from('http://ns.adobe.com/xap', 'utf-8') | ||
@@ -11,12 +12,21 @@ const flirMarker = Buffer.from('FLIR', 'utf-8') | ||
class ExifTransformer extends Transform { | ||
remainingBytes: number | undefined | ||
remainingScrubBytes: number | undefined | ||
remainingGoodBytes: number | undefined | ||
pending: Array<Buffer> | ||
mode: 'png' | 'other' | undefined | ||
constructor (options?: TransformOptions) { | ||
super(options) | ||
this.remainingBytes = undefined | ||
this.remainingScrubBytes = undefined | ||
this.pending = [] | ||
} | ||
override _transform (chunk: any, _: string, callback: TransformCallback) { | ||
override _transform (chunk: any, _: BufferEncoding, callback: TransformCallback) { | ||
if (this.mode === undefined) { | ||
this.mode = pngMarker.equals(Uint8Array.prototype.slice.call(chunk, 0, 8)) ? 'png' : 'other' | ||
if (this.mode === 'png') { | ||
this.push(Uint8Array.prototype.slice.call(chunk, 0, 8)) | ||
chunk = Buffer.from(Uint8Array.prototype.slice.call(chunk, 8)) | ||
} | ||
} | ||
this._scrub(false, chunk) | ||
@@ -34,5 +44,13 @@ callback() | ||
_scrub (atEnd: Boolean, chunk?: Buffer) { | ||
switch (this.mode) { | ||
case 'other': return this._scrubOther(atEnd, chunk) | ||
case 'png': return this._scrubPNG(atEnd, chunk) | ||
default: throw new Error('unknown mode') | ||
} | ||
} | ||
_scrubOther (atEnd: Boolean, chunk?: Buffer) { | ||
let pendingChunk = chunk ? Buffer.concat([...this.pending, chunk]) : Buffer.concat(this.pending) | ||
// currently haven't detected an app1 marker | ||
if (this.remainingBytes === undefined) { | ||
if (this.remainingScrubBytes === undefined) { | ||
const app1Start = pendingChunk.indexOf(app1Marker) | ||
@@ -59,8 +77,8 @@ // no app1 in the current pendingChunk | ||
} else { | ||
const candidateMarker = pendingChunk.slice(app1Start + 4, app1Start + maxMarkerLength + 4) | ||
const candidateMarker = Uint8Array.prototype.slice.call(pendingChunk, app1Start + 4, app1Start + maxMarkerLength + 4) | ||
if (exifMarker.compare(candidateMarker, 0, exifMarker.length) === 0 || xmpMarker.compare(candidateMarker, 0, xmpMarker.length) === 0 || flirMarker.compare(candidateMarker, 0, flirMarker.length) === 0) { | ||
// we add 2 to the remainingBytes to account for the app1 marker | ||
this.remainingBytes = pendingChunk.readUInt16BE(app1Start + 2) + 2 | ||
this.push(pendingChunk.slice(0, app1Start)) | ||
pendingChunk = pendingChunk.slice(app1Start) | ||
// we add 2 to the remainingScrubBytes to account for the app1 marker | ||
this.remainingScrubBytes = pendingChunk.readUInt16BE(app1Start + 2) + 2 | ||
this.push(Uint8Array.prototype.slice.call(pendingChunk, 0, app1Start)) | ||
pendingChunk = Buffer.from(Uint8Array.prototype.slice.call(pendingChunk, app1Start)) | ||
} | ||
@@ -72,11 +90,11 @@ } | ||
// we have successfully read an app1/exif marker, so we can remove data | ||
if (this.remainingBytes !== undefined && this.remainingBytes !== 0) { | ||
// there is more data than we want to remove, so we only remove up to remainingBytes | ||
if (pendingChunk.length >= this.remainingBytes) { | ||
const remainingBuffer = pendingChunk.slice(this.remainingBytes) | ||
if (this.remainingScrubBytes !== undefined && this.remainingScrubBytes !== 0) { | ||
// there is more data than we want to remove, so we only remove up to remainingScrubBytes | ||
if (pendingChunk.length >= this.remainingScrubBytes) { | ||
const remainingBuffer = Buffer.from(Uint8Array.prototype.slice.call(pendingChunk, this.remainingScrubBytes)) | ||
this.pending = remainingBuffer.length !== 0 ? [remainingBuffer] : [] | ||
this.remainingBytes = undefined | ||
this.remainingScrubBytes = undefined | ||
// this chunk is too large, remove everything | ||
} else { | ||
this.remainingBytes -= pendingChunk.length | ||
this.remainingScrubBytes -= pendingChunk.length | ||
this.pending.length = 0 | ||
@@ -87,6 +105,69 @@ } | ||
this.push(pendingChunk) | ||
this.remainingBytes = undefined | ||
this.remainingScrubBytes = undefined | ||
this.pending.length = 0 | ||
} | ||
} | ||
_scrubPNG (atEnd: Boolean, chunk?: Buffer) { | ||
let pendingChunk = chunk ? Buffer.concat([...this.pending, chunk]) : Buffer.concat(this.pending) | ||
while (pendingChunk.length !== 0) { | ||
pendingChunk = this._processPNGGood(pendingChunk) | ||
if (this.remainingScrubBytes !== undefined) { | ||
if (pendingChunk.length >= this.remainingScrubBytes) { | ||
const remainingBuffer = Buffer.from(Uint8Array.prototype.slice.call(pendingChunk, this.remainingScrubBytes)) | ||
this.pending = remainingBuffer.length !== 0 ? [remainingBuffer] : [] | ||
this.remainingScrubBytes = undefined | ||
// this chunk is too large, remove everything | ||
return | ||
} | ||
this.remainingScrubBytes -= pendingChunk.length | ||
this.pending.length = 0 | ||
} | ||
if (pendingChunk.length === 0) return | ||
if (pendingChunk.length < 8) { | ||
if (atEnd) { | ||
this.push(pendingChunk) | ||
} else { | ||
this.pending = [pendingChunk] | ||
} | ||
return | ||
} | ||
const size = pendingChunk.readUInt32BE(0) | ||
const chunkType = Uint8Array.prototype.slice.call(pendingChunk, 4, 8).toString() | ||
switch (chunkType) { | ||
case 'tIME': | ||
case 'iTXt': | ||
case 'tEXt': | ||
case 'zTXt': | ||
case 'eXIf': | ||
case 'dSIG': | ||
this.remainingScrubBytes = size + 12 | ||
continue | ||
default: | ||
this.remainingGoodBytes = size + 12 | ||
continue | ||
} | ||
} | ||
} | ||
_processPNGGood (chunk: Buffer): Buffer { | ||
if (this.remainingGoodBytes === undefined) { | ||
return chunk | ||
} | ||
this.pending.length = 0 | ||
// we need all these bytes | ||
if (this.remainingGoodBytes >= chunk.length) { | ||
this.remainingGoodBytes -= chunk.length | ||
this.push(chunk) | ||
return Buffer.alloc(0) | ||
} else { | ||
this.push(Uint8Array.prototype.slice.call(chunk, 0, this.remainingGoodBytes)) | ||
const remaining = Buffer.from(Uint8Array.prototype.slice.call(chunk, this.remainingGoodBytes)) | ||
this.remainingGoodBytes = undefined | ||
return remaining | ||
} | ||
} | ||
} | ||
@@ -93,0 +174,0 @@ |
{ | ||
"name": "exif-be-gone", | ||
"version": "1.3.2", | ||
"version": "1.4.0", | ||
"description": "Remove EXIF data from your image files.", | ||
@@ -9,2 +9,3 @@ "main": "index.js", | ||
}, | ||
"types": "index.d.ts", | ||
"scripts": { | ||
@@ -11,0 +12,0 @@ "build": "tsc -p . && chmod 755 cli.js", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
1883804
8816.57%19
90%440
54.93%