diff --git a/web/apps/cast/src/constants/upload.ts b/web/apps/cast/src/constants/upload.ts index 801d8a6ab4f70439026688a14904f755dec705e8..2ae1c43833f80d445aacbbc23abd9cfcdef2c8a8 100644 --- a/web/apps/cast/src/constants/upload.ts +++ b/web/apps/cast/src/constants/upload.ts @@ -1,6 +1,3 @@ -import { FILE_TYPE } from "@/media/file"; -import { FileTypeInfo } from "types/upload"; - export const RAW_FORMATS = [ "heic", "rw2", @@ -14,42 +11,3 @@ export const RAW_FORMATS = [ "dng", "tif", ]; - -// list of format that were missed by type-detection for some files. -export const WHITELISTED_FILE_FORMATS: FileTypeInfo[] = [ - { fileType: FILE_TYPE.IMAGE, exactType: "jpeg", mimeType: "image/jpeg" }, - { fileType: FILE_TYPE.IMAGE, exactType: "jpg", mimeType: "image/jpeg" }, - { fileType: FILE_TYPE.VIDEO, exactType: "webm", mimeType: "video/webm" }, - { fileType: FILE_TYPE.VIDEO, exactType: "mod", mimeType: "video/mpeg" }, - { fileType: FILE_TYPE.VIDEO, exactType: "mp4", mimeType: "video/mp4" }, - { fileType: FILE_TYPE.IMAGE, exactType: "gif", mimeType: "image/gif" }, - { fileType: FILE_TYPE.VIDEO, exactType: "dv", mimeType: "video/x-dv" }, - { - fileType: FILE_TYPE.VIDEO, - exactType: "wmv", - mimeType: "video/x-ms-asf", - }, - { - fileType: FILE_TYPE.VIDEO, - exactType: "hevc", - mimeType: "video/hevc", - }, - { - fileType: FILE_TYPE.IMAGE, - exactType: "raf", - mimeType: "image/x-fuji-raf", - }, - { - fileType: FILE_TYPE.IMAGE, - exactType: "orf", - mimeType: "image/x-olympus-orf", - }, - - { - fileType: FILE_TYPE.IMAGE, - exactType: "crw", - mimeType: "image/x-canon-crw", - }, -]; - -export const KNOWN_NON_MEDIA_FORMATS = ["xmp", "html", "txt"]; diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index d13a263464454c571ee16bc820f8c766af483e5d..99b2209decc482eed5428580fd2212c309addfaa 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import log from "@/next/log"; import PairedSuccessfullyOverlay from "components/PairedSuccessfullyOverlay"; import { PhotoAuditorium } from "components/PhotoAuditorium"; diff --git a/web/apps/cast/src/services/castDownloadManager.ts b/web/apps/cast/src/services/castDownloadManager.ts index 40a5f25af172da691a491c59645a7f6171d8f5ff..2314ed54ea4fa620b27a650ba8ffcfa09f8faa79 100644 --- a/web/apps/cast/src/services/castDownloadManager.ts +++ b/web/apps/cast/src/services/castDownloadManager.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { CustomError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; diff --git a/web/apps/cast/src/services/detect-type.ts b/web/apps/cast/src/services/detect-type.ts new file mode 100644 index 0000000000000000000000000000000000000000..ebe0bc2df9f0b574c8e54d8c76d3491eadc831f2 --- /dev/null +++ b/web/apps/cast/src/services/detect-type.ts @@ -0,0 +1,39 @@ +import { KnownFileTypeInfos } from "@/media/file-type"; +import { nameAndExtension } from "@/next/file"; +import log from "@/next/log"; +import FileType from "file-type"; + +/** + * Try to deduce the MIME type for the given {@link file}. Return the MIME type + * string if successful _and_ if it is an image or a video, otherwise return + * `undefined`. + * + * 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. + */ +export const tryDetectMediaMIMEType = async (file: File): Promise => { + const chunkSizeForTypeDetection = 4100; + + let mime: string | undefined; + try { + const fileChunk = file.slice(0, chunkSizeForTypeDetection); + const chunkData = new Uint8Array(await fileChunk.arrayBuffer()); + const result = await FileType.fromBuffer(chunkData); + mime = result?.mime; + } catch (e) { + log.error( + `Failed to deduce MIME type from file contents for ${file}, will try with the file name instead`, + e, + ); + } + + if (mime) { + if (mime.startsWith("image/") || mime.startsWith("video/")) return mime; + else throw new Error(`Detected MIME type ${mime} is not a media file`); + } + + let [, ext] = nameAndExtension(file.name); + if (!ext) return undefined; + ext = ext.toLowerCase(); + return KnownFileTypeInfos.find((f) => f.exactType == ext)?.mimeType; +}; diff --git a/web/apps/cast/src/services/typeDetectionService.ts b/web/apps/cast/src/services/typeDetectionService.ts deleted file mode 100644 index 883d96b0e16719565b59499281792451b2f62255..0000000000000000000000000000000000000000 --- a/web/apps/cast/src/services/typeDetectionService.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { FILE_TYPE } from "@/media/file"; -import { convertBytesToHumanReadable, nameAndExtension } from "@/next/file"; -import log from "@/next/log"; -import { CustomError } from "@ente/shared/error"; -import { - KNOWN_NON_MEDIA_FORMATS, - WHITELISTED_FILE_FORMATS, -} from "constants/upload"; -import FileType from "file-type"; -import { FileTypeInfo } from "types/upload"; - -const TYPE_VIDEO = "video"; -const TYPE_IMAGE = "image"; -const CHUNK_SIZE_FOR_TYPE_DETECTION = 4100; - -export async function getFileType(receivedFile: File): Promise { - try { - let fileType: FILE_TYPE; - - const typeResult = await extractFileType(receivedFile); - const mimTypeParts: string[] = typeResult.mime?.split("/"); - if (mimTypeParts?.length !== 2) { - throw Error(CustomError.INVALID_MIME_TYPE(typeResult.mime)); - } - - switch (mimTypeParts[0]) { - case TYPE_IMAGE: - fileType = FILE_TYPE.IMAGE; - break; - case TYPE_VIDEO: - fileType = FILE_TYPE.VIDEO; - break; - default: - throw Error(CustomError.NON_MEDIA_FILE); - } - return { - fileType, - exactType: typeResult.ext, - mimeType: typeResult.mime, - }; - } catch (e) { - const ne = nameAndExtension(receivedFile.name); - const fileFormat = ne[1].toLowerCase(); - const whiteListedFormat = WHITELISTED_FILE_FORMATS.find( - (a) => a.exactType === fileFormat, - ); - if (whiteListedFormat) { - return whiteListedFormat; - } - if (KNOWN_NON_MEDIA_FORMATS.includes(fileFormat)) { - 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 Error(CustomError.TYPE_DETECTION_FAILED(fileFormat)); - } -} - -async function extractFileType(file: File) { - const fileBlobChunk = file.slice(0, CHUNK_SIZE_FOR_TYPE_DETECTION); - const fileDataChunk = await getUint8ArrayView(fileBlobChunk); - return getFileTypeFromBuffer(fileDataChunk); -} - -export async function getUint8ArrayView(file: Blob): Promise { - try { - return new Uint8Array(await file.arrayBuffer()); - } catch (e) { - log.error( - `Failed to read file blob of size ${convertBytesToHumanReadable(file.size)}`, - e, - ); - throw e; - } -} - -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}`); - } - return result; -} diff --git a/web/apps/cast/src/types/upload.ts b/web/apps/cast/src/types/upload.ts index 91b5c3385180ab1630df6039e1caba661e0e8caf..b74621fadcd1d57ba3ea9297a5098c08666b5da9 100644 --- a/web/apps/cast/src/types/upload.ts +++ b/web/apps/cast/src/types/upload.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; export interface Metadata { title: string; diff --git a/web/apps/cast/src/utils/file.ts b/web/apps/cast/src/utils/file.ts index 1f04a916df56b5569eaf7517f133fde258a36955..b81a8630bb9572c2b3cd02ba19c6b41b9013e1d7 100644 --- a/web/apps/cast/src/utils/file.ts +++ b/web/apps/cast/src/utils/file.ts @@ -1,10 +1,10 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import { decodeLivePhoto } from "@/media/live-photo"; import log from "@/next/log"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { RAW_FORMATS } from "constants/upload"; import CastDownloadManager from "services/castDownloadManager"; -import { getFileType } from "services/typeDetectionService"; +import { tryDetectMediaMIMEType } from "services/detect-type"; import { EncryptedEnteFile, EnteFile, @@ -132,10 +132,11 @@ export const getPreviewableImage = async ( ); fileBlob = new Blob([imageData]); } - const fileType = await getFileType( + const mimeType = await tryDetectMediaMIMEType( new File([fileBlob], file.metadata.title), ); - fileBlob = new Blob([fileBlob], { type: fileType.mimeType }); + if (!mimeType) return undefined; + fileBlob = new Blob([fileBlob], { type: mimeType }); return fileBlob; } catch (e) { log.error("failed to download file", e); diff --git a/web/apps/photos/src/components/PhotoFrame.tsx b/web/apps/photos/src/components/PhotoFrame.tsx index 5ff70b3d45f2f958cea95fc588bfb01891909bcb..8c935ee2741a921085d055d848f519e37598166b 100644 --- a/web/apps/photos/src/components/PhotoFrame.tsx +++ b/web/apps/photos/src/components/PhotoFrame.tsx @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import log from "@/next/log"; import { PHOTOS_PAGES } from "@ente/shared/constants/pages"; import { CustomError } from "@ente/shared/error"; diff --git a/web/apps/photos/src/components/PhotoViewer/FileInfo/RenderFileName.tsx b/web/apps/photos/src/components/PhotoViewer/FileInfo/RenderFileName.tsx index c1b25e85065c868194ff294bbeebeeca3a9e5ca0..39905118550f5441322458995a740372e0c2780f 100644 --- a/web/apps/photos/src/components/PhotoViewer/FileInfo/RenderFileName.tsx +++ b/web/apps/photos/src/components/PhotoViewer/FileInfo/RenderFileName.tsx @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import { nameAndExtension } from "@/next/file"; import log from "@/next/log"; import { FlexWrapper } from "@ente/shared/components/Container"; diff --git a/web/apps/photos/src/components/PhotoViewer/index.tsx b/web/apps/photos/src/components/PhotoViewer/index.tsx index a19469a6f4591aa1a6f586bb3c5bc605e87c0328..4518011130d57411efc8c882e8841ec1bdd1b278 100644 --- a/web/apps/photos/src/components/PhotoViewer/index.tsx +++ b/web/apps/photos/src/components/PhotoViewer/index.tsx @@ -16,7 +16,7 @@ import { isSupportedRawFormat, } from "utils/file"; -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import { FlexWrapper } from "@ente/shared/components/Container"; import EnteSpinner from "@ente/shared/components/EnteSpinner"; import AlbumOutlined from "@mui/icons-material/AlbumOutlined"; diff --git a/web/apps/photos/src/components/PlaceholderThumbnails.tsx b/web/apps/photos/src/components/PlaceholderThumbnails.tsx index 7266bf178139e934b0436d289b0129158558280d..662e4228776715768623d9a001233d7b5d6ea634 100644 --- a/web/apps/photos/src/components/PlaceholderThumbnails.tsx +++ b/web/apps/photos/src/components/PlaceholderThumbnails.tsx @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import { Overlay } from "@ente/shared/components/Container"; import PhotoOutlined from "@mui/icons-material/PhotoOutlined"; import PlayCircleOutlineOutlined from "@mui/icons-material/PlayCircleOutlineOutlined"; diff --git a/web/apps/photos/src/components/Search/SearchBar/searchInput/index.tsx b/web/apps/photos/src/components/Search/SearchBar/searchInput/index.tsx index 87c45b59fd18f7f2f2aa45de61537863ddc26b0a..3f737b3e0c6920a1474fe2766bc36bc23674d4fe 100644 --- a/web/apps/photos/src/components/Search/SearchBar/searchInput/index.tsx +++ b/web/apps/photos/src/components/Search/SearchBar/searchInput/index.tsx @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import CloseIcon from "@mui/icons-material/Close"; import { IconButton } from "@mui/material"; import { t } from "i18next"; diff --git a/web/apps/photos/src/components/pages/gallery/PreviewCard.tsx b/web/apps/photos/src/components/pages/gallery/PreviewCard.tsx index b9288c59b2e83f7cc5c18153097c09fc6c085759..8091618a1f2e257c576b0654695ae7cf0172acb8 100644 --- a/web/apps/photos/src/components/pages/gallery/PreviewCard.tsx +++ b/web/apps/photos/src/components/pages/gallery/PreviewCard.tsx @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import log from "@/next/log"; import { Overlay } from "@ente/shared/components/Container"; import { CustomError } from "@ente/shared/error"; diff --git a/web/apps/photos/src/constants/upload.ts b/web/apps/photos/src/constants/upload.ts index b188626dff3f8e2815281090abb58ca4b861b543..faa8e586fb11a1c43d4c4f67b4607cebf7b9b44e 100644 --- a/web/apps/photos/src/constants/upload.ts +++ b/web/apps/photos/src/constants/upload.ts @@ -1,50 +1,5 @@ -import { FILE_TYPE } from "@/media/file"; import { ENCRYPTION_CHUNK_SIZE } from "@ente/shared/crypto/constants"; -import { FileTypeInfo, Location } from "types/upload"; - -// list of format that were missed by type-detection for some files. -export const WHITELISTED_FILE_FORMATS: FileTypeInfo[] = [ - { fileType: FILE_TYPE.IMAGE, exactType: "jpeg", mimeType: "image/jpeg" }, - { fileType: FILE_TYPE.IMAGE, exactType: "jpg", mimeType: "image/jpeg" }, - { fileType: FILE_TYPE.VIDEO, exactType: "webm", mimeType: "video/webm" }, - { fileType: FILE_TYPE.VIDEO, exactType: "mod", mimeType: "video/mpeg" }, - { fileType: FILE_TYPE.VIDEO, exactType: "mp4", mimeType: "video/mp4" }, - { fileType: FILE_TYPE.IMAGE, exactType: "gif", mimeType: "image/gif" }, - { fileType: FILE_TYPE.VIDEO, exactType: "dv", mimeType: "video/x-dv" }, - { - fileType: FILE_TYPE.VIDEO, - exactType: "wmv", - mimeType: "video/x-ms-asf", - }, - { - fileType: FILE_TYPE.VIDEO, - exactType: "hevc", - mimeType: "video/hevc", - }, - { - fileType: FILE_TYPE.IMAGE, - exactType: "raf", - mimeType: "image/x-fuji-raf", - }, - { - fileType: FILE_TYPE.IMAGE, - exactType: "orf", - mimeType: "image/x-olympus-orf", - }, - - { - fileType: FILE_TYPE.IMAGE, - exactType: "crw", - mimeType: "image/x-canon-crw", - }, - { - fileType: FILE_TYPE.VIDEO, - exactType: "mov", - mimeType: "video/quicktime", - }, -]; - -export const KNOWN_NON_MEDIA_FORMATS = ["xmp", "html", "txt"]; +import { Location } from "types/upload"; // this is the chunk size of the un-encrypted file which is read and encrypted before uploading it as a single part. export const MULTIPART_PART_SIZE = 20 * 1024 * 1024; diff --git a/web/apps/photos/src/services/clip-service.ts b/web/apps/photos/src/services/clip-service.ts index ab1ce928c5a4a5802b2771a687cb4903be037732..703c89cf4bc78ea43551249bde2a8dc4d6e09fc5 100644 --- a/web/apps/photos/src/services/clip-service.ts +++ b/web/apps/photos/src/services/clip-service.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import { ensureElectron } from "@/next/electron"; import log from "@/next/log"; import ComlinkCryptoWorker from "@ente/shared/crypto"; diff --git a/web/apps/photos/src/services/deduplicationService.ts b/web/apps/photos/src/services/deduplicationService.ts index f9416396f2799ef206761356a23c86e5399d1757..d591e89ff7c3b35bec8804d1f4a9f6ddd3bc879d 100644 --- a/web/apps/photos/src/services/deduplicationService.ts +++ b/web/apps/photos/src/services/deduplicationService.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import log from "@/next/log"; import HTTPService from "@ente/shared/network/HTTPService"; import { getEndpoint } from "@ente/shared/network/api"; diff --git a/web/apps/photos/src/services/download/index.ts b/web/apps/photos/src/services/download/index.ts index 7a2c359329bc6bfdb7820f04f0a4bb0a6549424f..37eeac440d8ac56e87fe60604edfa8c2b5f68396 100644 --- a/web/apps/photos/src/services/download/index.ts +++ b/web/apps/photos/src/services/download/index.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import { decodeLivePhoto } from "@/media/live-photo"; import { openCache, type BlobCache } from "@/next/blob-cache"; import log from "@/next/log"; diff --git a/web/apps/photos/src/services/exif.ts b/web/apps/photos/src/services/exif.ts index acc6a0b088ce28ecd54dccfe2f04f7f4fe950446..aea44dedc5ab7fbd7d97d9076e89c0eb0c6e8d60 100644 --- a/web/apps/photos/src/services/exif.ts +++ b/web/apps/photos/src/services/exif.ts @@ -1,9 +1,10 @@ +import { type FileTypeInfo } from "@/media/file-type"; import log from "@/next/log"; import { validateAndGetCreationUnixTimeInMicroSeconds } from "@ente/shared/time"; import { NULL_LOCATION } from "constants/upload"; import exifr from "exifr"; import piexif from "piexifjs"; -import { FileTypeInfo, Location } from "types/upload"; +import { Location } from "types/upload"; type ParsedEXIFData = Record & Partial<{ diff --git a/web/apps/photos/src/services/export/index.ts b/web/apps/photos/src/services/export/index.ts index c949b4636021994de8d51de8b185ea133a2d6d9c..3ec98a8d09a559adc1362d2a8a18b13a179ae1ff 100644 --- a/web/apps/photos/src/services/export/index.ts +++ b/web/apps/photos/src/services/export/index.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import { decodeLivePhoto } from "@/media/live-photo"; import { ensureElectron } from "@/next/electron"; import log from "@/next/log"; diff --git a/web/apps/photos/src/services/export/migration.ts b/web/apps/photos/src/services/export/migration.ts index 6c13ce7fb304bc8e3c1917edb482f4a1f91d1732..a6d0af29f5cb85d05cef5f4fe6dd4372a741fa40 100644 --- a/web/apps/photos/src/services/export/migration.ts +++ b/web/apps/photos/src/services/export/migration.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import { decodeLivePhoto } from "@/media/live-photo"; import { ensureElectron } from "@/next/electron"; import log from "@/next/log"; diff --git a/web/apps/photos/src/services/fix-exif.ts b/web/apps/photos/src/services/fix-exif.ts index 55f3dce1282299102d81ff9088cf7bb193e3b479..81c3611f000faf44e718318f181c836bf1756951 100644 --- a/web/apps/photos/src/services/fix-exif.ts +++ b/web/apps/photos/src/services/fix-exif.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import log from "@/next/log"; import { validateAndGetCreationUnixTimeInMicroSeconds } from "@ente/shared/time"; import type { FixOption } from "components/FixCreationTime"; diff --git a/web/apps/photos/src/services/machineLearning/mlWorkManager.ts b/web/apps/photos/src/services/machineLearning/mlWorkManager.ts index a00c24ad3a8bd0fdc10c694cc819eb8bf4b7775f..d1c5e9db5e9da651516c6154dca61a80989b48f7 100644 --- a/web/apps/photos/src/services/machineLearning/mlWorkManager.ts +++ b/web/apps/photos/src/services/machineLearning/mlWorkManager.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import log from "@/next/log"; import { ComlinkWorker } from "@/next/worker/comlink-worker"; import { eventBus, Events } from "@ente/shared/events"; diff --git a/web/apps/photos/src/services/machineLearning/readerService.ts b/web/apps/photos/src/services/machineLearning/readerService.ts index d4538064124e3038ce87b621e26a8654b63869e4..62aebdbd1fa993a0cd2c08e4ddd8774549e8bdf5 100644 --- a/web/apps/photos/src/services/machineLearning/readerService.ts +++ b/web/apps/photos/src/services/machineLearning/readerService.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import log from "@/next/log"; import { MLSyncContext, MLSyncFileContext } from "types/machineLearning"; import { diff --git a/web/apps/photos/src/services/searchService.ts b/web/apps/photos/src/services/searchService.ts index 362e76fbef497bab8750130ab40d3fc0568f56a6..96c574b9ddf144f4311dfdf267888796d91d4f27 100644 --- a/web/apps/photos/src/services/searchService.ts +++ b/web/apps/photos/src/services/searchService.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import log from "@/next/log"; import * as chrono from "chrono-node"; import { t } from "i18next"; diff --git a/web/apps/photos/src/services/typeDetectionService.ts b/web/apps/photos/src/services/typeDetectionService.ts index 598b100465317765ab8eab3e292a108b59d5418d..3ca50ca02d97c7a3fe290e3d40c7ba6f84fa0318 100644 --- a/web/apps/photos/src/services/typeDetectionService.ts +++ b/web/apps/photos/src/services/typeDetectionService.ts @@ -1,13 +1,13 @@ -import { FILE_TYPE } from "@/media/file"; +import { + FILE_TYPE, + KnownFileTypeInfos, + KnownNonMediaFileExtensions, + type FileTypeInfo, +} from "@/media/file-type"; import log from "@/next/log"; import { ElectronFile } from "@/next/types/file"; import { CustomError } from "@ente/shared/error"; -import { - KNOWN_NON_MEDIA_FORMATS, - WHITELISTED_FILE_FORMATS, -} from "constants/upload"; import FileType, { FileTypeResult } from "file-type"; -import { FileTypeInfo } from "types/upload"; import { getFileExtension } from "utils/file"; import { getUint8ArrayView } from "./readerService"; @@ -50,13 +50,13 @@ export async function getFileType( }; } catch (e) { const fileFormat = getFileExtension(receivedFile.name); - const whiteListedFormat = WHITELISTED_FILE_FORMATS.find( + const whiteListedFormat = KnownFileTypeInfos.find( (a) => a.exactType === fileFormat, ); if (whiteListedFormat) { return whiteListedFormat; } - if (KNOWN_NON_MEDIA_FORMATS.includes(fileFormat)) { + if (KnownNonMediaFileExtensions.includes(fileFormat)) { throw Error(CustomError.UNSUPPORTED_FILE_FORMAT); } if (e.message === CustomError.NON_MEDIA_FILE) { diff --git a/web/apps/photos/src/services/upload/metadata.ts b/web/apps/photos/src/services/upload/metadata.ts index 4d7b06e33fcbcb7af9a761ff80cdbd410c15faa8..b5716e4de22ffa595bca3878c6f28896ac7c61d3 100644 --- a/web/apps/photos/src/services/upload/metadata.ts +++ b/web/apps/photos/src/services/upload/metadata.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE, type FileTypeInfo } from "@/media/file-type"; import { getFileNameSize } from "@/next/file"; import log from "@/next/log"; import { ElectronFile } from "@/next/types/file"; @@ -17,7 +17,6 @@ import * as ffmpegService from "services/ffmpeg"; import { getElectronFileStream, getFileStream } from "services/readerService"; import { FilePublicMagicMetadataProps } from "types/file"; import { - FileTypeInfo, Metadata, ParsedExtractedMetadata, type LivePhotoAssets2, diff --git a/web/apps/photos/src/services/upload/thumbnail.ts b/web/apps/photos/src/services/upload/thumbnail.ts index d8ba3a20174066dc5c9a178469f47d9d13da9bcc..b89bc2f793c0376610d79bda00373f84110efc67 100644 --- a/web/apps/photos/src/services/upload/thumbnail.ts +++ b/web/apps/photos/src/services/upload/thumbnail.ts @@ -1,11 +1,10 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE, type FileTypeInfo } from "@/media/file-type"; import log from "@/next/log"; import { type Electron } from "@/next/types/ipc"; import { withTimeout } from "@ente/shared/utils"; import { BLACK_THUMBNAIL_BASE64 } from "constants/upload"; import * as ffmpeg from "services/ffmpeg"; import { heicToJPEG } from "services/heic-convert"; -import { FileTypeInfo } from "types/upload"; import { isFileHEIC } from "utils/file"; /** Maximum width or height of the generated thumbnail */ diff --git a/web/apps/photos/src/services/upload/uploadManager.ts b/web/apps/photos/src/services/upload/uploadManager.ts index 9917ee8925c145e16be15f055eead83818df291b..0654141584940f2a9150f2ba3bdffcc1aab7179d 100644 --- a/web/apps/photos/src/services/upload/uploadManager.ts +++ b/web/apps/photos/src/services/upload/uploadManager.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import { potentialFileTypeFromExtension } from "@/media/live-photo"; import { ensureElectron } from "@/next/electron"; import { nameAndExtension } from "@/next/file"; diff --git a/web/apps/photos/src/services/upload/uploadService.ts b/web/apps/photos/src/services/upload/uploadService.ts index 1df51a5ac2f9d76de8db346965c732c81ecd8a77..4e2292445b5ae3f7a62a66552f0a53dd2ef2aa94 100644 --- a/web/apps/photos/src/services/upload/uploadService.ts +++ b/web/apps/photos/src/services/upload/uploadService.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE, type FileTypeInfo } from "@/media/file-type"; import { encodeLivePhoto } from "@/media/live-photo"; import { ensureElectron } from "@/next/electron"; import { basename } from "@/next/file"; @@ -28,7 +28,6 @@ import { BackupedFile, EncryptedFile, FileInMemory, - FileTypeInfo, FileWithMetadata, ProcessedFile, PublicUploadProps, diff --git a/web/apps/photos/src/types/search/index.ts b/web/apps/photos/src/types/search/index.ts index 5918bb9ddec7ba018840b8674a45639b4b837e54..cf50f4a0602f00575575db07ba5cbe79b111c3d0 100644 --- a/web/apps/photos/src/types/search/index.ts +++ b/web/apps/photos/src/types/search/index.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import { City } from "services/locationSearchService"; import { LocationTagData } from "types/entity"; import { EnteFile } from "types/file"; diff --git a/web/apps/photos/src/types/upload/index.ts b/web/apps/photos/src/types/upload/index.ts index bdcef330b24eb471153d15498076364d98a575fc..2111617bde228ee333424a7bf2c0e4edacb2cd19 100644 --- a/web/apps/photos/src/types/upload/index.ts +++ b/web/apps/photos/src/types/upload/index.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import type { ElectronFile } from "@/next/types/file"; import { B64EncryptionResult, @@ -46,14 +46,6 @@ export interface MultipartUploadURLs { completeURL: string; } -export interface FileTypeInfo { - fileType: FILE_TYPE; - exactType: string; - mimeType?: string; - imageType?: string; - videoType?: string; -} - export interface UploadAsset { isLivePhoto?: boolean; file?: File | ElectronFile; diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index 1ae6804bbd1196928b434e8aa106d3bfb63e8c83..c6e5d5356885e9f5637f88961f1be7e2fe668ae0 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE, type FileTypeInfo } from "@/media/file-type"; import { decodeLivePhoto } from "@/media/live-photo"; import log from "@/next/log"; import { CustomErrorMessage, type Electron } from "@/next/types/ipc"; @@ -35,7 +35,6 @@ import { SetFilesDownloadProgressAttributesCreator, } from "types/gallery"; import { VISIBILITY_STATE } from "types/magicMetadata"; -import { FileTypeInfo } from "types/upload"; import { isArchivedFile, updateMagicMetadata } from "utils/magicMetadata"; import { safeFileName } from "utils/native-fs"; import { writeStream } from "utils/native-stream"; diff --git a/web/apps/photos/src/utils/machineLearning/index.ts b/web/apps/photos/src/utils/machineLearning/index.ts index 4e9b795ec1caafdde54633658cb6f6672185c908..bc9ae397496f0f84b4b362f46baad1dbbf652eeb 100644 --- a/web/apps/photos/src/utils/machineLearning/index.ts +++ b/web/apps/photos/src/utils/machineLearning/index.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import { decodeLivePhoto } from "@/media/live-photo"; import log from "@/next/log"; import PQueue from "p-queue"; diff --git a/web/apps/photos/src/utils/photoFrame/index.ts b/web/apps/photos/src/utils/photoFrame/index.ts index ccd47b604d450948feeb9a9b9ef9c3bc0c9e38c2..93b680149f23a56e9d4accf7789801187966f58a 100644 --- a/web/apps/photos/src/utils/photoFrame/index.ts +++ b/web/apps/photos/src/utils/photoFrame/index.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import log from "@/next/log"; import { LivePhotoSourceURL, SourceURLs } from "services/download"; import { EnteFile } from "types/file"; diff --git a/web/apps/photos/tests/upload.test.ts b/web/apps/photos/tests/upload.test.ts index 3fc7e8541e15f2aa71421ce86b793b50dc2ec0ed..17143da09797eb400012abe3a1fa34c945e22525 100644 --- a/web/apps/photos/tests/upload.test.ts +++ b/web/apps/photos/tests/upload.test.ts @@ -1,4 +1,4 @@ -import { FILE_TYPE } from "@/media/file"; +import { FILE_TYPE } from "@/media/file-type"; import { tryToParseDateTime } from "@ente/shared/time"; import { getLocalCollections } from "services/collectionService"; import { getLocalFiles } from "services/fileService"; diff --git a/web/packages/media/file-type.ts b/web/packages/media/file-type.ts new file mode 100644 index 0000000000000000000000000000000000000000..e7eb902751bbf4f5e17805aa9c2d079f4927a032 --- /dev/null +++ b/web/packages/media/file-type.ts @@ -0,0 +1,58 @@ +export enum FILE_TYPE { + IMAGE, + VIDEO, + LIVE_PHOTO, + OTHERS, +} + +export interface FileTypeInfo { + fileType: FILE_TYPE; + exactType: string; + mimeType?: string; + imageType?: string; + videoType?: string; +} + +// list of format that were missed by type-detection for some files. +export const KnownFileTypeInfos: FileTypeInfo[] = [ + { fileType: FILE_TYPE.IMAGE, exactType: "jpeg", mimeType: "image/jpeg" }, + { fileType: FILE_TYPE.IMAGE, exactType: "jpg", mimeType: "image/jpeg" }, + { fileType: FILE_TYPE.VIDEO, exactType: "webm", mimeType: "video/webm" }, + { fileType: FILE_TYPE.VIDEO, exactType: "mod", mimeType: "video/mpeg" }, + { fileType: FILE_TYPE.VIDEO, exactType: "mp4", mimeType: "video/mp4" }, + { fileType: FILE_TYPE.IMAGE, exactType: "gif", mimeType: "image/gif" }, + { fileType: FILE_TYPE.VIDEO, exactType: "dv", mimeType: "video/x-dv" }, + { + fileType: FILE_TYPE.VIDEO, + exactType: "wmv", + mimeType: "video/x-ms-asf", + }, + { + fileType: FILE_TYPE.VIDEO, + exactType: "hevc", + mimeType: "video/hevc", + }, + { + fileType: FILE_TYPE.IMAGE, + exactType: "raf", + mimeType: "image/x-fuji-raf", + }, + { + fileType: FILE_TYPE.IMAGE, + exactType: "orf", + mimeType: "image/x-olympus-orf", + }, + + { + fileType: FILE_TYPE.IMAGE, + exactType: "crw", + mimeType: "image/x-canon-crw", + }, + { + fileType: FILE_TYPE.VIDEO, + exactType: "mov", + mimeType: "video/quicktime", + }, +]; + +export const KnownNonMediaFileExtensions = ["xmp", "html", "txt"]; diff --git a/web/packages/media/file.ts b/web/packages/media/file.ts deleted file mode 100644 index 8917db8025371beac88ab75faf6d8b28a5697ebc..0000000000000000000000000000000000000000 --- a/web/packages/media/file.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum FILE_TYPE { - IMAGE, - VIDEO, - LIVE_PHOTO, - OTHERS, -} diff --git a/web/packages/media/live-photo.ts b/web/packages/media/live-photo.ts index c4a631467366c07245b767048ed33e686afadb80..0e48d7120979390f69af3bf087c1ee8afb942ac4 100644 --- a/web/packages/media/live-photo.ts +++ b/web/packages/media/live-photo.ts @@ -1,6 +1,6 @@ import { fileNameFromComponents, nameAndExtension } from "@/next/file"; import JSZip from "jszip"; -import { FILE_TYPE } from "./file"; +import { FILE_TYPE } from "./file-type"; const potentialImageExtensions = [ "heic",