From e1faa965b8dd6e9b8f5b7f5d5343602e903acc7a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 20 Apr 2024 17:28:05 +0530 Subject: [PATCH] Remove layer of abstraction --- desktop/src/main/services/convert.ts | 157 ++++++++++----------------- 1 file changed, 59 insertions(+), 98 deletions(-) diff --git a/desktop/src/main/services/convert.ts b/desktop/src/main/services/convert.ts index d30ebb99d..7f38a86ea 100644 --- a/desktop/src/main/services/convert.ts +++ b/desktop/src/main/services/convert.ts @@ -1,3 +1,5 @@ +/** @file Image conversions */ + import { existsSync } from "fs"; import fs from "node:fs/promises"; import path from "path"; @@ -7,6 +9,62 @@ import { writeStream } from "../stream"; import { execAsync, isDev } from "../utils-electron"; import { deleteTempFile, makeTempFilePath } from "../utils-temp"; +export const convertToJPEG = async ( + fileName: string, + imageData: Uint8Array, +): Promise => { + const inputFilePath = await makeTempFilePath(fileName); + const outputFilePath = await makeTempFilePath("output.jpeg"); + + // Construct the command first, it may throw on NotAvailable on win32. + const command = convertToJPEGCommand(inputFilePath, outputFilePath); + + try { + await fs.writeFile(inputFilePath, imageData); + await execAsync(command); + return new Uint8Array(await fs.readFile(outputFilePath)); + } finally { + try { + deleteTempFile(outputFilePath); + deleteTempFile(inputFilePath); + } catch (e) { + log.error("Ignoring error when cleaning up temp files", e); + } + } +}; + +const convertToJPEGCommand = ( + inputFilePath: string, + outputFilePath: string, +) => { + switch (process.platform) { + case "darwin": + return [ + "sips", + "-s", + "format", + "jpeg", + inputFilePath, + "--out", + outputFilePath, + ]; + case "linux": + return [ + imageMagickPath(), + inputFilePath, + "-quality", + "100%", + outputFilePath, + ]; + default: // "win32" + throw new Error(CustomErrorMessage.NotAvailable); + } +}; + +/** Path to the Linux image-magick executable bundled with our app */ +const imageMagickPath = () => + path.join(isDev ? "build" : process.resourcesPath, "image-magick"); + const IMAGE_MAGICK_PLACEHOLDER = "IMAGE_MAGICK"; const MAX_DIMENSION_PLACEHOLDER = "MAX_DIMENSION"; const SAMPLE_SIZE_PLACEHOLDER = "SAMPLE_SIZE"; @@ -17,16 +75,6 @@ const QUALITY_PLACEHOLDER = "QUALITY"; const MAX_QUALITY = 70; const MIN_QUALITY = 50; -const SIPS_HEIC_CONVERT_COMMAND_TEMPLATE = [ - "sips", - "-s", - "format", - "jpeg", - INPUT_PATH_PLACEHOLDER, - "--out", - OUTPUT_PATH_PLACEHOLDER, -]; - const SIPS_THUMBNAIL_GENERATE_COMMAND_TEMPLATE = [ "sips", "-s", @@ -42,14 +90,6 @@ const SIPS_THUMBNAIL_GENERATE_COMMAND_TEMPLATE = [ OUTPUT_PATH_PLACEHOLDER, ]; -const IMAGEMAGICK_HEIC_CONVERT_COMMAND_TEMPLATE = [ - IMAGE_MAGICK_PLACEHOLDER, - INPUT_PATH_PLACEHOLDER, - "-quality", - "100%", - OUTPUT_PATH_PLACEHOLDER, -]; - const IMAGE_MAGICK_THUMBNAIL_GENERATE_COMMAND_TEMPLATE = [ IMAGE_MAGICK_PLACEHOLDER, INPUT_PATH_PLACEHOLDER, @@ -65,85 +105,6 @@ const IMAGE_MAGICK_THUMBNAIL_GENERATE_COMMAND_TEMPLATE = [ OUTPUT_PATH_PLACEHOLDER, ]; -const imageMagickStaticPath = () => - path.join(isDev ? "build" : process.resourcesPath, "image-magick"); - -export const convertToJPEG = async ( - fileName: string, - imageData: Uint8Array, -): Promise => { - if (process.platform == "win32") - throw new Error(CustomErrorMessage.NotAvailable); - - let tempInputFilePath: string; - let tempOutputFilePath: string; - try { - tempInputFilePath = await makeTempFilePath(fileName); - tempOutputFilePath = await makeTempFilePath("output.jpeg"); - - await fs.writeFile(tempInputFilePath, imageData); - - await execAsync( - constructConvertCommand(tempInputFilePath, tempOutputFilePath), - ); - - return new Uint8Array(await fs.readFile(tempOutputFilePath)); - } catch (e) { - log.error("Failed to convert HEIC", e); - throw e; - } finally { - try { - await fs.rm(tempInputFilePath, { force: true }); - } catch (e) { - log.error(`Failed to remove tempInputFile ${tempInputFilePath}`, e); - } - try { - await fs.rm(tempOutputFilePath, { force: true }); - } catch (e) { - log.error( - `Failed to remove tempOutputFile ${tempOutputFilePath}`, - e, - ); - } - } -}; - -function constructConvertCommand( - tempInputFilePath: string, - tempOutputFilePath: string, -) { - let convertCmd: string[]; - if (process.platform == "darwin") { - convertCmd = SIPS_HEIC_CONVERT_COMMAND_TEMPLATE.map((cmdPart) => { - if (cmdPart === INPUT_PATH_PLACEHOLDER) { - return tempInputFilePath; - } - if (cmdPart === OUTPUT_PATH_PLACEHOLDER) { - return tempOutputFilePath; - } - return cmdPart; - }); - } else if (process.platform == "linux") { - convertCmd = IMAGEMAGICK_HEIC_CONVERT_COMMAND_TEMPLATE.map( - (cmdPart) => { - if (cmdPart === IMAGE_MAGICK_PLACEHOLDER) { - return imageMagickStaticPath(); - } - if (cmdPart === INPUT_PATH_PLACEHOLDER) { - return tempInputFilePath; - } - if (cmdPart === OUTPUT_PATH_PLACEHOLDER) { - return tempOutputFilePath; - } - return cmdPart; - }, - ); - } else { - throw new Error(`Unsupported OS ${process.platform}`); - } - return convertCmd; -} - export async function generateImageThumbnail( inputFile: File | ElectronFile, maxDimension: number, @@ -248,7 +209,7 @@ function constructThumbnailGenerationCommand( thumbnailGenerationCmd = IMAGE_MAGICK_THUMBNAIL_GENERATE_COMMAND_TEMPLATE.map((cmdPart) => { if (cmdPart === IMAGE_MAGICK_PLACEHOLDER) { - return imageMagickStaticPath(); + return imageMagickPath(); } if (cmdPart === INPUT_PATH_PLACEHOLDER) { return inputFilePath;