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:
parent
7c49aa55e2
commit
6682817578
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue