diff --git a/web/apps/photos/src/services/exif.ts b/web/apps/photos/src/services/exif.ts index af3552ab81..0ceab6d289 100644 --- a/web/apps/photos/src/services/exif.ts +++ b/web/apps/photos/src/services/exif.ts @@ -4,7 +4,7 @@ import { validateAndGetCreationUnixTimeInMicroSeconds } from "@ente/shared/time" import { NULL_LOCATION } from "constants/upload"; import exifr from "exifr"; import piexif from "piexifjs"; -import { Location } from "types/upload"; +import { Location, type ParsedExtractedMetadata } from "types/upload"; type ParsedEXIFData = Record & Partial<{ @@ -34,6 +34,60 @@ type RawEXIFData = Record & ImageHeight: number; }>; +const EXIF_TAGS_NEEDED = [ + "DateTimeOriginal", + "CreateDate", + "ModifyDate", + "GPSLatitude", + "GPSLongitude", + "GPSLatitudeRef", + "GPSLongitudeRef", + "DateCreated", + "ExifImageWidth", + "ExifImageHeight", + "ImageWidth", + "ImageHeight", + "PixelXDimension", + "PixelYDimension", + "MetadataDate", +]; + +/** + * Read EXIF data from an image and use that to construct and return an + * {@link ParsedExtractedMetadata}. This function is tailored for use when we + * upload files. + * + * @param fileOrData The image {@link File}, or its contents. + */ +export const parseImageMetadata = async ( + fileOrData: File | Uint8Array, + fileTypeInfo: FileTypeInfo, +): Promise => { + /* + if (!(receivedFile instanceof File)) { + receivedFile = new File( + [await receivedFile.blob()], + receivedFile.name, + { + lastModified: receivedFile.lastModified, + }, + ); + } + */ + const exifData = await getParsedExifData( + fileOrData, + fileTypeInfo, + EXIF_TAGS_NEEDED, + ); + + return { + location: getEXIFLocation(exifData), + creationTime: getEXIFTime(exifData), + width: exifData?.imageWidth ?? null, + height: exifData?.imageHeight ?? null, + }; +}; + export async function getParsedExifData( receivedFile: File, { extension }: FileTypeInfo, diff --git a/web/apps/photos/src/services/upload/metadata.ts b/web/apps/photos/src/services/upload/metadata.ts index f0e0c0f75f..506961aec0 100644 --- a/web/apps/photos/src/services/upload/metadata.ts +++ b/web/apps/photos/src/services/upload/metadata.ts @@ -12,7 +12,7 @@ import { import type { DataStream } from "@ente/shared/utils/data-stream"; import { Remote } from "comlink"; import { FILE_READER_CHUNK_SIZE, NULL_LOCATION } from "constants/upload"; -import { getEXIFLocation, getEXIFTime, getParsedExifData } from "services/exif"; +import { parseImageMetadata } from "services/exif"; import * as ffmpegService from "services/ffmpeg"; import { getElectronFileStream, getFileStream } from "services/readerService"; import { FilePublicMagicMetadataProps } from "types/file"; @@ -30,24 +30,6 @@ import { } from "./takeout"; import { getFileName } from "./uploadService"; -const EXIF_TAGS_NEEDED = [ - "DateTimeOriginal", - "CreateDate", - "ModifyDate", - "GPSLatitude", - "GPSLongitude", - "GPSLatitudeRef", - "GPSLongitudeRef", - "DateCreated", - "ExifImageWidth", - "ExifImageHeight", - "ImageWidth", - "ImageHeight", - "PixelXDimension", - "PixelYDimension", - "MetadataDate", -]; - const NULL_EXTRACTED_METADATA: ParsedExtractedMetadata = { location: NULL_LOCATION, creationTime: null, @@ -84,6 +66,45 @@ export const extractAssetMetadata = async ( ); }; +async function extractLivePhotoMetadata( + worker: Remote, + parsedMetadataJSONMap: Map, + collectionID: number, + fileTypeInfo: FileTypeInfo, + livePhotoAssets: LivePhotoAssets2, +): Promise { + const imageFileTypeInfo: FileTypeInfo = { + fileType: FILE_TYPE.IMAGE, + extension: fileTypeInfo.imageType, + }; + const { + metadata: imageMetadata, + publicMagicMetadata: imagePublicMagicMetadata, + } = await extractFileMetadata( + worker, + parsedMetadataJSONMap, + collectionID, + imageFileTypeInfo, + livePhotoAssets.image, + ); + const videoHash = await getFileHash( + worker, + /* TODO(MR): ElectronFile changes */ + livePhotoAssets.video as File | ElectronFile, + ); + return { + metadata: { + ...imageMetadata, + title: getFileName(livePhotoAssets.image), + fileType: FILE_TYPE.LIVE_PHOTO, + imageHash: imageMetadata.hash, + videoHash: videoHash, + hash: undefined, + }, + publicMagicMetadata: imagePublicMagicMetadata, + }; +} + async function extractFileMetadata( worker: Remote, parsedMetadataJSONMap: Map, @@ -152,7 +173,6 @@ async function getImageMetadata( receivedFile: File | ElectronFile, fileTypeInfo: FileTypeInfo, ): Promise { - let imageMetadata = NULL_EXTRACTED_METADATA; try { if (!(receivedFile instanceof File)) { receivedFile = new File( @@ -163,22 +183,11 @@ async function getImageMetadata( }, ); } - const exifData = await getParsedExifData( - receivedFile, - fileTypeInfo, - EXIF_TAGS_NEEDED, - ); - - imageMetadata = { - location: getEXIFLocation(exifData), - creationTime: getEXIFTime(exifData), - width: exifData?.imageWidth ?? null, - height: exifData?.imageHeight ?? null, - }; + return await parseImageMetadata(receivedFile, fileTypeInfo); } catch (e) { - log.error("getExifData failed", e); + log.error("Failed to parse image metadata", e); + return NULL_EXTRACTED_METADATA; } - return imageMetadata; } // tries to extract date from file name if available else returns null @@ -237,45 +246,6 @@ async function getVideoMetadata(file: File | ElectronFile) { return videoMetadata; } -async function extractLivePhotoMetadata( - worker: Remote, - parsedMetadataJSONMap: Map, - collectionID: number, - fileTypeInfo: FileTypeInfo, - livePhotoAssets: LivePhotoAssets2, -): Promise { - const imageFileTypeInfo: FileTypeInfo = { - fileType: FILE_TYPE.IMAGE, - extension: fileTypeInfo.imageType, - }; - const { - metadata: imageMetadata, - publicMagicMetadata: imagePublicMagicMetadata, - } = await extractFileMetadata( - worker, - parsedMetadataJSONMap, - collectionID, - imageFileTypeInfo, - livePhotoAssets.image, - ); - const videoHash = await getFileHash( - worker, - /* TODO(MR): ElectronFile changes */ - livePhotoAssets.video as File | ElectronFile, - ); - return { - metadata: { - ...imageMetadata, - title: getFileName(livePhotoAssets.image), - fileType: FILE_TYPE.LIVE_PHOTO, - imageHash: imageMetadata.hash, - videoHash: videoHash, - hash: undefined, - }, - publicMagicMetadata: imagePublicMagicMetadata, - }; -} - async function getFileHash( worker: Remote, file: File | ElectronFile,