diff --git a/web/apps/photos/src/services/upload/thumbnail.ts b/web/apps/photos/src/services/upload/thumbnail.ts index 01bb6c4f0c..b64521497d 100644 --- a/web/apps/photos/src/services/upload/thumbnail.ts +++ b/web/apps/photos/src/services/upload/thumbnail.ts @@ -14,25 +14,26 @@ const maxThumbnailDimension = 720; const maxThumbnailSize = 100 * 1024; // 100 KB /** - * Generate a JPEG thumbnail for the given image or video data. + * Generate a JPEG thumbnail for the given image or video blob. * * The thumbnail has a smaller file size so that is quick to load. But more * importantly, it uses a universal file format (JPEG in our case) so that the * thumbnail itself can be opened in all clients, even those like the web client * itself that might not yet have support for more exotic formats. * - * @param blob The data (blob) of the file whose thumbnail we want to generate. - * @param fileTypeInfo The type information for the file. + * @param blob The image or video blob whose thumbnail we want to generate. + * + * @param fileTypeInfo The type information for the file this blob came from. * * @return The JPEG data of the generated thumbnail. */ -export const generateThumbnail = async ( +export const generateThumbnailWeb = async ( blob: Blob, fileTypeInfo: FileTypeInfo, ): Promise => fileTypeInfo.fileType === FILE_TYPE.IMAGE ? await generateImageThumbnailUsingCanvas(blob, fileTypeInfo) - : await generateVideoThumbnail(blob); + : await generateVideoThumbnailWeb(blob); const generateImageThumbnailUsingCanvas = async ( blob: Blob, @@ -74,12 +75,12 @@ const generateImageThumbnailUsingCanvas = async ( return await compressedJPEGData(canvas); }; -const generateVideoThumbnail = async (blob: Blob) => { +const generateVideoThumbnailWeb = async (blob: Blob) => { try { - return await ffmpeg.generateVideoThumbnail(blob); + return await ffmpeg.generateVideoThumbnailWeb(blob); } catch (e) { log.error( - `Failed to generate video thumbnail using FFmpeg, will fallback to canvas`, + `Failed to generate video thumbnail using the wasm FFmpeg web worker, will fallback to canvas`, e, ); return generateVideoThumbnailUsingCanvas(blob); @@ -173,52 +174,35 @@ const percentageSizeDiff = ( ) => ((oldThumbnailSize - newThumbnailSize) * 100) / oldThumbnailSize; /** - * A fallback, black, thumbnail for use in cases where thumbnail generation - * fails. - */ -export const fallbackThumbnail = () => - Uint8Array.from(atob(BLACK_THUMBNAIL_BASE64), (c) => c.charCodeAt(0)); - -/** - * Generate a JPEG thumbnail for the given file using native tools. + * Generate a JPEG thumbnail for the given file or path using native tools. * * This function only works when we're running in the context of our desktop * app, and this dependency is enforced by the need to pass the {@link electron} * object which we use to perform IPC with the Node.js side of our desktop app. * - * @param fileOrPath Either the image or video File, or the path to the image or - * video file on the user's local filesystem, whose thumbnail we want to + * @param dataOrPath The image or video {@link File}, or the path to the image + * or video file on the user's local filesystem, whose thumbnail we want to * generate. * - * @param fileTypeInfo The type information for the file. + * @param fileTypeInfo The type information for {@link fileOrPath}. * * @return The JPEG data of the generated thumbnail. * - * @see {@link generateThumbnail}. + * See also {@link generateThumbnailWeb}. */ export const generateThumbnailNative = async ( electron: Electron, fileOrPath: File | string, fileTypeInfo: FileTypeInfo, -): Promise => { - try { - const thumbnail = - fileTypeInfo.fileType === FILE_TYPE.IMAGE - ? await generateImageThumbnailNative(electron, fileOrPath) - : await generateVideoThumbnail(blob); - - if (thumbnail.length == 0) throw new Error("Empty thumbnail"); - return { thumbnail, hasStaticThumbnail: false }; - } catch (e) { - log.error(`Failed to generate ${fileTypeInfo.exactType} thumbnail`, e); - return { thumbnail: fallbackThumbnail(), hasStaticThumbnail: true }; - } -}; +): Promise => + fileTypeInfo.fileType === FILE_TYPE.IMAGE + ? await generateImageThumbnailNative(electron, fileOrPath) + : await generateVideoThumbnailNative(blob); const generateImageThumbnailNative = async ( electron: Electron, fileOrPath: File | string, -): Promise => { +) => { const startTime = Date.now(); const jpegData = await electron.generateImageThumbnail( fileOrPath instanceof File @@ -232,3 +216,19 @@ const generateImageThumbnailNative = async ( ); return jpegData; }; + +const dataOrPath = (fileOrPath) => { + fileOrPath +} +const generateVideoThumbnailNative = async ( + electron: Electron, + fileOrPath: File | string, +) => ffmpeg.generateVideoThumbnailNative(electron, ) + + +/** + * A fallback, black, thumbnail for use in cases where thumbnail generation + * fails. + */ +export const fallbackThumbnail = () => + Uint8Array.from(atob(BLACK_THUMBNAIL_BASE64), (c) => c.charCodeAt(0)); diff --git a/web/apps/photos/src/services/upload/uploadService.ts b/web/apps/photos/src/services/upload/uploadService.ts index f49a2a1fb1..f531c22388 100644 --- a/web/apps/photos/src/services/upload/uploadService.ts +++ b/web/apps/photos/src/services/upload/uploadService.ts @@ -375,7 +375,6 @@ class ModuleState { const moduleState = new ModuleState(); - /** * Read the given file or path into an in-memory representation. * @@ -424,13 +423,19 @@ const moduleState = new ModuleState(); * we can do all the rest of the IPC operations using the path itself, and for * the read during upload using a streaming IPC mechanism. */ -async function readFile( +const readFileOrPath = async ( fileOrPath: File | string, fileTypeInfo: FileTypeInfo, -): Promise { +): Promise => { log.info(`Reading file ${fopLabel(fileOrPath)} `); - let thumbnail: Uint8Array + // If it's a file, read it into data + const dataOrPath = + fileOrPath instanceof File + ? new Uint8Array(await fileOrPath.arrayBuffer()) + : fileOrPath; + + let thumbnail: Uint8Array; const electron = globalThis.electron; const available = !moduleState.isNativeThumbnailCreationNotAvailable; @@ -462,7 +467,6 @@ async function readFile( filedata = await getUint8ArrayView(rawFile); } - try { const thumbnail = fileTypeInfo.fileType === FILE_TYPE.IMAGE @@ -476,11 +480,9 @@ async function readFile( return { thumbnail: fallbackThumbnail(), hasStaticThumbnail: true }; } - if (filedata instanceof Uint8Array) { - } else { - filedata.stream + filedata.stream; } log.info(`read file data successfully ${getFileNameSize(rawFile)} `); @@ -495,7 +497,7 @@ async function readFile( thumbnail, hasStaticThumbnail, }; -} +}; async function readLivePhoto( fileTypeInfo: FileTypeInfo,