input filename is not needed
tested with sips
This commit is contained in:
parent
4461775283
commit
05cd0bcd2c
7 changed files with 51 additions and 42 deletions
|
@ -141,14 +141,18 @@ export const attachIPCHandlers = () => {
|
|||
|
||||
// - Conversion
|
||||
|
||||
ipcMain.handle("convertToJPEG", (_, fileName, imageData) =>
|
||||
convertToJPEG(fileName, imageData),
|
||||
ipcMain.handle("convertToJPEG", (_, imageData: Uint8Array) =>
|
||||
convertToJPEG(imageData),
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
"generateImageThumbnail",
|
||||
(_, inputFile, maxDimension, maxSize) =>
|
||||
generateImageThumbnail(inputFile, maxDimension, maxSize),
|
||||
(
|
||||
_,
|
||||
dataOrPath: Uint8Array | string,
|
||||
maxDimension: number,
|
||||
maxSize: number,
|
||||
) => generateImageThumbnail(dataOrPath, maxDimension, maxSize),
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
|
|
|
@ -3,20 +3,17 @@
|
|||
import { existsSync } from "fs";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "path";
|
||||
import { CustomErrorMessage, ElectronFile } from "../../types/ipc";
|
||||
import { CustomErrorMessage } from "../../types/ipc";
|
||||
import log from "../log";
|
||||
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<Uint8Array> => {
|
||||
const inputFilePath = await makeTempFilePath(fileName);
|
||||
export const convertToJPEG = async (imageData: Uint8Array) => {
|
||||
const inputFilePath = await makeTempFilePath();
|
||||
const outputFilePath = await makeTempFilePath(".jpeg");
|
||||
|
||||
// Construct the command first, it may throw on NotAvailable on win32.
|
||||
// Construct the command first, it may throw NotAvailable on win32.
|
||||
const command = convertToJPEGCommand(inputFilePath, outputFilePath);
|
||||
|
||||
try {
|
||||
|
@ -106,10 +103,28 @@ const IMAGE_MAGICK_THUMBNAIL_GENERATE_COMMAND_TEMPLATE = [
|
|||
];
|
||||
|
||||
export async function generateImageThumbnail(
|
||||
inputFile: File | ElectronFile,
|
||||
dataOrPath: Uint8Array | string,
|
||||
maxDimension: number,
|
||||
maxSize: number,
|
||||
): Promise<Uint8Array> {
|
||||
const inputFilePath = await makeTempFilePath(fileName);
|
||||
const outputFilePath = await makeTempFilePath(".jpeg");
|
||||
|
||||
// Construct the command first, it may throw 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);
|
||||
}
|
||||
}
|
||||
let inputFilePath = null;
|
||||
let createdTempInputFile = null;
|
||||
try {
|
||||
|
|
|
@ -124,20 +124,17 @@ const fsIsDir = (dirPath: string): Promise<boolean> =>
|
|||
|
||||
// - Conversion
|
||||
|
||||
const convertToJPEG = (
|
||||
fileName: string,
|
||||
imageData: Uint8Array,
|
||||
): Promise<Uint8Array> =>
|
||||
ipcRenderer.invoke("convertToJPEG", fileName, imageData);
|
||||
const convertToJPEG = (imageData: Uint8Array): Promise<Uint8Array> =>
|
||||
ipcRenderer.invoke("convertToJPEG", imageData);
|
||||
|
||||
const generateImageThumbnail = (
|
||||
inputFile: File | ElectronFile,
|
||||
dataOrPath: Uint8Array | string,
|
||||
maxDimension: number,
|
||||
maxSize: number,
|
||||
): Promise<Uint8Array> =>
|
||||
ipcRenderer.invoke(
|
||||
"generateImageThumbnail",
|
||||
inputFile,
|
||||
dataOrPath,
|
||||
maxDimension,
|
||||
maxSize,
|
||||
);
|
||||
|
|
|
@ -59,15 +59,12 @@ export const generateThumbnail = async (
|
|||
const thumbnail =
|
||||
fileTypeInfo.fileType === FILE_TYPE.IMAGE
|
||||
? await generateImageThumbnail(blob, fileTypeInfo)
|
||||
: await generateVideoThumbnail(blob, fileTypeInfo);
|
||||
: await generateVideoThumbnail(blob);
|
||||
|
||||
if (thumbnail.length == 0) throw new Error("Empty thumbnail");
|
||||
return { thumbnail, hasStaticThumbnail: false };
|
||||
} catch (e) {
|
||||
log.error(
|
||||
`Failed to generate thumbnail for format ${fileTypeInfo.exactType}`,
|
||||
e,
|
||||
);
|
||||
log.error(`Failed to generate ${fileTypeInfo.exactType} thumbnail`, e);
|
||||
return { thumbnail: fallbackThumbnail(), hasStaticThumbnail: true };
|
||||
}
|
||||
};
|
||||
|
|
|
@ -292,14 +292,12 @@ export const getRenderableImage = async (fileName: string, imageBlob: Blob) => {
|
|||
return imageBlob;
|
||||
}
|
||||
|
||||
let jpegBlob: Blob | undefined;
|
||||
|
||||
const available = !moduleState.isNativeJPEGConversionNotAvailable;
|
||||
if (isElectron() && available && isSupportedRawFormat(exactType)) {
|
||||
// If we're running in our desktop app, see if our Node.js layer can
|
||||
// convert this into a JPEG using native tools for us.
|
||||
try {
|
||||
jpegBlob = await nativeConvertToJPEG(fileName, imageBlob);
|
||||
return await nativeConvertToJPEG(imageBlob);
|
||||
} catch (e) {
|
||||
if (e.message == CustomErrorMessage.NotAvailable) {
|
||||
moduleState.isNativeJPEGConversionNotAvailable = true;
|
||||
|
@ -309,12 +307,12 @@ export const getRenderableImage = async (fileName: string, imageBlob: Blob) => {
|
|||
}
|
||||
}
|
||||
|
||||
if (!jpegBlob && isFileHEIC(exactType)) {
|
||||
if (!isFileHEIC(exactType)) {
|
||||
// If it is an HEIC file, use our web HEIC converter.
|
||||
jpegBlob = await heicToJPEG(imageBlob);
|
||||
return await heicToJPEG(imageBlob);
|
||||
}
|
||||
|
||||
return jpegBlob;
|
||||
return undefined;
|
||||
} catch (e) {
|
||||
log.error(
|
||||
`Failed to get renderable image for ${JSON.stringify(fileTypeInfo ?? fileName)}`,
|
||||
|
@ -324,7 +322,7 @@ export const getRenderableImage = async (fileName: string, imageBlob: Blob) => {
|
|||
}
|
||||
};
|
||||
|
||||
const nativeConvertToJPEG = async (fileName: string, imageBlob: Blob) => {
|
||||
const nativeConvertToJPEG = async (imageBlob: Blob) => {
|
||||
const startTime = Date.now();
|
||||
const imageData = new Uint8Array(await imageBlob.arrayBuffer());
|
||||
const electron = globalThis.electron;
|
||||
|
@ -332,8 +330,8 @@ const nativeConvertToJPEG = async (fileName: string, imageBlob: Blob) => {
|
|||
// the main thread since workers don't have access to the `window` (and
|
||||
// thus, to the `window.electron`) object.
|
||||
const jpegData = electron
|
||||
? await electron.convertToJPEG(fileName, imageData)
|
||||
: await workerBridge.convertToJPEG(fileName, imageData);
|
||||
? await electron.convertToJPEG(imageData)
|
||||
: await workerBridge.convertToJPEG(imageData);
|
||||
log.debug(() => `Native JPEG conversion took ${Date.now() - startTime} ms`);
|
||||
return new Blob([jpegData]);
|
||||
};
|
||||
|
|
|
@ -204,14 +204,10 @@ export interface Electron {
|
|||
* yet possible, this function will throw an error with the
|
||||
* {@link CustomErrorMessage.NotAvailable} message.
|
||||
*
|
||||
* @param fileName The name of the file whose data we're being given.
|
||||
* @param imageData The raw image data (the contents of the image file).
|
||||
* @returns JPEG data of the converted image.
|
||||
*/
|
||||
convertToJPEG: (
|
||||
fileName: string,
|
||||
imageData: Uint8Array,
|
||||
) => Promise<Uint8Array>;
|
||||
convertToJPEG: (imageData: Uint8Array) => Promise<Uint8Array>;
|
||||
|
||||
/**
|
||||
* Generate a JPEG thumbnail for the given image.
|
||||
|
@ -224,14 +220,16 @@ export interface Electron {
|
|||
* not yet possible, this function will throw an error with the
|
||||
* {@link CustomErrorMessage.NotAvailable} message.
|
||||
*
|
||||
* @param inputFile The file whose thumbnail we want.
|
||||
* @param dataOrPath The data-of or path-to the image whose thumbnail we
|
||||
* want.
|
||||
* @param maxDimension The maximum width or height of the generated
|
||||
* thumbnail.
|
||||
* @param maxSize Maximum size (in bytes) of the generated thumbnail.
|
||||
*
|
||||
* @returns JPEG data of the generated thumbnail.
|
||||
*/
|
||||
generateImageThumbnail: (
|
||||
inputFile: File | ElectronFile,
|
||||
dataOrPath: Uint8Array | string,
|
||||
maxDimension: number,
|
||||
maxSize: number,
|
||||
) => Promise<Uint8Array>;
|
||||
|
|
|
@ -44,8 +44,8 @@ const workerBridge = {
|
|||
logToDisk,
|
||||
// Needed by ML worker
|
||||
getAuthToken: () => ensureLocalUser().then((user) => user.token),
|
||||
convertToJPEG: (fileName: string, imageData: Uint8Array) =>
|
||||
ensureElectron().convertToJPEG(fileName, imageData),
|
||||
convertToJPEG: (imageData: Uint8Array) =>
|
||||
ensureElectron().convertToJPEG(imageData),
|
||||
detectFaces: (input: Float32Array) => ensureElectron().detectFaces(input),
|
||||
faceEmbedding: (input: Float32Array) =>
|
||||
ensureElectron().faceEmbedding(input),
|
||||
|
|
Loading…
Add table
Reference in a new issue