diff --git a/web/packages/gallery/components/viewer/data-source.ts b/web/packages/gallery/components/viewer/data-source.ts index 715d152805..ced982eee2 100644 --- a/web/packages/gallery/components/viewer/data-source.ts +++ b/web/packages/gallery/components/viewer/data-source.ts @@ -64,6 +64,14 @@ export type ItemData = PhotoSwipeSlideData & { * portion of the live photo. */ imageURL?: string; + /** + * The original image associated with the file, as a Blob. + * + * - For images, this will be the original image itself. + * - For live photos, this will be the image component of the live photo. + * - For videos, this will be not be present. + */ + originalImageBlob?: Blob; /** * The renderable object URL of the video associated with the file. * @@ -283,8 +291,9 @@ const enqueueUpdates = async (file: EnteFile) => { const sourceURLs = await downloadManager.renderableSourceURLs(file); const imageURL = ensureString(sourceURLs.url); + const originalImageBlob = sourceURLs.originalImageBlob!; const itemData = await withDimensions(imageURL); - update({ ...itemData, imageURL }); + update({ ...itemData, imageURL, originalImageBlob }); break; } @@ -301,8 +310,16 @@ const enqueueUpdates = async (file: EnteFile) => { await downloadManager.renderableSourceURLs(file); const livePhotoSourceURLs = sourceURLs.url as LivePhotoSourceURL; - const imageURL = await livePhotoSourceURLs.image(); - const imageData = await withDimensions(ensureString(imageURL)); + const imageURL = ensureString( + await livePhotoSourceURLs.image(), + ); + const originalImageBlob = + (await livePhotoSourceURLs.originalImageBlob())!; + const imageData = { + ...(await withDimensions(imageURL)), + imageURL, + originalImageBlob, + }; update(imageData); const videoURL = await livePhotoSourceURLs.video(); update({ ...imageData, videoURL }); @@ -390,7 +407,7 @@ export const fileInfoExifForFile = ( * * This function is expected to be called when an item is loaded as PhotoSwipe * content. It can be safely called multiple times - it will ignore calls until - * the item has an associated {@link imageURL}, and it will also ignore calls + * the item has an associated {@link originalImageBlob}, and it will also ignore calls * that are made after exif data has already been extracted. * * If required, it will extract the exif data from the file, massage it to a @@ -401,7 +418,7 @@ export const fileInfoExifForFile = ( * See also {@link forgetExifForItemData}. */ export const updateFileInfoExifIfNeeded = async (itemData: ItemData) => { - const { fileID, fileType, imageURL } = itemData; + const { fileID, fileType, originalImageBlob } = itemData; // We already have it available. if (_state.fileInfoExifByFileID.has(fileID)) return; @@ -418,11 +435,10 @@ export const updateFileInfoExifIfNeeded = async (itemData: ItemData) => { } // This is not a video, but the original image is not available yet. - if (!imageURL) return; + if (!originalImageBlob) return; try { - const blob = await (await fetch(imageURL)).blob(); - const file = new File([blob], ""); + const file = new File([originalImageBlob], ""); const tags = await extractRawExif(file); const parsed = parseExif(tags); return updateNotifyAndReturn({ tags, parsed }); diff --git a/web/packages/gallery/services/download.ts b/web/packages/gallery/services/download.ts index b36a06cc51..6d811cee09 100644 --- a/web/packages/gallery/services/download.ts +++ b/web/packages/gallery/services/download.ts @@ -26,6 +26,7 @@ import { decodeLivePhoto } from "@/media/live-photo"; export interface LivePhotoSourceURL { image: () => Promise; + originalImageBlob: () => Promise; video: () => Promise; } @@ -52,7 +53,7 @@ export interface LoadedLivePhotoSourceURL { */ export interface RenderableSourceURLs { url: string | LivePhotoSourceURL | LoadedLivePhotoSourceURL; - originalImageURL?: string | undefined; + originalImageBlob?: Blob | undefined; type: "normal" | "livePhoto"; /** * `true` if there is potential conversion that can still be applied. @@ -598,7 +599,9 @@ const createRenderableSourceURLs = async ( : undefined; let url: RenderableSourceURLs["url"] | undefined; - let originalImageURL: RenderableSourceURLs["originalImageURL"] | undefined; + let originalImageBlob: + | RenderableSourceURLs["originalImageBlob"] + | undefined; let type: RenderableSourceURLs["type"] = "normal"; let mimeType: string | undefined; let canForceConvert = false; @@ -609,7 +612,7 @@ const createRenderableSourceURLs = async ( const convertedBlob = await renderableImageBlob(fileBlob, fileName); const convertedURL = existingOrNewObjectURL(convertedBlob); url = convertedURL; - originalImageURL = originalFileURL; + originalImageBlob = fileBlob; mimeType = convertedBlob.type; break; } @@ -646,7 +649,7 @@ const createRenderableSourceURLs = async ( // @ts-ignore return { url: url!, - originalImageURL, + originalImageBlob, type, mimeType, canForceConvert, @@ -671,6 +674,15 @@ async function getRenderableLivePhotoURL( } }; + const getOriginalImageBlob = async () => { + try { + return new Blob([livePhoto.imageData]); + } catch { + //ignore and return null + return undefined; + } + }; + const getRenderableLivePhotoVideoURL = async () => { try { const videoBlob = new Blob([livePhoto.videoData]); @@ -689,6 +701,7 @@ async function getRenderableLivePhotoURL( return { image: getRenderableLivePhotoImageURL, + originalImageBlob: getOriginalImageBlob, video: getRenderableLivePhotoVideoURL, }; }