diff --git a/web/apps/photos/src/services/upload/uploadService.ts b/web/apps/photos/src/services/upload/uploadService.ts index 98729f4ce..db6e3643e 100644 --- a/web/apps/photos/src/services/upload/uploadService.ts +++ b/web/apps/photos/src/services/upload/uploadService.ts @@ -196,8 +196,8 @@ export const uploader = async ( * (tee will not work for strictly sequential reads of large streams). */ - const { fileTypeInfo, fileSize } = - await readFileTypeInfoAndSize(uploadAsset); + const { fileTypeInfo, fileSize, lastModifiedMs } = + await readAssetDetails(uploadAsset); const maxFileSize = 4 * 1024 * 1024 * 1024; /* 4 GB */ if (fileSize >= maxFileSize) @@ -208,6 +208,7 @@ export const uploader = async ( const { metadata, publicMagicMetadata } = await extractAssetMetadata( uploadAsset, fileTypeInfo, + lastModifiedMs, collection.id, parsedMetadataJSONMap, worker, @@ -419,27 +420,28 @@ const readFileOrPath = async ( return { dataOrStream, fileSize, lastModifiedMs }; }; +interface ReadAssetDetailsResult { + fileTypeInfo: FileTypeInfo; + fileSize: number; + lastModifiedMs: number; +} + /** - * Read the file(s) to determine the type and size of the given {@link asset}. + * Read the file(s) to determine the type, size and last modified time of the + * given {@link asset}. */ -const readFileTypeInfoAndSize = async ({ +const readAssetDetails = async ({ isLivePhoto, livePhotoAssets, file, -}: UploadAsset2): Promise<{ fileTypeInfo: FileTypeInfo; fileSize: number }> => +}: UploadAsset2): Promise => isLivePhoto - ? readLivePhotoFileTypeInfoAndSize(livePhotoAssets) - : readFOPFileTypeInfoAndSize(file); + ? readLivePhotoDetails(livePhotoAssets) + : readImageOrVideoDetails(file); -const readLivePhotoFileTypeInfoAndSize = async ({ - image, - video, -}: LivePhotoAssets2): Promise<{ - fileTypeInfo: FileTypeInfo; - fileSize: number; -}> => { - const img = await readFOPFileTypeInfoAndSize(image); - const vid = await readFOPFileTypeInfoAndSize(video); +const readLivePhotoDetails = async ({ image, video }: LivePhotoAssets2) => { + const img = await readImageOrVideoDetails(image); + const vid = await readImageOrVideoDetails(video); return { fileTypeInfo: { @@ -449,6 +451,7 @@ const readLivePhotoFileTypeInfoAndSize = async ({ videoType: vid.fileTypeInfo.extension, }, fileSize: img.fileSize + vid.fileSize, + lastModifiedMs: img.lastModifiedMs, }; }; @@ -457,14 +460,14 @@ const readLivePhotoFileTypeInfoAndSize = async ({ * fallback, to determine its MIME type. From that, construct and return a * {@link FileTypeInfo}. * - * While we're at it, also return the size of the file. + * While we're at it, also return the size of the file, and its last modified + * time (expressed as epoch milliseconds). * * @param fileOrPath See: [Note: Reading a fileOrPath] */ -const readFOPFileTypeInfoAndSize = async ( - fileOrPath: File | string, -): Promise<{ fileTypeInfo: FileTypeInfo; fileSize: number }> => { - const { dataOrStream, fileSize } = await readFileOrPath(fileOrPath); +const readImageOrVideoDetails = async (fileOrPath: File | string) => { + const { dataOrStream, fileSize, lastModifiedMs } = + await readFileOrPath(fileOrPath); const fileTypeInfo = await detectFileTypeInfoFromChunk(async () => { if (dataOrStream instanceof Uint8Array) { @@ -477,7 +480,7 @@ const readFOPFileTypeInfoAndSize = async ( } }, getFileName(fileOrPath)); - return { fileTypeInfo, fileSize }; + return { fileTypeInfo, fileSize, lastModifiedMs }; }; /** @@ -503,6 +506,7 @@ interface ExtractAssetMetadataResult { const extractAssetMetadata = async ( { isLivePhoto, file, livePhotoAssets }: UploadAsset2, fileTypeInfo: FileTypeInfo, + lastModifiedMs: number, collectionID: number, parsedMetadataJSONMap: Map, worker: Remote, @@ -511,6 +515,7 @@ const extractAssetMetadata = async ( ? await extractLivePhotoMetadata( livePhotoAssets, fileTypeInfo, + lastModifiedMs, collectionID, parsedMetadataJSONMap, worker, @@ -518,6 +523,7 @@ const extractAssetMetadata = async ( : await extractImageOrVideoMetadata( file, fileTypeInfo, + lastModifiedMs, collectionID, parsedMetadataJSONMap, worker, @@ -526,6 +532,7 @@ const extractAssetMetadata = async ( const extractLivePhotoMetadata = async ( livePhotoAssets: LivePhotoAssets2, fileTypeInfo: FileTypeInfo, + lastModifiedMs: number, collectionID: number, parsedMetadataJSONMap: Map, worker: Remote, @@ -538,6 +545,7 @@ const extractLivePhotoMetadata = async ( await extractImageOrVideoMetadata( livePhotoAssets.image, imageFileTypeInfo, + lastModifiedMs, collectionID, parsedMetadataJSONMap, worker, @@ -561,6 +569,7 @@ const extractLivePhotoMetadata = async ( const extractImageOrVideoMetadata = async ( fileOrPath: File | string, fileTypeInfo: FileTypeInfo, + lastModifiedMs: number, collectionID: number, parsedMetadataJSONMap: Map, worker: Remote, @@ -571,8 +580,11 @@ const extractImageOrVideoMetadata = async ( let extractedMetadata: ParsedExtractedMetadata; if (fileType === FILE_TYPE.IMAGE) { extractedMetadata = - (await tryExtractImageMetadata(fileOrPath, fileTypeInfo)) ?? - NULL_EXTRACTED_METADATA; + (await tryExtractImageMetadata( + fileOrPath, + fileTypeInfo, + lastModifiedMs, + )) ?? NULL_EXTRACTED_METADATA; } else if (fileType === FILE_TYPE.VIDEO) { extractedMetadata = (await tryExtractVideoMetadata(fileOrPath)) ?? @@ -583,13 +595,16 @@ const extractImageOrVideoMetadata = async ( const hash = await computeHash(fileOrPath, worker); + const modificationTime = lastModifiedMs * 1000; + const creationTime = + extractedMetadata.creationTime ?? + tryParseEpochMicrosecondsFromFileName(fileName) ?? + modificationTime; + const metadata: Metadata = { title: fileName, - creationTime: - extractedMetadata.creationTime ?? - tryParseEpochMicrosecondsFromFileName(fileName) ?? - receivedFile.lastModified * 1000, - modificationTime: receivedFile.lastModified * 1000, + creationTime, + modificationTime, latitude: extractedMetadata.location.latitude, longitude: extractedMetadata.location.longitude, fileType, @@ -624,16 +639,13 @@ const NULL_EXTRACTED_METADATA: ParsedExtractedMetadata = { async function tryExtractImageMetadata( fileOrPath: File | string, fileTypeInfo: FileTypeInfo, + lastModifiedMs: number, ): Promise { try { if (!(fileOrPath instanceof File)) { - fileOrPath = new File( - [await fileOrPath.blob()], - fileOrPath.name, - { - lastModified: fileOrPath.lastModified, - }, - ); + fileOrPath = new File([await fileOrPath.blob()], fileOrPath.name, { + lastModified: lastModifiedMs, + }); } return await parseImageMetadata(fileOrPath, fileTypeInfo); } catch (e) {