Desktop side
This commit is contained in:
parent
4750caf156
commit
4461775283
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue