Refine GPS-stripping approach

This commit is contained in:
Sam Becker 2024-06-08 11:14:45 -05:00
parent 5ff6009329
commit a09e3b2dba
12 changed files with 65 additions and 92 deletions

View File

@ -30,8 +30,6 @@
"nanoids",
"nextjs",
"parameterizes",
"piexif",
"piexifjs",
"presigner",
"Provia",
"qaub",

View File

@ -47,7 +47,6 @@
"next-auth": "5.0.0-beta.18",
"next-themes": "^0.3.0",
"pg": "^8.12.0",
"piexifjs": "^1.0.6",
"postcss": "8.4.38",
"react": "18.3.1",
"react-dom": "18.3.1",

56
pnpm-lock.yaml generated
View File

@ -122,9 +122,6 @@ importers:
pg:
specifier: ^8.12.0
version: 8.12.0
piexifjs:
specifier: ^1.0.6
version: 1.0.6
postcss:
specifier: 8.4.38
version: 8.4.38
@ -3450,9 +3447,6 @@ packages:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
piexifjs@1.0.6:
resolution: {integrity: sha512-0wVyH0cKohzBQ5Gi2V1BuxYpxWfxF3cSqfFXfPIpl5tl9XLS5z4ogqhUCD20AbHi0h9aJkqXNJnkVev6gwh2ag==}
pify@2.3.0:
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
engines: {node: '>=0.10.0'}
@ -4403,10 +4397,10 @@ snapshots:
'@aws-crypto/sha1-browser': 3.0.0
'@aws-crypto/sha256-browser': 3.0.0
'@aws-crypto/sha256-js': 3.0.0
'@aws-sdk/client-sso-oidc': 3.590.0
'@aws-sdk/client-sts': 3.590.0(@aws-sdk/client-sso-oidc@3.590.0)
'@aws-sdk/client-sso-oidc': 3.590.0(@aws-sdk/client-sts@3.590.0)
'@aws-sdk/client-sts': 3.590.0
'@aws-sdk/core': 3.588.0
'@aws-sdk/credential-provider-node': 3.590.0(@aws-sdk/client-sso-oidc@3.590.0)(@aws-sdk/client-sts@3.590.0(@aws-sdk/client-sso-oidc@3.590.0))
'@aws-sdk/credential-provider-node': 3.590.0(@aws-sdk/client-sso-oidc@3.590.0(@aws-sdk/client-sts@3.590.0))(@aws-sdk/client-sts@3.590.0)
'@aws-sdk/middleware-bucket-endpoint': 3.587.0
'@aws-sdk/middleware-expect-continue': 3.577.0
'@aws-sdk/middleware-flexible-checksums': 3.587.0
@ -4461,13 +4455,13 @@ snapshots:
transitivePeerDependencies:
- aws-crt
'@aws-sdk/client-sso-oidc@3.590.0':
'@aws-sdk/client-sso-oidc@3.590.0(@aws-sdk/client-sts@3.590.0)':
dependencies:
'@aws-crypto/sha256-browser': 3.0.0
'@aws-crypto/sha256-js': 3.0.0
'@aws-sdk/client-sts': 3.590.0(@aws-sdk/client-sso-oidc@3.590.0)
'@aws-sdk/client-sts': 3.590.0
'@aws-sdk/core': 3.588.0
'@aws-sdk/credential-provider-node': 3.590.0(@aws-sdk/client-sso-oidc@3.590.0)(@aws-sdk/client-sts@3.590.0(@aws-sdk/client-sso-oidc@3.590.0))
'@aws-sdk/credential-provider-node': 3.590.0(@aws-sdk/client-sso-oidc@3.590.0(@aws-sdk/client-sts@3.590.0))(@aws-sdk/client-sts@3.590.0)
'@aws-sdk/middleware-host-header': 3.577.0
'@aws-sdk/middleware-logger': 3.577.0
'@aws-sdk/middleware-recursion-detection': 3.577.0
@ -4504,6 +4498,7 @@ snapshots:
'@smithy/util-utf8': 3.0.0
tslib: 2.6.2
transitivePeerDependencies:
- '@aws-sdk/client-sts'
- aws-crt
'@aws-sdk/client-sso@3.590.0':
@ -4549,13 +4544,13 @@ snapshots:
transitivePeerDependencies:
- aws-crt
'@aws-sdk/client-sts@3.590.0(@aws-sdk/client-sso-oidc@3.590.0)':
'@aws-sdk/client-sts@3.590.0':
dependencies:
'@aws-crypto/sha256-browser': 3.0.0
'@aws-crypto/sha256-js': 3.0.0
'@aws-sdk/client-sso-oidc': 3.590.0
'@aws-sdk/client-sso-oidc': 3.590.0(@aws-sdk/client-sts@3.590.0)
'@aws-sdk/core': 3.588.0
'@aws-sdk/credential-provider-node': 3.590.0(@aws-sdk/client-sso-oidc@3.590.0)(@aws-sdk/client-sts@3.590.0(@aws-sdk/client-sso-oidc@3.590.0))
'@aws-sdk/credential-provider-node': 3.590.0(@aws-sdk/client-sso-oidc@3.590.0(@aws-sdk/client-sts@3.590.0))(@aws-sdk/client-sts@3.590.0)
'@aws-sdk/middleware-host-header': 3.577.0
'@aws-sdk/middleware-logger': 3.577.0
'@aws-sdk/middleware-recursion-detection': 3.577.0
@ -4592,7 +4587,6 @@ snapshots:
'@smithy/util-utf8': 3.0.0
tslib: 2.6.2
transitivePeerDependencies:
- '@aws-sdk/client-sso-oidc'
- aws-crt
'@aws-sdk/core@3.588.0':
@ -4624,14 +4618,14 @@ snapshots:
'@smithy/util-stream': 3.0.1
tslib: 2.6.2
'@aws-sdk/credential-provider-ini@3.590.0(@aws-sdk/client-sso-oidc@3.590.0)(@aws-sdk/client-sts@3.590.0(@aws-sdk/client-sso-oidc@3.590.0))':
'@aws-sdk/credential-provider-ini@3.590.0(@aws-sdk/client-sso-oidc@3.590.0(@aws-sdk/client-sts@3.590.0))(@aws-sdk/client-sts@3.590.0)':
dependencies:
'@aws-sdk/client-sts': 3.590.0(@aws-sdk/client-sso-oidc@3.590.0)
'@aws-sdk/client-sts': 3.590.0
'@aws-sdk/credential-provider-env': 3.587.0
'@aws-sdk/credential-provider-http': 3.587.0
'@aws-sdk/credential-provider-process': 3.587.0
'@aws-sdk/credential-provider-sso': 3.590.0(@aws-sdk/client-sso-oidc@3.590.0)
'@aws-sdk/credential-provider-web-identity': 3.587.0(@aws-sdk/client-sts@3.590.0(@aws-sdk/client-sso-oidc@3.590.0))
'@aws-sdk/credential-provider-sso': 3.590.0(@aws-sdk/client-sso-oidc@3.590.0(@aws-sdk/client-sts@3.590.0))
'@aws-sdk/credential-provider-web-identity': 3.587.0(@aws-sdk/client-sts@3.590.0)
'@aws-sdk/types': 3.577.0
'@smithy/credential-provider-imds': 3.1.0
'@smithy/property-provider': 3.1.0
@ -4642,14 +4636,14 @@ snapshots:
- '@aws-sdk/client-sso-oidc'
- aws-crt
'@aws-sdk/credential-provider-node@3.590.0(@aws-sdk/client-sso-oidc@3.590.0)(@aws-sdk/client-sts@3.590.0(@aws-sdk/client-sso-oidc@3.590.0))':
'@aws-sdk/credential-provider-node@3.590.0(@aws-sdk/client-sso-oidc@3.590.0(@aws-sdk/client-sts@3.590.0))(@aws-sdk/client-sts@3.590.0)':
dependencies:
'@aws-sdk/credential-provider-env': 3.587.0
'@aws-sdk/credential-provider-http': 3.587.0
'@aws-sdk/credential-provider-ini': 3.590.0(@aws-sdk/client-sso-oidc@3.590.0)(@aws-sdk/client-sts@3.590.0(@aws-sdk/client-sso-oidc@3.590.0))
'@aws-sdk/credential-provider-ini': 3.590.0(@aws-sdk/client-sso-oidc@3.590.0(@aws-sdk/client-sts@3.590.0))(@aws-sdk/client-sts@3.590.0)
'@aws-sdk/credential-provider-process': 3.587.0
'@aws-sdk/credential-provider-sso': 3.590.0(@aws-sdk/client-sso-oidc@3.590.0)
'@aws-sdk/credential-provider-web-identity': 3.587.0(@aws-sdk/client-sts@3.590.0(@aws-sdk/client-sso-oidc@3.590.0))
'@aws-sdk/credential-provider-sso': 3.590.0(@aws-sdk/client-sso-oidc@3.590.0(@aws-sdk/client-sts@3.590.0))
'@aws-sdk/credential-provider-web-identity': 3.587.0(@aws-sdk/client-sts@3.590.0)
'@aws-sdk/types': 3.577.0
'@smithy/credential-provider-imds': 3.1.0
'@smithy/property-provider': 3.1.0
@ -4669,10 +4663,10 @@ snapshots:
'@smithy/types': 3.0.0
tslib: 2.6.2
'@aws-sdk/credential-provider-sso@3.590.0(@aws-sdk/client-sso-oidc@3.590.0)':
'@aws-sdk/credential-provider-sso@3.590.0(@aws-sdk/client-sso-oidc@3.590.0(@aws-sdk/client-sts@3.590.0))':
dependencies:
'@aws-sdk/client-sso': 3.590.0
'@aws-sdk/token-providers': 3.587.0(@aws-sdk/client-sso-oidc@3.590.0)
'@aws-sdk/token-providers': 3.587.0(@aws-sdk/client-sso-oidc@3.590.0(@aws-sdk/client-sts@3.590.0))
'@aws-sdk/types': 3.577.0
'@smithy/property-provider': 3.1.0
'@smithy/shared-ini-file-loader': 3.1.0
@ -4682,9 +4676,9 @@ snapshots:
- '@aws-sdk/client-sso-oidc'
- aws-crt
'@aws-sdk/credential-provider-web-identity@3.587.0(@aws-sdk/client-sts@3.590.0(@aws-sdk/client-sso-oidc@3.590.0))':
'@aws-sdk/credential-provider-web-identity@3.587.0(@aws-sdk/client-sts@3.590.0)':
dependencies:
'@aws-sdk/client-sts': 3.590.0(@aws-sdk/client-sso-oidc@3.590.0)
'@aws-sdk/client-sts': 3.590.0
'@aws-sdk/types': 3.577.0
'@smithy/property-provider': 3.1.0
'@smithy/types': 3.0.0
@ -4809,9 +4803,9 @@ snapshots:
'@smithy/types': 3.0.0
tslib: 2.6.2
'@aws-sdk/token-providers@3.587.0(@aws-sdk/client-sso-oidc@3.590.0)':
'@aws-sdk/token-providers@3.587.0(@aws-sdk/client-sso-oidc@3.590.0(@aws-sdk/client-sts@3.590.0))':
dependencies:
'@aws-sdk/client-sso-oidc': 3.590.0
'@aws-sdk/client-sso-oidc': 3.590.0(@aws-sdk/client-sts@3.590.0)
'@aws-sdk/types': 3.577.0
'@smithy/property-provider': 3.1.0
'@smithy/shared-ini-file-loader': 3.1.0
@ -8550,8 +8544,6 @@ snapshots:
picomatch@2.3.1: {}
piexifjs@1.0.6: {}
pify@2.3.0: {}
pirates@4.0.6: {}

View File

@ -108,6 +108,7 @@ export const addAllUploadsAction = async ({
photoFormExif,
imageResizedBase64,
shouldStripGpsData,
fileBytes,
} = await extractImageDataFromBlobPath(url, {
includeInitialPhotoFields: true,
generateBlurData: BLUR_ENABLED,
@ -152,6 +153,7 @@ export const addAllUploadsAction = async ({
const updatedUrl = await convertUploadToPhoto(
url,
shouldStripGpsData,
fileBytes,
);
if (updatedUrl) {
stream.update({
@ -184,6 +186,7 @@ export const updatePhotoAction = async (formData: FormData) =>
let url: string | undefined;
if (photo.hidden && photo.url.includes(photo.id)) {
// Backfill:
// Anonymize storage url on update if necessary by
// re-running image upload transfer logic
url = await convertUploadToPhoto(photo.url);
@ -303,6 +306,7 @@ export const syncPhotoAction = async (formData: FormData) =>
photoFormExif,
imageResizedBase64,
shouldStripGpsData,
fileBytes,
} = await extractImageDataFromBlobPath(photo.url, {
includeInitialPhotoFields: false,
generateBlurData: BLUR_ENABLED,
@ -316,6 +320,7 @@ export const syncPhotoAction = async (formData: FormData) =>
const url = await convertUploadToPhoto(
photo.url,
shouldStripGpsData,
fileBytes,
);
if (url) { photo.url = url; }
}

View File

@ -359,6 +359,7 @@ export default function PhotoForm({
type="hidden"
name="shouldStripGpsData"
value={shouldStripGpsData ? 'true' : 'false'}
readOnly
/>
</div>
{/* Actions */}

View File

@ -11,7 +11,7 @@ import { ExifData, ExifParserFactory } from 'ts-exif-parser';
import { PhotoFormData } from './form';
import { FilmSimulation } from '@/simulation';
import sharp, { Sharp } from 'sharp';
import { GEO_PRIVACY_ENABLED } from '@/site/config';
import { GEO_PRIVACY_ENABLED, PRO_MODE_ENABLED } from '@/site/config';
const IMAGE_WIDTH_RESIZE = 200;
const IMAGE_WIDTH_BLUR = 200;
@ -28,6 +28,7 @@ export const extractImageDataFromBlobPath = async (
photoFormExif?: Partial<PhotoFormData>
imageResizedBase64?: string
shouldStripGpsData?: boolean
fileBytes?: ArrayBuffer
}> => {
const {
includeInitialPhotoFields,
@ -100,6 +101,7 @@ export const extractImageDataFromBlobPath = async (
},
imageResizedBase64,
shouldStripGpsData,
fileBytes,
};
};
@ -134,20 +136,27 @@ export const blurImageFromUrl = async (url: string) =>
.then(res => res.arrayBuffer())
.then(buffer => blurImage(buffer));
const GPS_NULL_STRING = '-';
export const removeGpsData = async (image: ArrayBuffer) =>
generateBase64(image, sharp => sharp
sharp(image)
.withExifMerge({
IFD3: {
GPSVersionID: '-',
GPSMapDatum: '-',
GPSLatitudeRef: '-',
GPSLatitude: '-',
GPSLongitudeRef: '-',
GPSLongitude: '-',
GPSTimeStamp: '-',
GPSAltitude: '-',
GPSAltitudeRef: '-',
GPSMapDatum: GPS_NULL_STRING,
GPSLatitudeRef: GPS_NULL_STRING,
GPSLatitude: GPS_NULL_STRING,
GPSLongitudeRef: GPS_NULL_STRING,
GPSLongitude: GPS_NULL_STRING,
GPSTimeStamp: GPS_NULL_STRING,
GPSAltitude: GPS_NULL_STRING,
GPSAltitudeRef: GPS_NULL_STRING,
GPSSatellites: GPS_NULL_STRING,
GPSDestLatitude: GPS_NULL_STRING,
GPSDestLongitudeRef: GPS_NULL_STRING,
GPSDestDistance: GPS_NULL_STRING,
GPSDestDistanceRef: GPS_NULL_STRING,
GPSAreaInformation: GPS_NULL_STRING,
},
})
.jpeg({ quality: 100 })
);
.toFormat('jpeg', { quality: PRO_MODE_ENABLED ? 100 : 80 })
.toBuffer();

View File

@ -5,28 +5,23 @@ import {
moveFile,
putFile,
} from '@/services/storage';
import { stripGpsFromFile } from '@/utility/exif-server';
import { removeGpsData } from './server';
export const convertUploadToPhoto = async (
urlOrigin: string,
stripGps?: boolean,
fileBytes?: ArrayBuffer,
) => {
const fileName = generateRandomFileNameForPhoto();
const fileExtension = getExtensionFromStorageUrl(urlOrigin);
const photoPath = `${fileName}.${fileExtension || 'jpg'}`;
if (stripGps) {
console.log('Fetching original file');
const fileBytes = await fetch(urlOrigin, { cache: 'no-store' })
.then(res => res.arrayBuffer());
const fileWithoutGps = await stripGpsFromFile(fileBytes);
console.log('Uploading file without GPS');
const fileWithoutGps = await removeGpsData(
fileBytes ?? await fetch(urlOrigin, { cache: 'no-store' })
.then(res => res.arrayBuffer())
);
return putFile(fileWithoutGps, photoPath).then(async url => {
if (url) {
console.log('Deleting original file');
await deleteFile(urlOrigin);
} else {
console.log('No url found');
}
if (url) { await deleteFile(urlOrigin); }
return url;
});
} else {

View File

@ -33,13 +33,13 @@ export const awsS3PutObjectCommandForKey = (Key: string) =>
new PutObjectCommand({ Bucket: AWS_S3_BUCKET, Key, ACL: 'public-read' });
export const awsS3Put = async (
file: Blob,
file: Buffer,
fileName: string,
): Promise<string> =>
awsS3Client().send(new PutObjectCommand({
Bucket: AWS_S3_BUCKET,
Key: fileName,
Body: Buffer.from(await file.arrayBuffer()),
Body: file,
ACL: 'public-read',
}))
.then(() => urlForKey(fileName));

View File

@ -54,13 +54,13 @@ export const cloudflareR2PutObjectCommandForKey = (Key: string) =>
new PutObjectCommand({ Bucket: CLOUDFLARE_R2_BUCKET, Key });
export const cloudflareR2Put = async (
file: Blob,
file: Buffer,
fileName: string,
): Promise<string> =>
cloudflareR2Client().send(new PutObjectCommand({
Bucket: CLOUDFLARE_R2_BUCKET,
Key: fileName,
Body: Buffer.from(await file.arrayBuffer()),
Body: file,
}))
.then(() => urlForKey(fileName));

View File

@ -136,7 +136,7 @@ export const uploadPhotoFromClient = async (
: vercelBlobUploadFromClient(file, `${PREFIX_UPLOAD}.${extension}`);
export const putFile = (
file: Blob,
file: Buffer,
fileName: string,
) => {
switch (CURRENT_STORAGE) {

View File

@ -29,7 +29,7 @@ export const vercelBlobUploadFromClient = async (
.then(({ url }) => url);
export const vercelBlobPut = (
file: File | Blob,
file: Buffer,
fileName: string,
): Promise<string> =>
put(fileName, file, {

View File

@ -1,26 +0,0 @@
// import * as PiExif from 'piexifjs';
import { b64toBlob } from './data';
import { removeGpsData } from '@/photo/server';
export const stripGpsFromFile = async (
fileBytes: ArrayBuffer
): Promise<Blob> => {
// const base64 = Buffer.from(fileBytes).toString('base64');
// const base64Url = `data:image/jpeg;base64,${base64}`;
// console.log('Stripping GPS from file');
// const exifObject = PiExif.load(base64Url);
// delete exifObject.GPS;
// const exifDataWithoutGps = PiExif.dump(exifObject);
// console.log('Updating EXIF');
// const data = PiExif.insert(
// exifDataWithoutGps,
// base64Url,
// );
// console.log('EXIF updated');
// Removing EXIF data with Sharp
return b64toBlob(await removeGpsData(fileBytes));
};