Manav Rathi 1 rok temu
rodzic
commit
3b0433c4ab

+ 5 - 169
web/apps/photos/src/services/upload/metadataService.ts

@@ -1,4 +1,4 @@
-import { getFileNameSize, nameAndExtension } from "@/next/file";
+import { getFileNameSize } from "@/next/file";
 import log from "@/next/log";
 import { ElectronFile } from "@/next/types/file";
 import { DedicatedCryptoWorker } from "@ente/shared/crypto/internal/crypto.worker";
@@ -21,12 +21,9 @@ import {
     Metadata,
     ParsedExtractedMetadata,
     type DataStream,
-    type FileWithCollection,
-    type FileWithCollection2,
     type LivePhotoAssets2,
     type UploadAsset2,
 } from "types/upload";
-import { getFileTypeFromExtensionForLivePhotoClustering } from "utils/file/livePhoto";
 import { getEXIFLocation, getEXIFTime, getParsedExifData } from "./exifService";
 import {
     MAX_FILE_NAME_LENGTH_GOOGLE_EXPORT,
@@ -295,171 +292,6 @@ async function extractLivePhotoMetadata(
     };
 }
 
-export function getLivePhotoSize(livePhotoAssets: LivePhotoAssets) {
-    return livePhotoAssets.image.size + livePhotoAssets.video.size;
-}
-
-/**
- * Go through the given files, combining any sibling image + video assets into a
- * single live photo when appropriate.
- */
-export const clusterLivePhotos = (mediaFiles: FileWithCollection2[]) => {
-    const result: FileWithCollection2[] = [];
-    mediaFiles
-        .sort((f, g) =>
-            nameAndExtension(getFileName(f.file))[0].localeCompare(
-                nameAndExtension(getFileName(g.file))[0],
-            ),
-        )
-        .sort((f, g) => f.collectionID - g.collectionID);
-    let index = 0;
-    while (index < mediaFiles.length - 1) {
-        const f = mediaFiles[index];
-        const g = mediaFiles[index + 1];
-        const fFileType = getFileTypeFromExtensionForLivePhotoClustering(
-            getFileName(f.file),
-        );
-        const gFileType = getFileTypeFromExtensionForLivePhotoClustering(
-            getFileName(g.file),
-        );
-        const fa: PotentialLivePhotoAsset = {
-            collectionID: f.collectionID,
-            fileType: fFileType,
-            fileName: getFileName(f.file),
-            /* TODO(MR): ElectronFile changes */
-            size: (f as FileWithCollection).file.size,
-        };
-        const ga: PotentialLivePhotoAsset = {
-            collectionID: g.collectionID,
-            fileType: gFileType,
-            fileName: getFileName(g.file),
-            /* TODO(MR): ElectronFile changes */
-            size: (g as FileWithCollection).file.size,
-        };
-        if (areLivePhotoAssets(fa, ga)) {
-            let imageFile: File | ElectronFile | string;
-            let videoFile: File | ElectronFile | string;
-            if (
-                fFileType === FILE_TYPE.IMAGE &&
-                gFileType === FILE_TYPE.VIDEO
-            ) {
-                imageFile = f.file;
-                videoFile = g.file;
-            } else {
-                videoFile = f.file;
-                imageFile = g.file;
-            }
-            const livePhotoLocalID = f.localID;
-            result.push({
-                localID: livePhotoLocalID,
-                collectionID: f.collectionID,
-                isLivePhoto: true,
-                livePhotoAssets: {
-                    image: imageFile,
-                    video: videoFile,
-                },
-            });
-            index += 2;
-        } else {
-            result.push({
-                ...f,
-                isLivePhoto: false,
-            });
-            index += 1;
-        }
-    }
-    if (index === mediaFiles.length - 1) {
-        result.push({
-            ...mediaFiles[index],
-            isLivePhoto: false,
-        });
-    }
-    return result;
-};
-
-interface PotentialLivePhotoAsset {
-    collectionID: number;
-    fileType: FILE_TYPE;
-    fileName: string;
-    size: number;
-}
-
-const areLivePhotoAssets = (
-    f: PotentialLivePhotoAsset,
-    g: PotentialLivePhotoAsset,
-) => {
-    if (f.collectionID != g.collectionID) return false;
-
-    const [fName, fExt] = nameAndExtension(f.fileName);
-    const [gName, gExt] = nameAndExtension(g.fileName);
-
-    let fPrunedName: string;
-    let gPrunedName: string;
-    if (f.fileType == FILE_TYPE.IMAGE && g.fileType == FILE_TYPE.VIDEO) {
-        fPrunedName = removePotentialLivePhotoSuffix(
-            fName,
-            // A Google Live Photo image file can have video extension appended
-            // as suffix, so we pass that to removePotentialLivePhotoSuffix to
-            // remove it.
-            //
-            // Example: IMG_20210630_0001.mp4.jpg (Google Live Photo image file)
-            gExt ? `.${gExt}` : undefined,
-        );
-        gPrunedName = removePotentialLivePhotoSuffix(gName);
-    } else if (f.fileType == FILE_TYPE.VIDEO && g.fileType == FILE_TYPE.IMAGE) {
-        fPrunedName = removePotentialLivePhotoSuffix(fName);
-        gPrunedName = removePotentialLivePhotoSuffix(
-            gName,
-            fExt ? `.${fExt}` : undefined,
-        );
-    } else {
-        return false;
-    }
-
-    if (fPrunedName != gPrunedName) return false;
-
-    // Also check that the size of an individual Live Photo asset is less than
-    // an (arbitrary) limit. This should be true in practice as the videos for a
-    // live photo are a few seconds long. Further on, the zipping library that
-    // we use doesn't support stream as a input.
-
-    const maxAssetSize = 20 * 1024 * 1024; /* 20MB */
-    if (f.size > maxAssetSize || g.size > maxAssetSize) {
-        log.info(
-            `Not classifying assets with too large sizes ${[f.size, g.size]} as a live photo`,
-        );
-        return false;
-    }
-
-    return true;
-};
-
-const removePotentialLivePhotoSuffix = (name: string, suffix?: string) => {
-    const suffix_3 = "_3";
-
-    // The icloud-photos-downloader library appends _HVEC to the end of the
-    // filename in case of live photos.
-    //
-    // https://github.com/icloud-photos-downloader/icloud_photos_downloader
-    const suffix_hvec = "_HVEC";
-
-    let foundSuffix: string | undefined;
-    if (name.endsWith(suffix_3)) {
-        foundSuffix = suffix_3;
-    } else if (
-        name.endsWith(suffix_hvec) ||
-        name.endsWith(suffix_hvec.toLowerCase())
-    ) {
-        foundSuffix = suffix_hvec;
-    } else if (suffix) {
-        if (name.endsWith(suffix) || name.endsWith(suffix.toLowerCase())) {
-            foundSuffix = suffix;
-        }
-    }
-
-    return foundSuffix ? name.slice(0, foundSuffix.length * -1) : name;
-};
-
 async function getFileHash(
     worker: Remote<DedicatedCryptoWorker>,
     file: File | ElectronFile,
@@ -499,3 +331,7 @@ async function getFileHash(
         log.info(`file hashing failed ${getFileNameSize(file)} ,${e.message} `);
     }
 }
+
+export function getLivePhotoSize(livePhotoAssets: LivePhotoAssets) {
+    return livePhotoAssets.image.size + livePhotoAssets.video.size;
+}

+ 164 - 1
web/apps/photos/src/services/upload/uploadManager.ts

@@ -1,4 +1,5 @@
 import { ensureElectron } from "@/next/electron";
+import { nameAndExtension } from "@/next/file";
 import log from "@/next/log";
 import { ElectronFile } from "@/next/types/file";
 import { ComlinkWorker } from "@/next/worker/comlink-worker";
@@ -9,6 +10,7 @@ import { Events, eventBus } from "@ente/shared/events";
 import { wait } from "@ente/shared/utils";
 import { Canceler } from "axios";
 import { Remote } from "comlink";
+import { FILE_TYPE } from "constants/file";
 import {
     RANDOM_PERCENTAGE_PROGRESS_FOR_PUT,
     UPLOAD_RESULT,
@@ -43,7 +45,6 @@ import {
     segregateMetadataAndMediaFiles2,
 } from "utils/upload";
 import { getLocalFiles } from "../fileService";
-import { clusterLivePhotoFiles, clusterLivePhotos } from "./metadataService";
 import {
     getMetadataJSONMapKeyForJSON,
     tryParseTakeoutMetadataJSON,
@@ -56,6 +57,7 @@ import UploadService, {
     getFileName,
     uploader,
 } from "./uploadService";
+import { getFileTypeFromExtensionForLivePhotoClustering } from "utils/file/livePhoto";
 
 const MAX_CONCURRENT_UPLOADS = 4;
 
@@ -800,3 +802,164 @@ const cancelRemainingUploads = async () => {
     await electron.setPendingUploadFiles("zips", []);
     await electron.setPendingUploadFiles("files", []);
 };
+
+/**
+ * Go through the given files, combining any sibling image + video assets into a
+ * single live photo when appropriate.
+ */
+const clusterLivePhotos = (mediaFiles: FileWithCollection2[]) => {
+    const result: FileWithCollection2[] = [];
+    mediaFiles
+        .sort((f, g) =>
+            nameAndExtension(getFileName(f.file))[0].localeCompare(
+                nameAndExtension(getFileName(g.file))[0],
+            ),
+        )
+        .sort((f, g) => f.collectionID - g.collectionID);
+    let index = 0;
+    while (index < mediaFiles.length - 1) {
+        const f = mediaFiles[index];
+        const g = mediaFiles[index + 1];
+        const fFileType = getFileTypeFromExtensionForLivePhotoClustering(
+            getFileName(f.file),
+        );
+        const gFileType = getFileTypeFromExtensionForLivePhotoClustering(
+            getFileName(g.file),
+        );
+        const fa: PotentialLivePhotoAsset = {
+            collectionID: f.collectionID,
+            fileType: fFileType,
+            fileName: getFileName(f.file),
+            /* TODO(MR): ElectronFile changes */
+            size: (f as FileWithCollection).file.size,
+        };
+        const ga: PotentialLivePhotoAsset = {
+            collectionID: g.collectionID,
+            fileType: gFileType,
+            fileName: getFileName(g.file),
+            /* TODO(MR): ElectronFile changes */
+            size: (g as FileWithCollection).file.size,
+        };
+        if (areLivePhotoAssets(fa, ga)) {
+            let imageFile: File | ElectronFile | string;
+            let videoFile: File | ElectronFile | string;
+            if (
+                fFileType === FILE_TYPE.IMAGE &&
+                gFileType === FILE_TYPE.VIDEO
+            ) {
+                imageFile = f.file;
+                videoFile = g.file;
+            } else {
+                videoFile = f.file;
+                imageFile = g.file;
+            }
+            const livePhotoLocalID = f.localID;
+            result.push({
+                localID: livePhotoLocalID,
+                collectionID: f.collectionID,
+                isLivePhoto: true,
+                livePhotoAssets: {
+                    image: imageFile,
+                    video: videoFile,
+                },
+            });
+            index += 2;
+        } else {
+            result.push({
+                ...f,
+                isLivePhoto: false,
+            });
+            index += 1;
+        }
+    }
+    if (index === mediaFiles.length - 1) {
+        result.push({
+            ...mediaFiles[index],
+            isLivePhoto: false,
+        });
+    }
+    return result;
+};
+
+interface PotentialLivePhotoAsset {
+    collectionID: number;
+    fileType: FILE_TYPE;
+    fileName: string;
+    size: number;
+}
+
+const areLivePhotoAssets = (
+    f: PotentialLivePhotoAsset,
+    g: PotentialLivePhotoAsset,
+) => {
+    if (f.collectionID != g.collectionID) return false;
+
+    const [fName, fExt] = nameAndExtension(f.fileName);
+    const [gName, gExt] = nameAndExtension(g.fileName);
+
+    let fPrunedName: string;
+    let gPrunedName: string;
+    if (f.fileType == FILE_TYPE.IMAGE && g.fileType == FILE_TYPE.VIDEO) {
+        fPrunedName = removePotentialLivePhotoSuffix(
+            fName,
+            // A Google Live Photo image file can have video extension appended
+            // as suffix, so we pass that to removePotentialLivePhotoSuffix to
+            // remove it.
+            //
+            // Example: IMG_20210630_0001.mp4.jpg (Google Live Photo image file)
+            gExt ? `.${gExt}` : undefined,
+        );
+        gPrunedName = removePotentialLivePhotoSuffix(gName);
+    } else if (f.fileType == FILE_TYPE.VIDEO && g.fileType == FILE_TYPE.IMAGE) {
+        fPrunedName = removePotentialLivePhotoSuffix(fName);
+        gPrunedName = removePotentialLivePhotoSuffix(
+            gName,
+            fExt ? `.${fExt}` : undefined,
+        );
+    } else {
+        return false;
+    }
+
+    if (fPrunedName != gPrunedName) return false;
+
+    // Also check that the size of an individual Live Photo asset is less than
+    // an (arbitrary) limit. This should be true in practice as the videos for a
+    // live photo are a few seconds long. Further on, the zipping library that
+    // we use doesn't support stream as a input.
+
+    const maxAssetSize = 20 * 1024 * 1024; /* 20MB */
+    if (f.size > maxAssetSize || g.size > maxAssetSize) {
+        log.info(
+            `Not classifying assets with too large sizes ${[f.size, g.size]} as a live photo`,
+        );
+        return false;
+    }
+
+    return true;
+};
+
+const removePotentialLivePhotoSuffix = (name: string, suffix?: string) => {
+    const suffix_3 = "_3";
+
+    // The icloud-photos-downloader library appends _HVEC to the end of the
+    // filename in case of live photos.
+    //
+    // https://github.com/icloud-photos-downloader/icloud_photos_downloader
+    const suffix_hvec = "_HVEC";
+
+    let foundSuffix: string | undefined;
+    if (name.endsWith(suffix_3)) {
+        foundSuffix = suffix_3;
+    } else if (
+        name.endsWith(suffix_hvec) ||
+        name.endsWith(suffix_hvec.toLowerCase())
+    ) {
+        foundSuffix = suffix_hvec;
+    } else if (suffix) {
+        if (name.endsWith(suffix) || name.endsWith(suffix.toLowerCase())) {
+            foundSuffix = suffix;
+        }
+    }
+
+    return foundSuffix ? name.slice(0, foundSuffix.length * -1) : name;
+};