mirror of
https://github.com/diced/zipline.git
synced 2025-12-12 15:50:11 -08:00
feat: new gps remover
This commit is contained in:
@@ -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
9
pnpm-lock.yaml
generated
@@ -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: {}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
14
src/lib/gps/constants.ts
Normal 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
131
src/lib/gps/index.ts
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user