feat: new gps remover

This commit is contained in:
diced
2025-06-06 15:06:21 -07:00
parent 81866b4b50
commit 6362d06253
6 changed files with 147 additions and 39 deletions

View File

@@ -57,7 +57,6 @@
"cross-env": "^7.0.3",
"dayjs": "^1.11.13",
"dotenv": "^16.5.0",
"exif-be-gone": "^1.5.1",
"fast-glob": "^3.3.3",
"fastify": "^5.3.3",
"fastify-plugin": "^5.0.1",

9
pnpm-lock.yaml generated
View File

@@ -101,9 +101,6 @@ importers:
dotenv:
specifier: ^16.5.0
version: 16.5.0
exif-be-gone:
specifier: ^1.5.1
version: 1.5.1
fast-glob:
specifier: ^3.3.3
version: 3.3.3
@@ -2818,10 +2815,6 @@ packages:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
exif-be-gone@1.5.1:
resolution: {integrity: sha512-+fV9PoomNVR5Hmp0n1c0ZVl78/GaFrpnC0t7q4F9Aey8NcL+7Lutcez8KY2Ni30NWpvgLXawqiRFFwtdo4QgFg==}
hasBin: true
extend@3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
@@ -7871,8 +7864,6 @@ snapshots:
events@3.3.0: {}
exif-be-gone@1.5.1: {}
extend@3.0.2: {}
fast-decode-uri-component@1.0.1: {}

View File

@@ -112,9 +112,9 @@ export async function handleFile({
let removedGps = false;
if (mimetype.startsWith('image/') && config.files.removeGpsMetadata) {
file.buffer = await removeGps(file.buffer);
const removed = removeGps(file.buffer);
if (file.buffer.length < file.file.bytesRead) {
if (removed) {
logger.c('gps').debug(`removed gps metadata from ${file.filename}`, {
nsize: bytes(file.buffer.length),
osize: bytes(file.file.bytesRead),

View File

@@ -1,27 +0,0 @@
import ExifTransformer from 'exif-be-gone';
import { PassThrough } from 'stream';
export async function removeGps(buffer: Buffer): Promise<Buffer> {
return new Promise<Buffer>((resolve, reject) => {
const input = new PassThrough();
input.end(buffer);
const transformer = new ExifTransformer();
const chunks: Buffer[] = [];
transformer.on('data', (chunk: Buffer) => {
chunks.push(chunk);
});
transformer.once('error', (err: Error) => {
reject(err);
});
transformer.once('end', () => {
const stripped = Buffer.concat(chunks);
resolve(stripped);
});
input.pipe(transformer);
});
}

14
src/lib/gps/constants.ts Normal file
View File

@@ -0,0 +1,14 @@
export const EXIF_JPEG_TAG = 0x45786966;
export const EXIF_PNG_TAG = 0x65584966;
export const GPS_IFD_TAG = 0x8825;
export const JPEG_EXIF_TAG = 0xffd8ffe1;
export const JPEG_APP1_TAG = 0xffe1;
export const JPEG_JFIF_TAG = 0xffd8ffe0;
export const TIFF_LE = 0x49492a00;
export const TIFF_BE = 0x4d4d002a;
export const PNG_TAG = 0x89504e47;
export const PNG_IEND = 0x49454e44;

131
src/lib/gps/index.ts Normal file
View File

@@ -0,0 +1,131 @@
// heavily modified from @xoi/gps-metadata-remover to fit the needs of zipline
import {
PNG_TAG,
PNG_IEND,
EXIF_PNG_TAG,
JPEG_EXIF_TAG,
JPEG_JFIF_TAG,
JPEG_APP1_TAG,
EXIF_JPEG_TAG,
TIFF_LE,
TIFF_BE,
GPS_IFD_TAG,
} from './constants';
function isLE(buffer: Buffer): boolean {
return buffer.readUInt32BE(0) === TIFF_LE;
}
function removeGpsEntries(buffer: Buffer, offset: number, le: boolean): void {
const numEntries = le ? buffer.readUInt16LE(offset) : buffer.readUInt16BE(offset);
const fieldsStart = offset + 2;
const toClear = numEntries * 12;
const zeroBuffer = Buffer.alloc(toClear);
zeroBuffer.copy(buffer, fieldsStart);
}
function parseExifTag(buffer: Buffer, offset: number, le: boolean) {
const tag = le ? buffer.readUInt16LE(offset) : buffer.readUInt16BE(offset);
const field = le ? buffer.readUInt16LE(offset + 2) : buffer.readUInt16BE(offset + 2);
const count = le ? buffer.readUInt32LE(offset + 4) : buffer.readUInt32BE(offset + 4);
const valueOffset = le ? buffer.readUInt32LE(offset + 8) : buffer.readUInt32BE(offset + 8);
return { tag, field, count, valueOffset };
}
function locateGpsTagOffset(buffer: Buffer, offset: number, le: boolean): number {
const numEntries = le ? buffer.readUInt16LE(offset) : buffer.readUInt16BE(offset);
const fieldsStart = offset + 2;
for (let i = 0; i < numEntries; i++) {
const entryOffset = fieldsStart + i * 12;
const { tag, field, count, valueOffset } = parseExifTag(buffer, entryOffset, le);
if (tag === GPS_IFD_TAG && field === 4 && count === 1) {
return valueOffset;
}
}
return -1;
}
function stripGpsFromTiff(buffer: Buffer, offset: number, le: boolean): boolean {
const gpsDirectoryOffset = locateGpsTagOffset(buffer, offset, le);
if (gpsDirectoryOffset >= 0) {
removeGpsEntries(buffer, gpsDirectoryOffset, le);
return true;
}
return false;
}
function stripGpsFromExif(buffer: Buffer, offset: number): boolean {
const headerSlice = buffer.subarray(offset, offset + 8);
const littleEndian = isLE(headerSlice);
const gpsDirectoryOffset = locateGpsTagOffset(buffer, offset + 8, littleEndian);
if (gpsDirectoryOffset >= 0) {
removeGpsEntries(buffer, gpsDirectoryOffset + offset, littleEndian);
return true;
}
return false;
}
export function removeGps(buffer: Buffer): boolean {
const signature = buffer.readUInt32BE(0);
let offset = 0;
let removed = false;
if (signature === PNG_TAG) {
offset += 8;
let chunkLength = 0;
let chunkType = 0;
while (chunkType !== PNG_IEND) {
chunkLength = buffer.readUInt32BE(offset);
chunkType = buffer.readUInt32BE(offset + 4);
if (chunkType === EXIF_PNG_TAG) {
const exifDataOffset = offset + 8;
removed = stripGpsFromExif(buffer, exifDataOffset);
}
if (chunkType !== PNG_IEND) {
offset += 12 + chunkLength;
}
}
} else if (signature === JPEG_EXIF_TAG || signature === JPEG_JFIF_TAG) {
offset += 4;
if (signature === JPEG_JFIF_TAG) {
const jfifSegmentSize = buffer.readUInt16BE(offset);
offset += jfifSegmentSize;
const nextMarker = buffer.readUInt16BE(offset);
if (nextMarker === JPEG_APP1_TAG) {
offset += 2;
} else {
return removed;
}
}
const exifSignature = buffer.readUInt32BE(offset + 2);
if (exifSignature === EXIF_JPEG_TAG) {
offset += 8;
removed = stripGpsFromExif(buffer, offset);
}
} else if (signature === TIFF_LE || signature === TIFF_BE) {
const littleEndian = isLE(buffer);
offset += 4;
const tiffIfdOffset = littleEndian ? buffer.readUInt32LE(offset) : buffer.readUInt32BE(offset);
removed = stripGpsFromTiff(buffer, tiffIfdOffset, littleEndian);
}
return removed;
}