185 lines
5.5 KiB
TypeScript
185 lines
5.5 KiB
TypeScript
import { defineStore, storeToRefs } from 'pinia';
|
|
import { ref } from 'vue';
|
|
import { useExiftoolStore } from '@/stores/exiftool';
|
|
import type { MetadataPreviewEntry } from '@/types/metadata/MetadataPreview';
|
|
import { makeKeyFromFile } from '@/utils/file/makeKeyFromFile';
|
|
|
|
const METADATA_IDLE_MESSAGE = 'Waiting to load metadata...';
|
|
const EXIFTOOL_INITIALIZING_MESSAGE = 'Initializing ExifTool. It may take a minute on slow networks...';
|
|
const METADATA_PROCESSING_MESSAGE = 'Stripping metadata...';
|
|
const METADATA_READY_MESSAGE = 'Metadata stripped';
|
|
const METADATA_LOAD_ERROR_MESSAGE = 'Could not load image metadata';
|
|
const METADATA_STRIP_ERROR_MESSAGE = 'Could not strip image metadata';
|
|
const EXIFTOOL_PREPARE_ERROR_MESSAGE = 'Could not prepare ExifTool';
|
|
|
|
const METADATA_IDLE_PREVIEW: MetadataPreviewEntry = {
|
|
status: 'idle',
|
|
message: METADATA_IDLE_MESSAGE
|
|
};
|
|
|
|
const METADATA_LOAD_ERROR_PREVIEW: MetadataPreviewEntry = {
|
|
status: 'error',
|
|
message: METADATA_LOAD_ERROR_MESSAGE
|
|
};
|
|
|
|
const EXIFTOOL_PREPARE_ERROR_PREVIEW: MetadataPreviewEntry = {
|
|
status: 'error',
|
|
message: EXIFTOOL_PREPARE_ERROR_MESSAGE
|
|
};
|
|
|
|
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, stripAllMetadata } = exiftoolStore;
|
|
|
|
const setPreviewEntry = (key: string, entry: MetadataPreviewEntry): void => {
|
|
previewsByKey.value = { ...previewsByKey.value, [key]: entry };
|
|
};
|
|
|
|
const setStrippedBlob = (key: string, blob: Blob): void => {
|
|
strippedBlobByKey.value = { ...strippedBlobByKey.value, [key]: blob };
|
|
};
|
|
|
|
const getPreview = (file?: File | null): MetadataPreviewEntry => {
|
|
if (!file) {
|
|
return METADATA_IDLE_PREVIEW;
|
|
}
|
|
|
|
const key = makeKeyFromFile(file);
|
|
|
|
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);
|
|
|
|
if (exiftoolInitStatus.value === 'error') {
|
|
setPreviewEntry(key, EXIFTOOL_PREPARE_ERROR_PREVIEW);
|
|
|
|
return EXIFTOOL_PREPARE_ERROR_PREVIEW;
|
|
}
|
|
|
|
if (exiftoolInitStatus.value !== 'ready') {
|
|
setPreviewEntry(key, { status: 'initializing', message: EXIFTOOL_INITIALIZING_MESSAGE });
|
|
|
|
try {
|
|
await initializeExiftool();
|
|
} catch (error) {
|
|
console.error('[metadata] ExifTool init failed', { file: file.name, error });
|
|
|
|
setPreviewEntry(key, EXIFTOOL_PREPARE_ERROR_PREVIEW);
|
|
|
|
return EXIFTOOL_PREPARE_ERROR_PREVIEW;
|
|
}
|
|
}
|
|
|
|
setPreviewEntry(key, { status: 'processing', message: METADATA_PROCESSING_MESSAGE });
|
|
|
|
let output: string;
|
|
|
|
try {
|
|
output = await readMetadata(file);
|
|
} catch (error) {
|
|
console.error('[metadata] read failed', { file: file.name, error });
|
|
|
|
setPreviewEntry(key, METADATA_LOAD_ERROR_PREVIEW);
|
|
|
|
return METADATA_LOAD_ERROR_PREVIEW;
|
|
}
|
|
|
|
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,
|
|
output
|
|
};
|
|
|
|
setPreviewEntry(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 });
|
|
|
|
setPreviewEntry(key, METADATA_LOAD_ERROR_PREVIEW);
|
|
|
|
return METADATA_LOAD_ERROR_PREVIEW;
|
|
}
|
|
|
|
setStrippedBlob(key, strippedBlob);
|
|
|
|
const entry: MetadataPreviewEntry = {
|
|
status: 'ready',
|
|
message: METADATA_READY_MESSAGE,
|
|
output,
|
|
afterStripOutput
|
|
};
|
|
|
|
setPreviewEntry(key, entry);
|
|
|
|
return entry;
|
|
};
|
|
|
|
const loadPreviews = async (files: readonly File[]): Promise<MetadataPreviewEntry[]> => {
|
|
const results: MetadataPreviewEntry[] = [];
|
|
|
|
for (const file of files) {
|
|
const result = await loadPreview(file);
|
|
|
|
results.push(result);
|
|
}
|
|
|
|
return results;
|
|
};
|
|
|
|
const clearAllPreviews = (): void => {
|
|
previewsByKey.value = {};
|
|
strippedBlobByKey.value = {};
|
|
};
|
|
|
|
return {
|
|
previewsByKey,
|
|
strippedBlobByKey,
|
|
getPreview,
|
|
getStrippedBlob,
|
|
loadPreview,
|
|
loadPreviews,
|
|
clearAllPreviews
|
|
};
|
|
});
|