Handle files with an heic extension that are not actually heic

This commit is contained in:
Manav Rathi 2024-05-10 11:52:21 +05:30
parent d110645703
commit 3dae87abaa
No known key found for this signature in database
4 changed files with 27 additions and 13 deletions

View file

@ -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;

View file

@ -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 });
};

View file

@ -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";

View file

@ -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";
};