From 85ac983ab9092edd36770925eff1bc6322d8ad8d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 23 Jun 2025 14:24:40 +0530 Subject: [PATCH] edits time --- web/apps/photos/src/components/FileList.tsx | 6 ++--- .../src/components/FileListWithViewer.tsx | 6 ++--- .../photos/src/services/upload-manager.ts | 7 +++-- web/apps/photos/tests/upload.test.ts | 10 +++---- web/packages/media/file-metadata.ts | 11 ++++++++ web/packages/media/file.ts | 7 ++--- web/packages/new/photos/services/export.ts | 15 +++++------ web/packages/new/photos/services/files.ts | 26 +++++++++---------- .../new/photos/services/ml/cluster.ts | 3 ++- web/packages/new/photos/services/ml/people.ts | 5 ++-- 10 files changed, 55 insertions(+), 41 deletions(-) diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index e37c2777f5..d5f460fa98 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -11,7 +11,7 @@ import { formattedDateRelative } from "ente-base/i18n-date"; import log from "ente-base/log"; import { downloadManager } from "ente-gallery/services/download"; import { EnteFile } from "ente-media/file"; -import { fileDurationString } from "ente-media/file-metadata"; +import { fileCreationTime, fileDurationString } from "ente-media/file-metadata"; import { FileType } from "ente-media/file-type"; import { GAP_BTW_TILES, @@ -360,11 +360,11 @@ export const FileList: React.FC = ({ if ( !currentDate || !isSameDay( - new Date(item.file.metadata.creationTime / 1000), + new Date(fileCreationTime(item.file) / 1000), new Date(currentDate), ) ) { - currentDate = item.file.metadata.creationTime / 1000; + currentDate = fileCreationTime(item.file) / 1000; timeStampList.push({ tag: "date", diff --git a/web/apps/photos/src/components/FileListWithViewer.tsx b/web/apps/photos/src/components/FileListWithViewer.tsx index d482074f95..b1e18c5890 100644 --- a/web/apps/photos/src/components/FileListWithViewer.tsx +++ b/web/apps/photos/src/components/FileListWithViewer.tsx @@ -7,7 +7,7 @@ import { } from "ente-gallery/components/viewer/FileViewer"; import type { Collection } from "ente-media/collection"; import { EnteFile } from "ente-media/file"; -import { fileFileName } from "ente-media/file-metadata"; +import { fileCreationTime, fileFileName } from "ente-media/file-metadata"; import { moveToTrash } from "ente-new/photos/services/collection"; import { PseudoCollectionID } from "ente-new/photos/services/collection-summary"; import { t } from "i18next"; @@ -227,8 +227,8 @@ const Container = styled("div")` /** * See: [Note: Timeline date string] */ -const fileTimelineDateString = (item: EnteFile) => { - const date = new Date(item.metadata.creationTime / 1000); +const fileTimelineDateString = (file: EnteFile) => { + const date = new Date(fileCreationTime(file) / 1000); return isSameDay(date, new Date()) ? t("today") : isSameDay(date, new Date(Date.now() - 24 * 60 * 60 * 1000)) diff --git a/web/apps/photos/src/services/upload-manager.ts b/web/apps/photos/src/services/upload-manager.ts index 2a0173f194..61f4e23a31 100644 --- a/web/apps/photos/src/services/upload-manager.ts +++ b/web/apps/photos/src/services/upload-manager.ts @@ -34,7 +34,10 @@ import { type EnteFile, type RemoteEnteFile, } from "ente-media/file"; -import type { ParsedMetadata } from "ente-media/file-metadata"; +import { + fileCreationTime, + type ParsedMetadata, +} from "ente-media/file-metadata"; import { FileType } from "ente-media/file-type"; import { potentialFileTypeFromExtension } from "ente-media/live-photo"; import { getLocalFiles } from "ente-new/photos/services/files"; @@ -413,7 +416,7 @@ class UploadManager { collection: Collection, sourceEnteFile: EnteFile, ) { - const timestamp = sourceEnteFile.metadata.creationTime; + const timestamp = fileCreationTime(sourceEnteFile); const dateTime = sourceEnteFile.pubMagicMetadata?.data.dateTime; const offset = sourceEnteFile.pubMagicMetadata?.data.offsetTime; diff --git a/web/apps/photos/tests/upload.test.ts b/web/apps/photos/tests/upload.test.ts index 543438249b..ff3ecff771 100644 --- a/web/apps/photos/tests/upload.test.ts +++ b/web/apps/photos/tests/upload.test.ts @@ -7,7 +7,7 @@ import { matchJSONMetadata, metadataJSONMapKeyForJSON, } from "ente-gallery/services/upload/metadata-json"; -import { fileFileName } from "ente-media/file-metadata"; +import { fileCreationTime, fileFileName } from "ente-media/file-metadata"; import { FileType } from "ente-media/file-type"; import { getLocalCollections } from "ente-new/photos/services/collections"; import { groupFilesByCollectionID } from "ente-new/photos/services/files"; @@ -303,11 +303,11 @@ async function exifDataParsingCheck(expectedState) { } if ( exifValues["creation_time"] && - exifValues["creation_time"] !== matchingFile.metadata.creationTime + exifValues["creation_time"] !== fileCreationTime(matchingFile) ) { throw Error(`exifDataParsingCheck failed ❌ , for ${fileName} - expected: ${exifValues["creation_time"]} got: ${matchingFile.metadata.creationTime}`); + expected: ${exifValues["creation_time"]} got: ${fileCreationTime(matchingFile)}`); } if ( exifValues["location"] && @@ -369,11 +369,11 @@ async function googleMetadataReadingCheck(expectedState) { } if ( metadata["creation_time"] && - metadata["creation_time"] !== matchingFile.metadata.creationTime + metadata["creation_time"] !== fileCreationTime(matchingFile) ) { throw Error(`googleMetadataJSON reading check failed ❌ , for ${fileName} - expected: ${metadata["creation_time"]} got: ${matchingFile.metadata.creationTime}`); + expected: ${metadata["creation_time"]} got: ${fileCreationTime(matchingFile)}`); } if ( metadata["location"] && diff --git a/web/packages/media/file-metadata.ts b/web/packages/media/file-metadata.ts index fe63a7a6e5..d476f6795d 100644 --- a/web/packages/media/file-metadata.ts +++ b/web/packages/media/file-metadata.ts @@ -459,6 +459,17 @@ export const isArchivedFile = (file: EnteFile) => export const fileFileName = (file: EnteFile) => file.pubMagicMetadata?.data.editedName ?? file.metadata.title; +/** + * Return the file's creation timestamp (epoch microseconds). + * + * This function handles files with edited dates. + * + * While sometimes the epoch timestamp is the correct value to use, it is also + * possible that {@link fileCreationPhotoDate} might be more appropriate. + */ +export const fileCreationTime = (file: EnteFile) => + file.pubMagicMetadata?.data.editedTime ?? file.metadata.creationTime; + /** * Return the file's creation date as a Date in the hypothetical "timezone of * the photo". diff --git a/web/packages/media/file.ts b/web/packages/media/file.ts index af8704d788..42a02333a7 100644 --- a/web/packages/media/file.ts +++ b/web/packages/media/file.ts @@ -470,8 +470,9 @@ const mergeMetadata1 = (file: EnteFile): EnteFile => { const mutableMetadata = file.pubMagicMetadata?.data; if (mutableMetadata) { const { editedTime, editedName, lat, long } = mutableMetadata; + // Not needed, fileCreationTime is used instead. if (editedTime) file.metadata.creationTime = editedTime; - // Not needed, use fileFileName. + // Not needed, fileFileName is used instead. if (editedName) file.metadata.title = editedName; // Use (lat, long) only if both are present and nonzero. if (lat && long) { @@ -480,11 +481,11 @@ const mergeMetadata1 = (file: EnteFile): EnteFile => { } } - // Moved to transformDecryptedMetadataJSON. + // Moved to transformDecryptedMetadataJSON. Not needed. if (!file.metadata.modificationTime) file.metadata.modificationTime = file.metadata.creationTime; - // Moved to transformDecryptedMetadataJSON. + // Moved to transformDecryptedMetadataJSON. Not needed. if (!file.metadata.fileType && file.id < 100000000) file.metadata.fileType = FileType.image; diff --git a/web/packages/new/photos/services/export.ts b/web/packages/new/photos/services/export.ts index fa8b1f0ab8..e5c39a9baa 100644 --- a/web/packages/new/photos/services/export.ts +++ b/web/packages/new/photos/services/export.ts @@ -16,7 +16,11 @@ import { downloadManager } from "ente-gallery/services/download"; import { writeStream } from "ente-gallery/utils/native-stream"; import type { Collection } from "ente-media/collection"; import { fileLogID, mergeMetadata, type EnteFile } from "ente-media/file"; -import { fileFileName, fileLocation } from "ente-media/file-metadata"; +import { + fileCreationTime, + fileFileName, + fileLocation, +} from "ente-media/file-metadata"; import { FileType } from "ente-media/file-type"; import { decodeLivePhoto } from "ente-media/live-photo"; import { @@ -1447,13 +1451,8 @@ const getGoogleLikeMetadataFile = ( dateTimeFormatter: Intl.DateTimeFormat, ) => { const metadata = file.metadata; - const publicMagicMetadata = file.pubMagicMetadata?.data; - const creationTime = Math.floor( - (publicMagicMetadata?.editedTime ?? metadata.creationTime) / 1e6, - ); - const modificationTime = Math.floor( - (metadata.modificationTime ?? metadata.creationTime) / 1e6, - ); + const creationTime = Math.floor(fileCreationTime(file) / 1e6); + const modificationTime = Math.floor(metadata.modificationTime / 1e6); const result: Record = { title: fileExportName, creationTime: { diff --git a/web/packages/new/photos/services/files.ts b/web/packages/new/photos/services/files.ts index e8f4f8159d..854b84f6fe 100644 --- a/web/packages/new/photos/services/files.ts +++ b/web/packages/new/photos/services/files.ts @@ -9,7 +9,7 @@ import { type EnteFile, type RemoteEnteFile, } from "ente-media/file"; -import { metadataHash } from "ente-media/file-metadata"; +import { fileCreationTime, metadataHash } from "ente-media/file-metadata"; import { type Trash } from "ente-new/photos/services/trash"; import HTTPService from "ente-shared/network/HTTPService"; import localForage from "ente-shared/storage/localForage"; @@ -170,13 +170,12 @@ export const sortFiles = (files: EnteFile[], sortAsc = false) => { // modification. const factor = sortAsc ? -1 : 1; return files.sort((a, b) => { - if (a.metadata.creationTime === b.metadata.creationTime) { - return ( - factor * - (b.metadata.modificationTime - a.metadata.modificationTime) - ); - } - return factor * (b.metadata.creationTime - a.metadata.creationTime); + const at = fileCreationTime(a); + const bt = fileCreationTime(b); + return at == bt + ? factor * + (b.metadata.modificationTime - a.metadata.modificationTime) + : factor * (bt - at); }); }; @@ -269,12 +268,11 @@ export const getLocalTrashFileIDs = () => const sortTrashFiles = (files: TrashedEnteFile[]) => { return files.sort((a, b) => { if (a.deleteBy === b.deleteBy) { - if (a.metadata.creationTime === b.metadata.creationTime) { - return ( - b.metadata.modificationTime - a.metadata.modificationTime - ); - } - return b.metadata.creationTime - a.metadata.creationTime; + const at = fileCreationTime(a); + const bt = fileCreationTime(b); + return at == bt + ? b.metadata.modificationTime - a.metadata.modificationTime + : bt - at; } return (a.deleteBy ?? 0) - (b.deleteBy ?? 0); }); diff --git a/web/packages/new/photos/services/ml/cluster.ts b/web/packages/new/photos/services/ml/cluster.ts index e4cf11c0e5..716e9e3946 100644 --- a/web/packages/new/photos/services/ml/cluster.ts +++ b/web/packages/new/photos/services/ml/cluster.ts @@ -16,6 +16,7 @@ import { type FaceIndex, } from "./face"; import { dotProduct } from "./math"; +import { fileCreationTime } from "ente-media/file-metadata"; /** * A face cluster is an set of faces, and a nanoid to uniquely identify it. @@ -229,7 +230,7 @@ const sortFacesNewestOnesFirst = ( assertionFailed(`Did not find a local file for faceID ${faceID}`); return 0; } - return file.metadata.creationTime; + return fileCreationTime(file); }; return faces.sort((a, b) => sortTimeForFace(b) - sortTimeForFace(a)); diff --git a/web/packages/new/photos/services/ml/people.ts b/web/packages/new/photos/services/ml/people.ts index bd5cb1daad..35d53a5a46 100644 --- a/web/packages/new/photos/services/ml/people.ts +++ b/web/packages/new/photos/services/ml/people.ts @@ -1,6 +1,7 @@ import { assertionFailed } from "ente-base/assert"; import log from "ente-base/log"; import type { EnteFile } from "ente-media/file"; +import { fileCreationTime } from "ente-media/file-metadata"; import { randomSample } from "ente-utils/array"; import { savedNormalFiles } from "../photos-fdb"; import { @@ -245,8 +246,8 @@ export const reconstructPeopleState = async (): Promise => { .map((faceID) => personFaceByID.get(faceID)) .filter((pf) => !!pf) .sort((a, b) => { - const at = a.file.metadata.creationTime; - const bt = b.file.metadata.creationTime; + const at = fileCreationTime(a.file); + const bt = fileCreationTime(b.file); return bt == at ? b.score - a.score : bt - at; });