Do not remove orientation EXIF data (#683)

* Keep orientation metadata

* npm install piexifjs

---------

Co-authored-by: ekzyis <ek@stacker.news>
This commit is contained in:
ekzyis 2023-12-19 16:45:21 +01:00 committed by GitHub
parent 7c49aa55e2
commit 6682817578
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 93 additions and 4 deletions

View File

@ -8,6 +8,7 @@ import { UPLOAD_TYPES_ALLOW } from '../lib/constants'
import { useToast } from './toast'
import gql from 'graphql-tag'
import { useMutation } from '@apollo/client'
import piexif from 'piexifjs'
export function decodeOriginalUrl (imgproxyUrl) {
const parts = imgproxyUrl.split('/')
@ -153,7 +154,6 @@ export const ImageUpload = forwardRef(({ children, className, onSelect, onUpload
const s3Upload = useCallback(async file => {
const img = new window.Image()
file = await removeExifData(file)
img.src = window.URL.createObjectURL(file)
return new Promise((resolve, reject) => {
img.onload = async () => {
onUpload?.(file)
@ -201,6 +201,8 @@ export const ImageUpload = forwardRef(({ children, className, onSelect, onUpload
onSuccess?.({ ...variables, id, name: file.name, url, file })
resolve(id)
}
img.onerror = reject
img.src = window.URL.createObjectURL(file)
})
}, [toaster, getSignedPOST])
@ -235,7 +237,8 @@ export const ImageUpload = forwardRef(({ children, className, onSelect, onUpload
})
// from https://stackoverflow.com/a/77472484
const removeExifData = (file) => {
const removeExifData = async (file) => {
if (!file || !file.type.startsWith('image/')) return file
const cleanBuffer = (arrayBuffer) => {
let dataView = new DataView(arrayBuffer)
const exifMarker = 0xffe1
@ -260,8 +263,50 @@ const removeExifData = (file) => {
modifiedBuffer.set(new Uint8Array(buffer.slice(offset + length)), offset)
return modifiedBuffer.buffer
}
return new Promise((resolve) => {
if (!file || !file.type.startsWith('image/')) return resolve(file)
function getOrientation (file) {
const fr = new window.FileReader()
return new Promise((resolve, reject) => {
fr.onload = function () {
const view = new DataView(this.result)
if (view.getUint16(0, false) !== 0xFFD8) {
// not JPEG
return resolve(-2)
}
const length = view.byteLength; let offset = 2
while (offset < length) {
if (view.getUint16(offset + 2, false) <= 8) return resolve(-1) // no orientation available
const marker = view.getUint16(offset, false)
offset += 2
if (marker === 0xFFE1) {
if (view.getUint32(offset += 2, false) !== 0x45786966) {
// no orientation available
return resolve(-1)
}
const little = view.getUint16(offset += 6, false) === 0x4949
offset += view.getUint32(offset + 4, little)
const tags = view.getUint16(offset, little)
offset += 2
for (let i = 0; i < tags; i++) {
if (view.getUint16(offset + (i * 12), little) === 0x0112) {
// orientation available
return resolve(view.getUint16(offset + (i * 12) + 8, little))
}
}
} else if ((marker & 0xFF00) !== 0xFF00) {
break
} else {
offset += view.getUint16(offset, false)
}
}
// no orientation available
return resolve(-1)
}
fr.onerror = reject
fr.readAsArrayBuffer(file)
})
}
const orientation = await getOrientation(file)
const cleanFile = await new Promise((resolve, reject) => {
const fr = new window.FileReader()
fr.onload = function () {
const cleanedBuffer = cleanBuffer(this.result)
@ -269,6 +314,38 @@ const removeExifData = (file) => {
const newFile = new File([blob], file.name, { type: file.type })
resolve(newFile)
}
fr.onerror = reject
fr.readAsArrayBuffer(file)
})
if (orientation <= 0) {
// not orientation available (-1) or not JPEG (-2)
return cleanFile
}
// put orientation value back in
return new Promise((resolve, reject) => {
const fr = new window.FileReader()
fr.onload = function () {
const zeroth = {}
// Orientation is of type SHORT so single int is ok, see https://piexifjs.readthedocs.io/en/latest/appendices.html
zeroth[piexif.ImageIFD.Orientation] = orientation
const exifObj = { '0th': zeroth }
const exifStr = piexif.dump(exifObj)
const inserted = piexif.insert(exifStr, this.result)
const dataUriToBuffer = (dataUri) => {
// data-uri scheme regexp from https://github.com/ragingwind/data-uri-regex/blob/a9d7474c833e8fbf5b1821fe65d8cccd6aea4536/index.js
// data:[<media type>][;charset=<character set>][;base64],<data>
const regexp = /^(data:)([\w/+-]*)(;charset=[\w-]+|;base64){0,1},(.*)/gi
const b64 = regexp.exec(dataUri)[4]
const buf = Buffer.from(b64, 'base64')
return buf
}
const buf = dataUriToBuffer(inserted)
const blob = new Blob([buf], { type: file.type })
const newFile = new File([blob], file.name, { type: file.type })
resolve(newFile)
}
fr.onerror = reject
// piexifjs library needs data URI as input
fr.readAsDataURL(cleanFile)
})
}

11
package-lock.json generated
View File

@ -56,6 +56,7 @@
"page-metadata-parser": "^1.1.4",
"pageres": "^7.1.0",
"pg-boss": "^9.0.3",
"piexifjs": "^1.0.6",
"prisma": "^5.4.2",
"qrcode.react": "^3.1.0",
"react": "^18.2.0",
@ -12281,6 +12282,11 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/piexifjs": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/piexifjs/-/piexifjs-1.0.6.tgz",
"integrity": "sha512-0wVyH0cKohzBQ5Gi2V1BuxYpxWfxF3cSqfFXfPIpl5tl9XLS5z4ogqhUCD20AbHi0h9aJkqXNJnkVev6gwh2ag=="
},
"node_modules/pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
@ -25152,6 +25158,11 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
},
"piexifjs": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/piexifjs/-/piexifjs-1.0.6.tgz",
"integrity": "sha512-0wVyH0cKohzBQ5Gi2V1BuxYpxWfxF3cSqfFXfPIpl5tl9XLS5z4ogqhUCD20AbHi0h9aJkqXNJnkVev6gwh2ag=="
},
"pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",

View File

@ -59,6 +59,7 @@
"page-metadata-parser": "^1.1.4",
"pageres": "^7.1.0",
"pg-boss": "^9.0.3",
"piexifjs": "^1.0.6",
"prisma": "^5.4.2",
"qrcode.react": "^3.1.0",
"react": "^18.2.0",