diff --git a/.vscode/settings.json b/.vscode/settings.json index 920390c5..bb3ac392 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -30,8 +30,6 @@ "nanoids", "nextjs", "parameterizes", - "piexif", - "piexifjs", "presigner", "Provia", "qaub", diff --git a/package.json b/package.json index 98906589..7e48c9d3 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f07c44e0..96d53ed6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: {} diff --git a/src/photo/actions.ts b/src/photo/actions.ts index b6673a2e..3311aa61 100644 --- a/src/photo/actions.ts +++ b/src/photo/actions.ts @@ -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; } } diff --git a/src/photo/form/PhotoForm.tsx b/src/photo/form/PhotoForm.tsx index 867e9dfd..26333b01 100644 --- a/src/photo/form/PhotoForm.tsx +++ b/src/photo/form/PhotoForm.tsx @@ -359,6 +359,7 @@ export default function PhotoForm({ type="hidden" name="shouldStripGpsData" value={shouldStripGpsData ? 'true' : 'false'} + readOnly /> {/* Actions */} diff --git a/src/photo/server.ts b/src/photo/server.ts index 249afc48..946374e7 100644 --- a/src/photo/server.ts +++ b/src/photo/server.ts @@ -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 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(); diff --git a/src/photo/storage.ts b/src/photo/storage.ts index 908cd168..b5396615 100644 --- a/src/photo/storage.ts +++ b/src/photo/storage.ts @@ -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 { diff --git a/src/services/storage/aws-s3.ts b/src/services/storage/aws-s3.ts index aa074221..9e7e2fa1 100644 --- a/src/services/storage/aws-s3.ts +++ b/src/services/storage/aws-s3.ts @@ -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 => awsS3Client().send(new PutObjectCommand({ Bucket: AWS_S3_BUCKET, Key: fileName, - Body: Buffer.from(await file.arrayBuffer()), + Body: file, ACL: 'public-read', })) .then(() => urlForKey(fileName)); diff --git a/src/services/storage/cloudflare-r2.ts b/src/services/storage/cloudflare-r2.ts index f1ae31f5..12a45572 100644 --- a/src/services/storage/cloudflare-r2.ts +++ b/src/services/storage/cloudflare-r2.ts @@ -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 => cloudflareR2Client().send(new PutObjectCommand({ Bucket: CLOUDFLARE_R2_BUCKET, Key: fileName, - Body: Buffer.from(await file.arrayBuffer()), + Body: file, })) .then(() => urlForKey(fileName)); diff --git a/src/services/storage/index.ts b/src/services/storage/index.ts index 09f5bfea..5303f603 100644 --- a/src/services/storage/index.ts +++ b/src/services/storage/index.ts @@ -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) { diff --git a/src/services/storage/vercel-blob.ts b/src/services/storage/vercel-blob.ts index 8d6cc26c..60cfdd22 100644 --- a/src/services/storage/vercel-blob.ts +++ b/src/services/storage/vercel-blob.ts @@ -29,7 +29,7 @@ export const vercelBlobUploadFromClient = async ( .then(({ url }) => url); export const vercelBlobPut = ( - file: File | Blob, + file: Buffer, fileName: string, ): Promise => put(fileName, file, { diff --git a/src/utility/exif-server.ts b/src/utility/exif-server.ts deleted file mode 100644 index 4931e57b..00000000 --- a/src/utility/exif-server.ts +++ /dev/null @@ -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 => { - // 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)); -};