feat: added exif cleanup
This commit is contained in:
parent
2ec8dbdc69
commit
885d8d1ec2
@ -12,7 +12,7 @@
|
||||
|
||||
.blob-list .blob {
|
||||
@apply p-1 hover:bg-neutral-700 rounded-md grid pr-4;
|
||||
grid-template-columns: 2em auto 6em 10em 7em 1em;
|
||||
grid-template-columns: 2em auto 6em 10em 7em 3em;
|
||||
}
|
||||
|
||||
.blob-list .blob span {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { DocumentIcon, ListBulletIcon, PhotoIcon, TrashIcon } from '@heroicons/react/24/outline';
|
||||
import { ClipboardDocumentIcon, DocumentIcon, ListBulletIcon, PhotoIcon, TrashIcon } from '@heroicons/react/24/outline';
|
||||
import { BlobDescriptor } from 'blossom-client-sdk';
|
||||
import { formatDate, formatFileSize } from '../../utils';
|
||||
import './BlobList.css';
|
||||
@ -75,49 +75,28 @@ const BlobList = ({ blobs, onDelete, title }: BlobListProps) => {
|
||||
<span>{formatFileSize(blob.size)}</span>
|
||||
<span>{blob.type && `${blob.type}`}</span>
|
||||
<span>{formatDate(blob.created)}</span>
|
||||
{onDelete && (
|
||||
<span>
|
||||
<a onClick={() => onDelete(blob)}>
|
||||
<TrashIcon />
|
||||
<div>
|
||||
<span>
|
||||
<a onClick={() => {navigator.clipboard.writeText(blob.url)}}>
|
||||
<ClipboardDocumentIcon />
|
||||
</a>
|
||||
</span>
|
||||
)}
|
||||
{onDelete && (
|
||||
|
||||
<span>
|
||||
<a onClick={() => onDelete(blob)}>
|
||||
<TrashIcon />
|
||||
</a>
|
||||
</span> )}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
/*
|
||||
)}
|
||||
} else {
|
||||
return (
|
||||
<div className="blob-list">
|
||||
{blobs.map((blob: BlobDescriptor) => (
|
||||
<div className="blob" key={blob.sha256}>
|
||||
<span>
|
||||
<DocumentIcon />
|
||||
</span>
|
||||
<span>
|
||||
<a href={blob.url} target="_blank">
|
||||
{blob.sha256}
|
||||
</a>
|
||||
</span>
|
||||
<span>{formatFileSize(blob.size)}</span>
|
||||
<span>{blob.type && `${blob.type}`}</span>
|
||||
<span>{formatDate(blob.created)}</span>
|
||||
{onDelete && (
|
||||
<span>
|
||||
<a onClick={() => onDelete(blob)}>
|
||||
<TrashIcon />
|
||||
</a>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}*/
|
||||
|
||||
};
|
||||
|
||||
export default BlobList;
|
||||
|
@ -6,6 +6,7 @@ import { useServerInfo } from '../utils/useServerInfo';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { ArrowUpOnSquareIcon } from '@heroicons/react/24/outline';
|
||||
import ProgressBar from '../components/ProgressBar/ProgressBar';
|
||||
import { removeExifData } from '../exif';
|
||||
|
||||
type TransferStats = {
|
||||
enabled: boolean;
|
||||
@ -20,11 +21,21 @@ function Upload() {
|
||||
const queryClient = useQueryClient();
|
||||
const [transfers, setTransfers] = useState<{ [key: string]: TransferStats }>({});
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
const [cleanPrivateData, setCleanPrivateData] = useState(true);
|
||||
|
||||
const upload = async () => {
|
||||
if (files && files.length) {
|
||||
const filesToUpload: File[] = [];
|
||||
for (const f of files) {
|
||||
if (cleanPrivateData) {
|
||||
filesToUpload.push(await removeExifData(f));
|
||||
} else {
|
||||
filesToUpload.push(f);
|
||||
}
|
||||
}
|
||||
|
||||
if (filesToUpload && filesToUpload.length) {
|
||||
// sum files sizes
|
||||
const totalSize = files.reduce((acc, f) => acc + f.size, 0);
|
||||
const totalSize = filesToUpload.reduce((acc, f) => acc + f.size, 0);
|
||||
|
||||
// set all entries size to totalSize
|
||||
setTransfers(ut => {
|
||||
@ -42,7 +53,7 @@ function Upload() {
|
||||
continue;
|
||||
}
|
||||
const serverUrl = serverInfo[server.name].url;
|
||||
for (const file of files) {
|
||||
for (const file of filesToUpload) {
|
||||
const uploadAuth = await BlossomClient.getUploadAuth(file, signEventTemplate, 'Upload Blob');
|
||||
const newBlob = await BlossomClient.uploadBlob(serverUrl, file, uploadAuth);
|
||||
|
||||
@ -94,7 +105,7 @@ function Upload() {
|
||||
<ArrowUpOnSquareIcon className="w-8 inline" /> Browse or drag & drop
|
||||
</label>
|
||||
|
||||
<div className="cursor-pointer grid gap-2" style={{ gridTemplateColumns: '1em 20em auto' }}>
|
||||
<div className="cursor-pointer grid gap-2" style={{ gridTemplateColumns: '1.5em 20em auto' }}>
|
||||
{servers.map(s => (
|
||||
<>
|
||||
<input
|
||||
@ -118,6 +129,18 @@ function Upload() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="cursor-pointer grid gap-2" style={{ gridTemplateColumns: '1.5em 20em' }}>
|
||||
<input
|
||||
className="w-5 accent-pink-700 hover:accent-pink-600"
|
||||
id="cleanData"
|
||||
type="checkbox"
|
||||
checked={cleanPrivateData}
|
||||
onChange={e => setCleanPrivateData(e.currentTarget.checked)}
|
||||
/>
|
||||
<label htmlFor="cleanData" className="cursor-pointer">
|
||||
Clean private data in images (EXIF)
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
className="p-2 px-4 bg-neutral-600 hover:bg-pink-700 text-white rounded-lg w-2/6"
|
||||
onClick={() => upload()}
|
||||
|
46
src/utils/exif.ts
Normal file
46
src/utils/exif.ts
Normal file
@ -0,0 +1,46 @@
|
||||
/* from: https://stackoverflow.com/a/77472484/47324 */
|
||||
|
||||
const cleanBuffer = (arrayBuffer: ArrayBuffer) => {
|
||||
let dataView = new DataView(arrayBuffer);
|
||||
const exifMarker = 0xffe1;
|
||||
let offset = 2; // Skip the first two bytes (0xFFD8)
|
||||
|
||||
while (offset < dataView.byteLength) {
|
||||
if (dataView.getUint16(offset) === exifMarker) {
|
||||
// Found an EXIF marker
|
||||
const segmentLength = dataView.getUint16(offset + 2, false) + 2;
|
||||
|
||||
// Update the arrayBuffer and dataView
|
||||
arrayBuffer = removeSegment(arrayBuffer, offset, segmentLength);
|
||||
dataView = new DataView(arrayBuffer);
|
||||
} else {
|
||||
// Move to the next marker
|
||||
offset += 2 + dataView.getUint16(offset + 2, false);
|
||||
}
|
||||
}
|
||||
|
||||
return arrayBuffer;
|
||||
};
|
||||
|
||||
const removeSegment = (buffer: ArrayBuffer, offset: number, length: number) => {
|
||||
// Create a new buffer without the specified segment
|
||||
const modifiedBuffer = new Uint8Array(buffer.byteLength - length);
|
||||
modifiedBuffer.set(new Uint8Array(buffer.slice(0, offset)), 0);
|
||||
modifiedBuffer.set(new Uint8Array(buffer.slice(offset + length)), offset);
|
||||
|
||||
return modifiedBuffer.buffer;
|
||||
};
|
||||
export const removeExifData = (file: File): Promise<File> => {
|
||||
return new Promise(resolve => {
|
||||
if (file && file.type.startsWith('image/')) {
|
||||
const fr = new FileReader();
|
||||
fr.onload = function (this: FileReader) {
|
||||
const cleanedBuffer = cleanBuffer(this.result as ArrayBuffer);
|
||||
const blob = new Blob([cleanedBuffer], { type: file.type });
|
||||
const newFile = new File([blob], file.name, { type: file.type });
|
||||
resolve(newFile);
|
||||
};
|
||||
fr.readAsArrayBuffer(file);
|
||||
} else resolve(file);
|
||||
});
|
||||
};
|
Loading…
Reference in New Issue
Block a user