Strip metadata in the ExifTool worker during upload preview.
Cache stripped blobs, show original vs stripped metadata tabs, and keep user-facing errors on fixed store messages with console logging for diagnostics.
This commit is contained in:
@@ -18,6 +18,11 @@ export const useExiftoolStore = defineStore('exiftool', () => {
|
||||
{ resolve: (output: string) => void; reject: (error: Error) => void }
|
||||
>();
|
||||
|
||||
const stripMetadataWaiters = new Map<
|
||||
string,
|
||||
{ resolve: (data: ArrayBuffer) => void; reject: (error: Error) => void }
|
||||
>();
|
||||
|
||||
const initializeExiftool = async (): Promise<void> => {
|
||||
if (initStatus.value === 'ready') {
|
||||
return;
|
||||
@@ -41,6 +46,14 @@ export const useExiftoolStore = defineStore('exiftool', () => {
|
||||
});
|
||||
|
||||
worker.value.addEventListener('message', handleWorkerMessage);
|
||||
|
||||
worker.value.addEventListener('error', event => {
|
||||
console.error('[exiftool] worker error', event.message, event.error ?? event);
|
||||
});
|
||||
|
||||
worker.value.addEventListener('messageerror', event => {
|
||||
console.error('[exiftool] worker messageerror', event);
|
||||
});
|
||||
}
|
||||
|
||||
worker.value.postMessage({ type: 'init' });
|
||||
@@ -66,6 +79,7 @@ export const useExiftoolStore = defineStore('exiftool', () => {
|
||||
}
|
||||
|
||||
if (message.type === 'init-error') {
|
||||
console.error('[exiftool] init-error', message.message);
|
||||
initStatus.value = 'error';
|
||||
initErrorMessage.value = message.message;
|
||||
initPromiseReject?.(new Error(message.message));
|
||||
@@ -96,6 +110,34 @@ export const useExiftoolStore = defineStore('exiftool', () => {
|
||||
}
|
||||
|
||||
readMetadataWaiters.delete(message.id);
|
||||
console.error('[exiftool] read-metadata-error', { id: message.id, message: message.message });
|
||||
waiter.reject(new Error(message.message));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type === 'strip-metadata-done') {
|
||||
const waiter = stripMetadataWaiters.get(message.id);
|
||||
|
||||
if (!waiter) {
|
||||
return;
|
||||
}
|
||||
|
||||
stripMetadataWaiters.delete(message.id);
|
||||
waiter.resolve(message.data);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type === 'strip-metadata-error') {
|
||||
const waiter = stripMetadataWaiters.get(message.id);
|
||||
|
||||
if (!waiter) {
|
||||
return;
|
||||
}
|
||||
|
||||
stripMetadataWaiters.delete(message.id);
|
||||
console.error('[exiftool] strip-metadata-error', { id: message.id, message: message.message });
|
||||
waiter.reject(new Error(message.message));
|
||||
}
|
||||
};
|
||||
@@ -117,10 +159,30 @@ export const useExiftoolStore = defineStore('exiftool', () => {
|
||||
});
|
||||
};
|
||||
|
||||
const stripAllMetadata = async (file: File): Promise<Blob> => {
|
||||
await initializeExiftool();
|
||||
|
||||
const id = crypto.randomUUID();
|
||||
|
||||
const workerInstance = worker.value;
|
||||
|
||||
if (!workerInstance) {
|
||||
throw new Error('ExifTool worker is unavailable');
|
||||
}
|
||||
|
||||
const data = await new Promise<ArrayBuffer>((resolve, reject) => {
|
||||
stripMetadataWaiters.set(id, { resolve, reject });
|
||||
workerInstance.postMessage({ type: 'strip-metadata', id, file });
|
||||
});
|
||||
|
||||
return new Blob([data], { type: file.type || 'application/octet-stream' });
|
||||
};
|
||||
|
||||
return {
|
||||
initStatus,
|
||||
initErrorMessage,
|
||||
initializeExiftool,
|
||||
readMetadata
|
||||
readMetadata,
|
||||
stripAllMetadata
|
||||
};
|
||||
});
|
||||
|
||||
@@ -7,8 +7,9 @@ const EXIFTOOL_PREPARE_ERROR_MESSAGE = 'Could not prepare ExifTool';
|
||||
const EXIFTOOL_INITIALIZING_MESSAGE = 'Initializing ExifTool';
|
||||
const METADATA_LOADING_MESSAGE = 'Loading metadata';
|
||||
const METADATA_LOAD_ERROR_MESSAGE = 'Could not load image metadata';
|
||||
const METADATA_STRIP_ERROR_MESSAGE = 'Could not strip image metadata';
|
||||
const METADATA_IDLE_MESSAGE = 'Waiting to load metadata';
|
||||
const METADATA_READY_MESSAGE = 'Metadata loaded';
|
||||
const METADATA_READY_MESSAGE = 'Metadata stripped';
|
||||
|
||||
const METADATA_IDLE_PREVIEW: MetadataPreviewEntry = {
|
||||
status: 'idle',
|
||||
@@ -22,11 +23,12 @@ const METADATA_LOADING_PREVIEW: MetadataPreviewEntry = {
|
||||
|
||||
export const useMetadataStore = defineStore('metadata', () => {
|
||||
const previewsByKey = ref<Record<string, MetadataPreviewEntry>>({});
|
||||
const strippedBlobByKey = ref<Record<string, Blob>>({});
|
||||
|
||||
const exiftoolStore = useExiftoolStore();
|
||||
|
||||
const { initStatus: exiftoolInitStatus } = storeToRefs(exiftoolStore);
|
||||
const { initializeExiftool, readMetadata } = exiftoolStore;
|
||||
const { initializeExiftool, readMetadata, stripAllMetadata } = exiftoolStore;
|
||||
|
||||
const makeKeyFromFile = (file: File): string => `${file.name}\0${file.size}\0${file.lastModified}`;
|
||||
|
||||
@@ -40,6 +42,16 @@ export const useMetadataStore = defineStore('metadata', () => {
|
||||
return previewsByKey.value[key] ?? METADATA_IDLE_PREVIEW;
|
||||
};
|
||||
|
||||
const getStrippedBlob = (file?: File | null): Blob | null => {
|
||||
if (!file) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const key = makeKeyFromFile(file);
|
||||
|
||||
return strippedBlobByKey.value[key] ?? null;
|
||||
};
|
||||
|
||||
const loadPreview = async (file: File): Promise<MetadataPreviewEntry> => {
|
||||
const key = makeKeyFromFile(file);
|
||||
|
||||
@@ -72,7 +84,9 @@ export const useMetadataStore = defineStore('metadata', () => {
|
||||
...previewsByKey.value,
|
||||
[key]: METADATA_LOADING_PREVIEW
|
||||
};
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.error('[metadata] ExifTool init failed', { file: file.name, error });
|
||||
|
||||
const entry: MetadataPreviewEntry = {
|
||||
status: 'error',
|
||||
message: EXIFTOOL_PREPARE_ERROR_MESSAGE
|
||||
@@ -84,19 +98,13 @@ export const useMetadataStore = defineStore('metadata', () => {
|
||||
}
|
||||
}
|
||||
|
||||
let output: string;
|
||||
|
||||
try {
|
||||
const output = await readMetadata(file);
|
||||
output = await readMetadata(file);
|
||||
} catch (error) {
|
||||
console.error('[metadata] read failed', { file: file.name, error });
|
||||
|
||||
const entry: MetadataPreviewEntry = {
|
||||
status: 'ready',
|
||||
message: METADATA_READY_MESSAGE,
|
||||
output
|
||||
};
|
||||
|
||||
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
||||
|
||||
return entry;
|
||||
} catch {
|
||||
const entry: MetadataPreviewEntry = {
|
||||
status: 'error',
|
||||
message: METADATA_LOAD_ERROR_MESSAGE
|
||||
@@ -106,6 +114,63 @@ export const useMetadataStore = defineStore('metadata', () => {
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
let strippedBlob: Blob;
|
||||
|
||||
try {
|
||||
strippedBlob = await stripAllMetadata(file);
|
||||
} catch (error) {
|
||||
console.error('[metadata] strip failed', {
|
||||
file: file.name,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
error
|
||||
});
|
||||
|
||||
const entry: MetadataPreviewEntry = {
|
||||
status: 'error',
|
||||
message: METADATA_STRIP_ERROR_MESSAGE
|
||||
};
|
||||
|
||||
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
const strippedFile = new File([strippedBlob], file.name, {
|
||||
type: file.type || strippedBlob.type || 'application/octet-stream',
|
||||
lastModified: file.lastModified
|
||||
});
|
||||
|
||||
let afterStripOutput: string;
|
||||
|
||||
try {
|
||||
afterStripOutput = await readMetadata(strippedFile);
|
||||
} catch (error) {
|
||||
console.error('[metadata] read after strip failed', { file: file.name, error });
|
||||
|
||||
const entry: MetadataPreviewEntry = {
|
||||
status: 'error',
|
||||
message: METADATA_LOAD_ERROR_MESSAGE
|
||||
};
|
||||
|
||||
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
strippedBlobByKey.value = { ...strippedBlobByKey.value, [key]: strippedBlob };
|
||||
|
||||
const entry: MetadataPreviewEntry = {
|
||||
status: 'ready',
|
||||
message: METADATA_READY_MESSAGE,
|
||||
output,
|
||||
afterStripOutput
|
||||
};
|
||||
|
||||
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
||||
|
||||
return entry;
|
||||
};
|
||||
|
||||
const loadPreviews = (files: readonly File[]): Promise<MetadataPreviewEntry[]> =>
|
||||
@@ -113,12 +178,15 @@ export const useMetadataStore = defineStore('metadata', () => {
|
||||
|
||||
const clearAllPreviews = (): void => {
|
||||
previewsByKey.value = {};
|
||||
strippedBlobByKey.value = {};
|
||||
};
|
||||
|
||||
return {
|
||||
previewsByKey,
|
||||
strippedBlobByKey,
|
||||
makeKeyFromFile,
|
||||
getPreview,
|
||||
getStrippedBlob,
|
||||
loadPreview,
|
||||
loadPreviews,
|
||||
clearAllPreviews
|
||||
|
||||
Reference in New Issue
Block a user