Desktop side

This commit is contained in:
Manav Rathi 2024-04-22 16:32:04 +05:30
parent 4750caf156
commit 4461775283
No known key found for this signature in database
8 changed files with 42 additions and 58 deletions

View file

@ -156,10 +156,9 @@ export const attachIPCHandlers = () => {
( (
_, _,
command: string[], command: string[],
inputDataOrPath: Uint8Array | string, dataOrPath: Uint8Array | string,
outputFileName: string,
timeoutMS: number, timeoutMS: number,
) => ffmpegExec(command, inputDataOrPath, outputFileName, timeoutMS), ) => ffmpegExec(command, dataOrPath, timeoutMS),
); );
// - ML // - ML

View file

@ -14,7 +14,7 @@ export const convertToJPEG = async (
imageData: Uint8Array, imageData: Uint8Array,
): Promise<Uint8Array> => { ): Promise<Uint8Array> => {
const inputFilePath = await makeTempFilePath(fileName); const inputFilePath = await makeTempFilePath(fileName);
const outputFilePath = await makeTempFilePath("output.jpeg"); const outputFilePath = await makeTempFilePath(".jpeg");
// Construct the command first, it may throw on NotAvailable on win32. // Construct the command first, it may throw on NotAvailable on win32.
const command = convertToJPEGCommand(inputFilePath, outputFilePath); const command = convertToJPEGCommand(inputFilePath, outputFilePath);
@ -150,7 +150,7 @@ async function generateImageThumbnail_(
let tempOutputFilePath: string; let tempOutputFilePath: string;
let quality = MAX_QUALITY; let quality = MAX_QUALITY;
try { try {
tempOutputFilePath = await makeTempFilePath("thumb.jpeg"); tempOutputFilePath = await makeTempFilePath(".jpeg");
let thumbnail: Uint8Array; let thumbnail: Uint8Array;
do { do {
await execAsync( await execAsync(

View file

@ -37,8 +37,7 @@ const outputPathPlaceholder = "OUTPUT";
*/ */
export const ffmpegExec = async ( export const ffmpegExec = async (
command: string[], command: string[],
inputDataOrPath: Uint8Array | string, dataOrPath: Uint8Array | string,
outputFileName: string,
timeoutMS: number, timeoutMS: number,
): Promise<Uint8Array> => { ): Promise<Uint8Array> => {
// TODO (MR): This currently copies files for both input and output. This // TODO (MR): This currently copies files for both input and output. This
@ -47,18 +46,18 @@ export const ffmpegExec = async (
let inputFilePath: string; let inputFilePath: string;
let isInputFileTemporary: boolean; let isInputFileTemporary: boolean;
if (typeof inputDataOrPath == "string") { if (typeof dataOrPath == "string") {
inputFilePath = inputDataOrPath; inputFilePath = dataOrPath;
isInputFileTemporary = false; isInputFileTemporary = false;
} else { } else {
inputFilePath = await makeTempFilePath("input" /* arbitrary */); inputFilePath = await makeTempFilePath(".in");
isInputFileTemporary = true; isInputFileTemporary = true;
await fs.writeFile(inputFilePath, inputDataOrPath); await fs.writeFile(inputFilePath, dataOrPath);
} }
let outputFilePath: string | undefined; let outputFilePath: string | undefined;
try { try {
outputFilePath = await makeTempFilePath(outputFileName); outputFilePath = await makeTempFilePath(".out");
const cmd = substitutePlaceholders( const cmd = substitutePlaceholders(
command, command,

View file

@ -20,7 +20,7 @@ const cachedCLIPImageSession = makeCachedInferenceSession(
); );
export const clipImageEmbedding = async (jpegImageData: Uint8Array) => { export const clipImageEmbedding = async (jpegImageData: Uint8Array) => {
const tempFilePath = await makeTempFilePath(""); const tempFilePath = await makeTempFilePath();
const imageStream = new Response(jpegImageData.buffer).body; const imageStream = new Response(jpegImageData.buffer).body;
await writeStream(tempFilePath, imageStream); await writeStream(tempFilePath, imageStream);
try { try {

View file

@ -25,20 +25,21 @@ const randomPrefix = () => {
}; };
/** /**
* Return the path to a temporary file with the given {@link formatSuffix}. * Return the path to a temporary file with the given {@link suffix}.
* *
* The function returns the path to a file in the system temp directory (in an * The function returns the path to a file in the system temp directory (in an
* Ente specific folder therin) with a random prefix and the given * Ente specific folder therin) with a random prefix and an (optional)
* {@link formatSuffix}. It ensures that there is no existing file with the same * {@link suffix}.
* name already. *
* It ensures that there is no existing file with the same name already.
* *
* Use {@link deleteTempFile} to remove this file when you're done. * Use {@link deleteTempFile} to remove this file when you're done.
*/ */
export const makeTempFilePath = async (formatSuffix: string) => { export const makeTempFilePath = async (suffix?: string) => {
const tempDir = await enteTempDirPath(); const tempDir = await enteTempDirPath();
let result: string; let result: string;
do { do {
result = path.join(tempDir, randomPrefix() + "-" + formatSuffix); result = path.join(tempDir, `${randomPrefix()}${suffix ?? ""}`);
} while (existsSync(result)); } while (existsSync(result));
return result; return result;
}; };

View file

@ -144,17 +144,10 @@ const generateImageThumbnail = (
const ffmpegExec = ( const ffmpegExec = (
command: string[], command: string[],
inputDataOrPath: Uint8Array | string, dataOrPath: Uint8Array | string,
outputFileName: string,
timeoutMS: number, timeoutMS: number,
): Promise<Uint8Array> => ): Promise<Uint8Array> =>
ipcRenderer.invoke( ipcRenderer.invoke("ffmpegExec", command, dataOrPath, timeoutMS);
"ffmpegExec",
command,
inputDataOrPath,
outputFileName,
timeoutMS,
);
// - ML // - ML

View file

@ -36,7 +36,6 @@ export const generateVideoThumbnail = async (blob: Blob) => {
outputPathPlaceholder, outputPathPlaceholder,
], ],
blob, blob,
"thumb.jpeg",
); );
try { try {
@ -161,27 +160,25 @@ export async function convertToMP4(file: File) {
* Run the given FFmpeg command. * Run the given FFmpeg command.
* *
* If we're running in the context of our desktop app, use the FFmpeg binary we * If we're running in the context of our desktop app, use the FFmpeg binary we
* bundle with our desktop app to run the command. Otherwise fallback to using * bundle with our desktop app to run the command. Otherwise fallback to using a
* the wasm FFmpeg we link to from our web app in a web worker. * wasm FFmpeg in a web worker.
* *
* As a rough ballpark, the native FFmpeg integration in the desktop app is * As a rough ballpark, currently the native FFmpeg integration in the desktop
* 10-20x faster than the wasm one currently. See: [Note: FFmpeg in Electron]. * app is 10-20x faster than the wasm one. See: [Note: FFmpeg in Electron].
*/ */
const ffmpegExec = async ( const ffmpegExec = async (
command: string[], command: string[],
blob: Blob, blob: Blob,
outputFileName: string,
timeoutMs: number = 0, timeoutMs: number = 0,
): Promise<Uint8Array> => { ) => {
const electron = globalThis.electron; const electron = globalThis.electron;
if (electron) if (electron) {
return electron.ffmpegExec(command, blob, outputFileName, timeoutMs); const data = new Uint8Array(await blob.arrayBuffer());
else return await electron.ffmpegExec(command, data, timeoutMs);
return workerFactory } else {
.lazy() const worker = await workerFactory.lazy()
.then((worker) => return await worker.exec(command, blob, timeoutMs);
worker.exec(command, blob, outputFileName, timeoutMs), }
);
}; };
const ffmpegExec2 = async ( const ffmpegExec2 = async (

View file

@ -237,11 +237,11 @@ export interface Electron {
) => Promise<Uint8Array>; ) => Promise<Uint8Array>;
/** /**
* Execute a ffmpeg {@link command}. * Execute a FFmpeg {@link command} on the given {@link dataOrPath}.
* *
* This executes the command using the ffmpeg executable we bundle with our * This executes the command using a FFmpeg executable we bundle with our
* desktop app. There is also a ffmpeg wasm implementation that we use when * desktop app. We also have a wasm FFmpeg wasm implementation that we use
* running on the web, it also has a sibling function with the same * when running on the web, which has a sibling function with the same
* parameters. See [Note: ffmpeg in Electron]. * parameters. See [Note: ffmpeg in Electron].
* *
* @param command An array of strings, each representing one positional * @param command An array of strings, each representing one positional
@ -250,25 +250,20 @@ export interface Electron {
* (respectively {@link inputPathPlaceholder}, * (respectively {@link inputPathPlaceholder},
* {@link outputPathPlaceholder}, {@link ffmpegPathPlaceholder}). * {@link outputPathPlaceholder}, {@link ffmpegPathPlaceholder}).
* *
* @param inputDataOrPath The bytes of the input file, or the path to the * @param dataOrPath The bytes of the input file, or the path to the input
* input file on the user's local disk. In both cases, the data gets * file on the user's local disk. In both cases, the data gets serialized to
* serialized to a temporary file, and then that path gets substituted in * a temporary file, and then that path gets substituted in the FFmpeg
* the ffmpeg {@link command} by {@link inputPathPlaceholder}. * {@link command} in lieu of {@link inputPathPlaceholder}.
*
* @param outputFileName The name of the file we instruct ffmpeg to produce
* when giving it the given {@link command}. The contents of this file get
* returned as the result.
* *
* @param timeoutMS If non-zero, then abort and throw a timeout error if the * @param timeoutMS If non-zero, then abort and throw a timeout error if the
* ffmpeg command takes more than the given number of milliseconds. * ffmpeg command takes more than the given number of milliseconds.
* *
* @returns The contents of the output file produced by the ffmpeg command * @returns The contents of the output file produced by the ffmpeg command
* at {@link outputFileName}. * (specified as {@link outputPathPlaceholder} in {@link command}).
*/ */
ffmpegExec: ( ffmpegExec: (
command: string[], command: string[],
inputDataOrPath: Uint8Array | string, dataOrPath: Uint8Array | string,
outputFileName: string,
timeoutMS: number, timeoutMS: number,
) => Promise<Uint8Array>; ) => Promise<Uint8Array>;