From f3f7288deb5097e47663b36df414ebc65986ef24 Mon Sep 17 00:00:00 2001 From: Abhinav Date: Mon, 2 Jan 2023 21:08:08 +0530 Subject: [PATCH] add image generation api --- src/api/heicConvert.ts | 9 --- src/api/imageProcessor.ts | 47 ++++++++++++++++ src/preload.ts | 3 +- .../{heicConverter.ts => imageProcessor.ts} | 56 +++++++++++++++++++ src/utils/ipcComms.ts | 9 ++- 5 files changed, 113 insertions(+), 11 deletions(-) delete mode 100644 src/api/heicConvert.ts create mode 100644 src/api/imageProcessor.ts rename src/services/{heicConverter.ts => imageProcessor.ts} (56%) diff --git a/src/api/heicConvert.ts b/src/api/heicConvert.ts deleted file mode 100644 index 68909b678..000000000 --- a/src/api/heicConvert.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ipcRenderer } from 'electron/renderer'; - -export async function convertHEIC(fileData: Uint8Array): Promise { - const convertedFileData = await ipcRenderer.invoke( - 'convert-heic', - fileData - ); - return convertedFileData; -} diff --git a/src/api/imageProcessor.ts b/src/api/imageProcessor.ts new file mode 100644 index 000000000..ffeb0e60e --- /dev/null +++ b/src/api/imageProcessor.ts @@ -0,0 +1,47 @@ +import { ipcRenderer } from 'electron/renderer'; +import { existsSync } from 'fs'; +import { logError } from '../services/logging'; +import { ElectronFile } from '../types'; + +export async function convertHEIC(fileData: Uint8Array): Promise { + const convertedFileData = await ipcRenderer.invoke( + 'convert-heic', + fileData + ); + return convertedFileData; +} + +export async function generateImageThumbnail( + inputFile: File | ElectronFile, + maxDimension: number +): Promise { + let inputFilePath = null; + let createdTempInputFile = null; + try { + if (!existsSync(inputFile.path)) { + const inputFileData = new Uint8Array(await inputFile.arrayBuffer()); + inputFilePath = await ipcRenderer.invoke( + 'write-temp-file', + inputFileData, + inputFile.name + ); + createdTempInputFile = true; + } else { + inputFilePath = inputFile.path; + } + const thumbnail = await ipcRenderer.invoke( + 'generate-image-thumbnail', + inputFilePath, + maxDimension + ); + return thumbnail; + } finally { + if (createdTempInputFile) { + try { + await ipcRenderer.invoke('remove-temp-file', inputFilePath); + } catch (e) { + logError(e, 'failed to deleteTempFile'); + } + } + } +} diff --git a/src/preload.ts b/src/preload.ts index 504a3016e..03120429b 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -48,7 +48,7 @@ import { } from './api/common'; import { fixHotReloadNext12 } from './utils/preload'; import { isFolder, getDirFiles } from './api/fs'; -import { convertHEIC } from './api/heicConvert'; +import { convertHEIC, generateImageThumbnail } from './api/imageProcessor'; import { setupLogging } from './utils/logging'; import { setupRendererProcessStatsLogger } from './utils/processStats'; import { runFFmpegCmd } from './api/ffmpeg'; @@ -104,4 +104,5 @@ windowObject['ElectronAPIs'] = { getSentryUserID, getAppVersion, runFFmpegCmd, + generateImageThumbnail, }; diff --git a/src/services/heicConverter.ts b/src/services/imageProcessor.ts similarity index 56% rename from src/services/heicConverter.ts rename to src/services/imageProcessor.ts index e1af0a27f..8dd0f5872 100644 --- a/src/services/heicConverter.ts +++ b/src/services/imageProcessor.ts @@ -70,3 +70,59 @@ async function runConvertCommand( Error(`${process.platform} native heic convert not supported yet`); } } + +export async function generateImageThumbnail( + inputFilePath: string, + width: number +): Promise { + let tempOutputFilePath: string; + try { + tempOutputFilePath = await generateTempFilePath('.jpeg'); + + await runThumbnailGenerationCommand( + inputFilePath, + tempOutputFilePath, + width + ); + + if (!existsSync(tempOutputFilePath)) { + throw new Error('heic convert output file not found'); + } + const convertedFileData = new Uint8Array( + await readFile(tempOutputFilePath) + ); + return convertedFileData; + } catch (e) { + logErrorSentry(e, 'ffmpeg run command error'); + throw e; + } finally { + try { + rmSync(tempOutputFilePath, { force: true }); + } catch (e) { + logErrorSentry(e, 'failed to remove tempOutputFile'); + } + } +} + +async function runThumbnailGenerationCommand( + inputFilePath: string, + tempOutputFilePath: string, + maxDimension: number +) { + if (isPlatform('mac')) { + await asyncExec( + `sips -s format jpeg -Z ${maxDimension} ${inputFilePath} --out ${tempOutputFilePath} ` + ); + } else if (isPlatform('linux')) { + await asyncExec( + `${getImageMagickStaticPath()} -define jpeg:size=${ + 2 * maxDimension + }x${2 * maxDimension} ${inputFilePath} -auto-orient + -thumbnail ${maxDimension}x${maxDimension}> -gravity center -unsharp 0x.5 ${tempOutputFilePath}` + ); + } else { + Error( + `${process.platform} native thumbnail generation not supported yet` + ); + } +} diff --git a/src/utils/ipcComms.ts b/src/utils/ipcComms.ts index debec6273..56a9e565e 100644 --- a/src/utils/ipcComms.ts +++ b/src/utils/ipcComms.ts @@ -14,7 +14,10 @@ import { getSentryUserID, logErrorSentry } from '../services/sentry'; import chokidar from 'chokidar'; import path from 'path'; import { getDirFilePaths } from '../services/fs'; -import { convertHEIC } from '../services/heicConverter'; +import { + convertHEIC, + generateImageThumbnail, +} from '../services/imageProcessor'; import { getAppVersion, skipAppVersion, @@ -146,4 +149,8 @@ export default function setupIpcComs( ipcMain.handle('remove-temp-file', (_, tempFilePath: string) => { return deleteTempFile(tempFilePath); }); + + ipcMain.handle('generate-image-thumbnail', (_, fileData, maxDimension) => { + return generateImageThumbnail(fileData, maxDimension); + }); }