diff --git a/README.md b/README.md index 63e97f177..d90d2e134 100644 --- a/README.md +++ b/README.md @@ -51,3 +51,8 @@ If you would like to motivate us to keep building, you can do so by [starring](h Follow us on [Twitter](https://twitter.com/enteio) and join [r/enteio](https://reddit.com/r/enteio) to get regular updates, connect with other customers, and discuss your ideas. An important part of our journey is to build better software by consistently listening to community feedback. Please feel free to [share your thoughts](mailto:feedback@ente.io) with us at any time. + +--- + +Cross-browser testing provided by +[](https://www.browserstack.com/open-source) diff --git a/src/constants/upload/index.ts b/src/constants/upload/index.ts index fb9f0a15d..532e6877e 100644 --- a/src/constants/upload/index.ts +++ b/src/constants/upload/index.ts @@ -4,9 +4,9 @@ import { Location, ParsedExtractedMetadata } from 'types/upload'; // list of format that were missed by type-detection for some files. export const FORMAT_MISSED_BY_FILE_TYPE_LIB = [ - { fileType: FILE_TYPE.IMAGE, exactType: 'jpeg' }, - { fileType: FILE_TYPE.IMAGE, exactType: 'jpg' }, - { fileType: FILE_TYPE.VIDEO, exactType: 'webm' }, + { 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' }, ]; // this is the chunk size of the un-encrypted file which is read and encrypted before uploading it as a single part. diff --git a/src/services/typeDetectionService.ts b/src/services/typeDetectionService.ts index a9fc46f62..5f101cb96 100644 --- a/src/services/typeDetectionService.ts +++ b/src/services/typeDetectionService.ts @@ -32,7 +32,11 @@ export async function getFileType( default: fileType = FILE_TYPE.OTHERS; } - return { fileType, exactType: typeResult.ext }; + return { + fileType, + exactType: typeResult.ext, + mimeType: typeResult.mime, + }; } catch (e) { const fileFormat = getFileExtension(receivedFile.name); const formatMissedByTypeDetection = FORMAT_MISSED_BY_FILE_TYPE_LIB.find( @@ -44,7 +48,11 @@ export async function getFileType( logError(e, CustomError.TYPE_DETECTION_FAILED, { fileFormat, }); - return { fileType: FILE_TYPE.OTHERS, exactType: fileFormat }; + return { + fileType: FILE_TYPE.OTHERS, + exactType: fileFormat, + mimeType: receivedFile.type, + }; } } @@ -53,7 +61,7 @@ async function extractFileType(reader: FileReader, file: File) { return getFileTypeFromBlob(reader, fileChunkBlob); } -export async function getFileTypeFromBlob(reader: FileReader, fileBlob: Blob) { +async function getFileTypeFromBlob(reader: FileReader, fileBlob: Blob) { try { const initialFiledata = await getUint8ArrayView(reader, fileBlob); return await FileType.fromBuffer(initialFiledata); diff --git a/src/services/upload/exifService.ts b/src/services/upload/exifService.ts index 1abb8dfb0..8554b424f 100644 --- a/src/services/upload/exifService.ts +++ b/src/services/upload/exifService.ts @@ -74,7 +74,7 @@ export async function updateFileCreationDateInEXIF( } } -export async function convertImageToDataURL(reader: FileReader, url: string) { +async function convertImageToDataURL(reader: FileReader, url: string) { const blob = await fetch(url).then((r) => r.blob()); const dataURL = await new Promise((resolve) => { reader.onload = () => resolve(reader.result as string); diff --git a/src/types/upload/index.ts b/src/types/upload/index.ts index 35a4692a6..6d7082a72 100644 --- a/src/types/upload/index.ts +++ b/src/types/upload/index.ts @@ -48,6 +48,7 @@ export interface MultipartUploadURLs { export interface FileTypeInfo { fileType: FILE_TYPE; exactType: string; + mimeType?: string; imageType?: string; videoType?: string; } diff --git a/src/utils/file/index.ts b/src/utils/file/index.ts index 7397cf0b4..2975c25f1 100644 --- a/src/utils/file/index.ts +++ b/src/utils/file/index.ts @@ -7,7 +7,7 @@ import { PublicMagicMetadataProps, } from 'types/file'; import { decodeMotionPhoto } from 'services/motionPhotoService'; -import { getFileTypeFromBlob } from 'services/typeDetectionService'; +import { getFileType } from 'services/typeDetectionService'; import DownloadManager from 'services/downloadManager'; import { logError } from 'utils/sentry'; import { User } from 'types/user'; @@ -25,7 +25,6 @@ import { import PublicCollectionDownloadManager from 'services/publicCollectionDownloadManager'; import HEICConverter from 'services/heicConverter/heicConverterService'; import ffmpegService from 'services/ffmpeg/ffmpegService'; - export function downloadAsFile(filename: string, content: string) { const file = new Blob([content], { type: 'text/plain', @@ -48,76 +47,80 @@ export async function downloadFile( token?: string, passwordToken?: string ) { - let fileURL: string; - let tempURL: string; + let fileBlob: Blob; + const fileReader = new FileReader(); if (accessedThroughSharedURL) { - fileURL = await PublicCollectionDownloadManager.getCachedOriginalFile( - file - )[0]; - tempURL; + const fileURL = + await PublicCollectionDownloadManager.getCachedOriginalFile( + file + )[0]; if (!fileURL) { - tempURL = URL.createObjectURL( - await new Response( - await PublicCollectionDownloadManager.downloadFile( - token, - passwordToken, - file - ) - ).blob() - ); - console.log({ tempURL }); - fileURL = tempURL; + fileBlob = await new Response( + await PublicCollectionDownloadManager.downloadFile( + token, + passwordToken, + file + ) + ).blob(); + } else { + fileBlob = await (await fetch(fileURL)).blob(); } } else { - fileURL = await DownloadManager.getCachedOriginalFile(file)[0]; + const fileURL = await DownloadManager.getCachedOriginalFile(file)[0]; if (!fileURL) { - tempURL = URL.createObjectURL( - await new Response( - await DownloadManager.downloadFile(file) - ).blob() - ); - fileURL = tempURL; + fileBlob = await new Response( + await DownloadManager.downloadFile(file) + ).blob(); + } else { + fileBlob = await (await fetch(fileURL)).blob(); } } - const fileType = getFileExtension(file.metadata.title); - let tempEditedFileURL: string; + const fileType = await getFileType( + fileReader, + new File([fileBlob], file.metadata.title) + ); if ( file.pubMagicMetadata?.data.editedTime && - (fileType === TYPE_JPEG || fileType === TYPE_JPG) + (fileType.exactType === TYPE_JPEG || fileType.exactType === TYPE_JPG) ) { - let fileBlob = await (await fetch(fileURL)).blob(); - fileBlob = await updateFileCreationDateInEXIF( - new FileReader(), + fileReader, fileBlob, new Date(file.pubMagicMetadata.data.editedTime / 1000) ); - tempEditedFileURL = URL.createObjectURL(fileBlob); - fileURL = tempEditedFileURL; } let tempImageURL: string; let tempVideoURL: string; + let tempURL: string; if (file.metadata.fileType === FILE_TYPE.LIVE_PHOTO) { - const fileBlob = await (await fetch(fileURL)).blob(); const originalName = fileNameWithoutExtension(file.metadata.title); const motionPhoto = await decodeMotionPhoto(fileBlob, originalName); - tempImageURL = URL.createObjectURL(new Blob([motionPhoto.image])); - tempVideoURL = URL.createObjectURL(new Blob([motionPhoto.video])); - downloadUsingAnchor(motionPhoto.imageNameTitle, tempImageURL); - downloadUsingAnchor(motionPhoto.videoNameTitle, tempVideoURL); + const image = new File([motionPhoto.image], motionPhoto.imageNameTitle); + const imageType = await getFileType(fileReader, image); + tempImageURL = URL.createObjectURL( + new Blob([motionPhoto.image], { type: imageType.mimeType }) + ); + const video = new File([motionPhoto.video], motionPhoto.videoNameTitle); + const videoType = await getFileType(fileReader, video); + tempVideoURL = URL.createObjectURL( + new Blob([motionPhoto.video], { type: videoType.mimeType }) + ); + downloadUsingAnchor(tempImageURL, motionPhoto.imageNameTitle); + downloadUsingAnchor(tempVideoURL, motionPhoto.videoNameTitle); } else { - downloadUsingAnchor(file.metadata.title, fileURL); + fileBlob = new Blob([fileBlob], { type: fileType.mimeType }); + tempURL = URL.createObjectURL(fileBlob); + downloadUsingAnchor(tempURL, file.metadata.title); } tempURL && URL.revokeObjectURL(tempURL); - tempEditedFileURL && URL.revokeObjectURL(tempEditedFileURL); tempImageURL && URL.revokeObjectURL(tempImageURL); tempVideoURL && URL.revokeObjectURL(tempVideoURL); } -function downloadUsingAnchor(name: string, link: string) { +function downloadUsingAnchor(link: string, name: string) { const a = document.createElement('a'); a.style.display = 'none'; a.href = link; @@ -335,11 +338,10 @@ export async function convertForPreview( fileBlob: Blob ): Promise { const convertIfHEIC = async (fileName: string, fileBlob: Blob) => { - const typeFromExtension = getFileExtension(fileName); const reader = new FileReader(); - const mimeType = - (await getFileTypeFromBlob(reader, fileBlob))?.mime ?? - typeFromExtension; + const mimeType = ( + await getFileType(reader, new File([fileBlob], file.metadata.title)) + ).exactType; if (isFileHEIC(mimeType)) { fileBlob = await HEICConverter.convert(fileBlob); }