diff --git a/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx b/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx index e19a2f557..e8f2fee7c 100644 --- a/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx +++ b/web/apps/photos/src/components/PhotoViewer/ImageEditorOverlay/index.tsx @@ -43,7 +43,7 @@ import mime from "mime-types"; import { AppContext } from "pages/_app"; import { getLocalCollections } from "services/collectionService"; import downloadManager from "services/download"; -import { deduceFileTypeInfo } from "services/typeDetectionService"; +import { detectFileTypeInfo } from "services/typeDetectionService"; import uploadManager from "services/upload/uploadManager"; import { EnteFile } from "types/file"; import { FileWithCollection } from "types/upload"; @@ -486,7 +486,7 @@ const ImageEditorOverlay = (props: IProps) => { if (!canvasRef.current) return; const editedFile = await getEditedFile(); - const fileType = await deduceFileTypeInfo(editedFile); + const fileType = await detectFileTypeInfo(editedFile); const tempImgURL = URL.createObjectURL( new Blob([editedFile], { type: fileType.mimeType }), ); diff --git a/web/apps/photos/src/components/PhotoViewer/index.tsx b/web/apps/photos/src/components/PhotoViewer/index.tsx index 1a2f3c189..465c83367 100644 --- a/web/apps/photos/src/components/PhotoViewer/index.tsx +++ b/web/apps/photos/src/components/PhotoViewer/index.tsx @@ -46,7 +46,7 @@ import { GalleryContext } from "pages/gallery"; import downloadManager, { LoadedLivePhotoSourceURL } from "services/download"; import { getParsedExifData } from "services/exif"; import { trashFiles } from "services/fileService"; -import { deduceFileTypeInfo } from "services/typeDetectionService"; +import { detectFileTypeInfo } from "services/typeDetectionService"; import { SetFilesDownloadProgressAttributesCreator } from "types/gallery"; import { isClipboardItemPresent } from "utils/common"; import { pauseVideo, playVideo } from "utils/photoFrame"; @@ -594,7 +594,7 @@ function PhotoViewer(props: Iprops) { .image; fileObject = await getFileFromURL(url, file.metadata.title); } - const fileTypeInfo = await deduceFileTypeInfo(fileObject); + const fileTypeInfo = await detectFileTypeInfo(fileObject); const exifData = await getParsedExifData( fileObject, fileTypeInfo, diff --git a/web/apps/photos/src/services/fix-exif.ts b/web/apps/photos/src/services/fix-exif.ts index 2856e3061..8c38aacde 100644 --- a/web/apps/photos/src/services/fix-exif.ts +++ b/web/apps/photos/src/services/fix-exif.ts @@ -2,7 +2,7 @@ import { FILE_TYPE } from "@/media/file-type"; import log from "@/next/log"; import { validateAndGetCreationUnixTimeInMicroSeconds } from "@ente/shared/time"; import type { FixOption } from "components/FixCreationTime"; -import { deduceFileTypeInfo } from "services/typeDetectionService"; +import { detectFileTypeInfo } from "services/typeDetectionService"; import { EnteFile } from "types/file"; import { changeFileCreationTime, @@ -53,7 +53,7 @@ export async function updateCreationTimeWithExif( [fileBlob], file.metadata.title, ); - const fileTypeInfo = await deduceFileTypeInfo(fileObject); + const fileTypeInfo = await detectFileTypeInfo(fileObject); const exifData = await getParsedExifData( fileObject, fileTypeInfo, diff --git a/web/apps/photos/src/services/typeDetectionService.ts b/web/apps/photos/src/services/typeDetectionService.ts index c0ea1ef9d..f570c1c6a 100644 --- a/web/apps/photos/src/services/typeDetectionService.ts +++ b/web/apps/photos/src/services/typeDetectionService.ts @@ -4,23 +4,18 @@ import { KnownNonMediaFileExtensions, type FileTypeInfo, } from "@/media/file-type"; -import log from "@/next/log"; +import { nameAndExtension } from "@/next/file"; import { ElectronFile } from "@/next/types/file"; import { CustomError } from "@ente/shared/error"; import FileType, { type FileTypeResult } from "file-type"; -import { getFileExtension } from "utils/file"; import { getUint8ArrayView } from "./readerService"; -const TYPE_VIDEO = "video"; -const TYPE_IMAGE = "image"; -const CHUNK_SIZE_FOR_TYPE_DETECTION = 4100; - /** - * Read the file's initial contents or use the file's name to deduce its type. + * Read the file's initial contents or use the file's name to detect its type. * - * This function first reads an initial chunk of the file and tries to deduce + * This function first reads an initial chunk of the file and tries to detect * the file's {@link FileTypeInfo} from it. If that doesn't work, it then falls - * back to using the file's name to deduce it. + * back to using the file's name to detect it. * * If neither of these two approaches work, it throws an exception. * @@ -32,9 +27,9 @@ const CHUNK_SIZE_FOR_TYPE_DETECTION = 4100; * user's local filesystem. It is only valid to provide a path if we're running * in the context of our desktop app. * - * @returns The deduced {@link FileTypeInfo}. + * @returns The detected {@link FileTypeInfo}. */ -export const deduceFileTypeInfo = async ( +export const detectFileTypeInfo = async ( fileOrPath: File | ElectronFile, ): Promise => { try { @@ -53,14 +48,14 @@ export const deduceFileTypeInfo = async ( throw Error(CustomError.INVALID_MIME_TYPE(typeResult.mime)); } switch (mimTypeParts[0]) { - case TYPE_IMAGE: + case "image": fileType = FILE_TYPE.IMAGE; break; - case TYPE_VIDEO: + case "video": fileType = FILE_TYPE.VIDEO; break; default: - throw Error(CustomError.NON_MEDIA_FILE); + throw new Error(CustomError.UNSUPPORTED_FILE_FORMAT); } return { fileType, @@ -68,27 +63,20 @@ export const deduceFileTypeInfo = async ( mimeType: typeResult.mime, }; } catch (e) { - const fileFormat = getFileExtension(fileOrPath.name); - const whiteListedFormat = KnownFileTypeInfos.find( - (a) => a.exactType === fileFormat, - ); - if (whiteListedFormat) { - return whiteListedFormat; - } - if (KnownNonMediaFileExtensions.includes(fileFormat)) { + const [, extension] = nameAndExtension(fileOrPath.name); + const known = KnownFileTypeInfos.find((f) => f.exactType == extension); + if (known) return known; + + if (KnownNonMediaFileExtensions.includes(extension)) throw Error(CustomError.UNSUPPORTED_FILE_FORMAT); - } - if (e.message === CustomError.NON_MEDIA_FILE) { - log.error(`unsupported file format ${fileFormat}`, e); - throw Error(CustomError.UNSUPPORTED_FILE_FORMAT); - } - log.error(`type detection failed for format ${fileFormat}`, e); - throw new Error(`type detection failed ${fileFormat}`); + + throw e; } }; async function extractFileType(file: File) { - const fileBlobChunk = file.slice(0, CHUNK_SIZE_FOR_TYPE_DETECTION); + const chunkSizeForTypeDetection = 4100; + const fileBlobChunk = file.slice(0, chunkSizeForTypeDetection); const fileDataChunk = await getUint8ArrayView(fileBlobChunk); return getFileTypeFromBuffer(fileDataChunk); } @@ -104,13 +92,7 @@ async function extractElectronFileType(file: ElectronFile) { async function getFileTypeFromBuffer(buffer: Uint8Array) { const result = await FileType.fromBuffer(buffer); if (!result?.mime) { - let logableInfo = ""; - try { - logableInfo = `result: ${JSON.stringify(result)}`; - } catch (e) { - logableInfo = "failed to stringify result"; - } - throw Error(`mimetype missing from file type result - ${logableInfo}`); + throw Error(`Could not deduce MIME type from buffer`); } return result; } diff --git a/web/apps/photos/src/services/upload/uploadService.ts b/web/apps/photos/src/services/upload/uploadService.ts index 91a21e08b..8bd91ad34 100644 --- a/web/apps/photos/src/services/upload/uploadService.ts +++ b/web/apps/photos/src/services/upload/uploadService.ts @@ -48,7 +48,7 @@ import { readStream } from "utils/native-stream"; import { hasFileHash } from "utils/upload"; import * as convert from "xml-js"; import { getFileStream } from "../readerService"; -import { deduceFileTypeInfo } from "../typeDetectionService"; +import { detectFileTypeInfo } from "../typeDetectionService"; import { extractAssetMetadata } from "./metadata"; import publicUploadHttpClient from "./publicUploadHttpClient"; import type { ParsedMetadataJSON } from "./takeout"; @@ -331,14 +331,14 @@ const getAssetFileType = ({ }: UploadAsset) => { return isLivePhoto ? getLivePhotoFileType(livePhotoAssets) - : deduceFileTypeInfo(file); + : detectFileTypeInfo(file); }; const getLivePhotoFileType = async ( livePhotoAssets: LivePhotoAssets, ): Promise => { - const imageFileTypeInfo = await deduceFileTypeInfo(livePhotoAssets.image); - const videoFileTypeInfo = await deduceFileTypeInfo(livePhotoAssets.video); + const imageFileTypeInfo = await detectFileTypeInfo(livePhotoAssets.image); + const videoFileTypeInfo = await detectFileTypeInfo(livePhotoAssets.video); return { fileType: FILE_TYPE.LIVE_PHOTO, exactType: `${imageFileTypeInfo.exactType}+${videoFileTypeInfo.exactType}`, diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index d059b4163..1bc15c6cc 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -19,7 +19,7 @@ import { updateFilePublicMagicMetadata, } from "services/fileService"; import { heicToJPEG } from "services/heic-convert"; -import { deduceFileTypeInfo } from "services/typeDetectionService"; +import { detectFileTypeInfo } from "services/typeDetectionService"; import { EncryptedEnteFile, EnteFile, @@ -130,19 +130,19 @@ export async function downloadFile(file: EnteFile) { const { imageFileName, imageData, videoFileName, videoData } = await decodeLivePhoto(file.metadata.title, fileBlob); const image = new File([imageData], imageFileName); - const imageType = await deduceFileTypeInfo(image); + const imageType = await detectFileTypeInfo(image); const tempImageURL = URL.createObjectURL( new Blob([imageData], { type: imageType.mimeType }), ); const video = new File([videoData], videoFileName); - const videoType = await deduceFileTypeInfo(video); + const videoType = await detectFileTypeInfo(video); const tempVideoURL = URL.createObjectURL( new Blob([videoData], { type: videoType.mimeType }), ); downloadUsingAnchor(tempImageURL, imageFileName); downloadUsingAnchor(tempVideoURL, videoFileName); } else { - const fileType = await deduceFileTypeInfo( + const fileType = await detectFileTypeInfo( new File([fileBlob], file.metadata.title), ); fileBlob = await new Response( @@ -305,7 +305,7 @@ export const getRenderableImage = async (fileName: string, imageBlob: Blob) => { let fileTypeInfo: FileTypeInfo; try { const tempFile = new File([imageBlob], fileName); - fileTypeInfo = await deduceFileTypeInfo(tempFile); + fileTypeInfo = await detectFileTypeInfo(tempFile); log.debug( () => `Obtaining renderable image for ${JSON.stringify(fileTypeInfo)}`, @@ -724,7 +724,7 @@ export const getArchivedFiles = (files: EnteFile[]) => { }; export const createTypedObjectURL = async (blob: Blob, fileName: string) => { - const type = await deduceFileTypeInfo(new File([blob], fileName)); + const type = await detectFileTypeInfo(new File([blob], fileName)); return URL.createObjectURL(new Blob([blob], { type: type.mimeType })); }; diff --git a/web/packages/shared/error/index.ts b/web/packages/shared/error/index.ts index c9cc6c193..abda1561a 100644 --- a/web/packages/shared/error/index.ts +++ b/web/packages/shared/error/index.ts @@ -67,7 +67,6 @@ export const CustomError = { AUTH_KEY_NOT_FOUND: "auth key not found", EXIF_DATA_NOT_FOUND: "exif data not found", SELECT_FOLDER_ABORTED: "select folder aborted", - NON_MEDIA_FILE: "non media file", PROCESSING_FAILED: "processing failed", EXPORT_RECORD_JSON_PARSING_FAILED: "export record json parsing failed", TWO_FACTOR_ENABLED: "two factor enabled",