Переглянути джерело

Handle files with an heic extension that are not actually heic

Manav Rathi 1 рік тому
батько
коміт
3dae87abaa

+ 3 - 0
web/apps/cast/src/services/detect-type.ts

@@ -9,6 +9,9 @@ import FileType from "file-type";
  *
  * It first peeks into the file's initial contents to detect the MIME type. If
  * that doesn't give any results, it tries to deduce it from the file's name.
+ *
+ * For the list of returned extensions, see (for our installed version):
+ * https://github.com/sindresorhus/file-type/blob/main/core.d.ts
  */
 export const detectMediaMIMEType = async (file: File): Promise<string> => {
     const chunkSizeForTypeDetection = 4100;

+ 14 - 12
web/apps/cast/src/services/render.ts

@@ -1,5 +1,5 @@
 import { FILE_TYPE } from "@/media/file-type";
-import { isNonWebImageFileExtension } from "@/media/formats";
+import { isHEICExtension, isNonWebImageFileExtension } from "@/media/formats";
 import { scaledImageDimensions } from "@/media/image";
 import { decodeLivePhoto } from "@/media/live-photo";
 import { createHEICConvertComlinkWorker } from "@/media/worker/heic-convert";
@@ -285,10 +285,14 @@ const isFileEligible = (file: EnteFile) => {
     if (!isImageOrLivePhoto(file)) return false;
     if (file.info.fileSize > 100 * 1024 * 1024) return false;
 
+    // This check is fast but potentially incorrect because in practice we do
+    // encounter files that are incorrectly named and have a misleading
+    // extension. To detect the actual type, we need to sniff the MIME type, but
+    // that requires downloading and decrypting the file first.
     const [, extension] = nameAndExtension(file.metadata.title);
     if (isNonWebImageFileExtension(extension)) {
         // Of the known non-web types, we support HEIC.
-        return isHEIC(extension);
+        return isHEICExtension(extension);
     }
 
     return true;
@@ -299,11 +303,6 @@ const isImageOrLivePhoto = (file: EnteFile) => {
     return fileType == FILE_TYPE.IMAGE || fileType == FILE_TYPE.LIVE_PHOTO;
 };
 
-const isHEIC = (extension: string) => {
-    const ext = extension.toLowerCase();
-    return ext == "heic" || ext == "heif";
-};
-
 export const heicToJPEG = async (heicBlob: Blob) => {
     let worker = heicWorker;
     if (!worker) heicWorker = worker = createHEICConvertComlinkWorker();
@@ -336,14 +335,17 @@ const renderableImageBlob = async (castToken: string, file: EnteFile) => {
         blob = new Blob([imageData]);
     }
 
-    const [, ext] = nameAndExtension(fileName);
-    if (isHEIC(ext)) {
-        blob = await heicToJPEG(blob);
-    }
-
+    // We cannot rely on the file's extension to detect the file type, some
+    // files are incorrectly named. So use a MIME type sniffer first, but if
+    // that fails than fallback to the extension.
     const mimeType = await detectMediaMIMEType(new File([blob], fileName));
     if (!mimeType)
         throw new Error(`Could not detect MIME type for file ${fileName}`);
+
+    if (mimeType == "image/heif" || mimeType == "image/heic") {
+        blob = await heicToJPEG(blob);
+    }
+
     return new Blob([blob], { type: mimeType });
 };
 

+ 2 - 1
web/apps/photos/src/utils/file/index.ts

@@ -5,10 +5,11 @@ import { lowercaseExtension } from "@/next/file";
 import log from "@/next/log";
 import { CustomErrorMessage, type Electron } from "@/next/types/ipc";
 import { workerBridge } from "@/next/worker/worker-bridge";
+import { withTimeout } from "@/utils/promise";
 import ComlinkCryptoWorker from "@ente/shared/crypto";
 import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
 import { User } from "@ente/shared/user/types";
-import { downloadUsingAnchor, withTimeout } from "@ente/shared/utils";
+import { downloadUsingAnchor } from "@ente/shared/utils";
 import { t } from "i18next";
 import isElectron from "is-electron";
 import { moveToHiddenCollection } from "services/collectionService";

+ 8 - 0
web/packages/media/formats.ts

@@ -24,3 +24,11 @@ const nonWebImageFileExtensions = [
  */
 export const isNonWebImageFileExtension = (extension: string) =>
     nonWebImageFileExtensions.includes(extension.toLowerCase());
+
+/**
+ * Return `true` if {@link extension} in for an HEIC-like file.
+ */
+export const isHEICExtension = (extension: string) => {
+    const ext = extension.toLowerCase();
+    return ext == "heic" || ext == "heif";
+};