This commit is contained in:
Manav Rathi 2024-04-30 09:55:45 +05:30
parent 77fe4f9f03
commit e9bf26e421
No known key found for this signature in database
5 changed files with 97 additions and 42 deletions

View file

@ -1,9 +1,14 @@
import pathToFfmpeg from "ffmpeg-static";
import fs from "node:fs/promises";
import type { ZipEntry } from "../../types/ipc";
import log from "../log";
import { withTimeout } from "../utils";
import { execAsync } from "../utils-electron";
import { deleteTempFile, makeTempFilePath } from "../utils-temp";
import {
deleteTempFile,
makeFileForDataOrPathOrZipEntry,
makeTempFilePath,
} from "../utils-temp";
/* Duplicated in the web app's code (used by the WASM FFmpeg implementation). */
const ffmpegPathPlaceholder = "FFMPEG";
@ -39,28 +44,24 @@ const outputPathPlaceholder = "OUTPUT";
*/
export const ffmpegExec = async (
command: string[],
dataOrPath: Uint8Array | string,
dataOrPathOrZipEntry: Uint8Array | string | ZipEntry,
outputFileExtension: string,
timeoutMS: number,
): Promise<Uint8Array> => {
// TODO (MR): This currently copies files for both input and output. This
// needs to be tested extremely large video files when invoked downstream of
// `convertToMP4` in the web code.
// TODO (MR): This currently copies files for both input (when
// dataOrPathOrZipEntry is data) and output. This needs to be tested
// extremely large video files when invoked downstream of `convertToMP4` in
// the web code.
let inputFilePath: string;
let isInputFileTemporary: boolean;
if (dataOrPath instanceof Uint8Array) {
inputFilePath = await makeTempFilePath();
isInputFileTemporary = true;
} else {
inputFilePath = dataOrPath;
isInputFileTemporary = false;
}
const {
path: inputFilePath,
isFileTemporary: isInputFileTemporary,
writeToTemporaryFile: writeToTemporaryInputFile,
} = await makeFileForDataOrPathOrZipEntry(dataOrPathOrZipEntry);
const outputFilePath = await makeTempFilePath(outputFileExtension);
try {
if (dataOrPath instanceof Uint8Array)
await fs.writeFile(inputFilePath, dataOrPath);
await writeToTemporaryInputFile();
const cmd = substitutePlaceholders(
command,

View file

@ -1,12 +1,15 @@
/** @file Image format conversions and thumbnail generation */
import StreamZip from "node-stream-zip";
import fs from "node:fs/promises";
import path from "path";
import { CustomErrorMessage, type ZipEntry } from "../../types/ipc";
import log from "../log";
import { execAsync, isDev } from "../utils-electron";
import { deleteTempFile, makeTempFilePath } from "../utils-temp";
import {
deleteTempFile,
makeFileForDataOrPathOrZipEntry,
makeTempFilePath,
} from "../utils-temp";
export const convertToJPEG = async (imageData: Uint8Array) => {
const inputFilePath = await makeTempFilePath();
@ -68,28 +71,11 @@ export const generateImageThumbnail = async (
maxDimension: number,
maxSize: number,
): Promise<Uint8Array> => {
let inputFilePath: string;
let isInputFileTemporary: boolean;
let writeToTemporaryInputFile = async () => {};
if (typeof dataOrPathOrZipEntry == "string") {
inputFilePath = dataOrPathOrZipEntry;
isInputFileTemporary = false;
} else {
inputFilePath = await makeTempFilePath();
isInputFileTemporary = true;
if (dataOrPathOrZipEntry instanceof Uint8Array) {
writeToTemporaryInputFile = async () => {
await fs.writeFile(inputFilePath, dataOrPathOrZipEntry);
};
} else {
writeToTemporaryInputFile = async () => {
const [zipPath, entryName] = dataOrPathOrZipEntry;
const zip = new StreamZip.async({ file: zipPath });
await zip.extract(entryName, inputFilePath);
zip.close();
};
}
}
const {
path: inputFilePath,
isFileTemporary: isInputFileTemporary,
writeToTemporaryFile: writeToTemporaryInputFile,
} = await makeFileForDataOrPathOrZipEntry(dataOrPathOrZipEntry);
const outputFilePath = await makeTempFilePath("jpeg");
@ -103,7 +89,7 @@ export const generateImageThumbnail = async (
);
try {
writeToTemporaryInputFile();
await writeToTemporaryInputFile();
let thumbnail: Uint8Array;
do {

View file

@ -21,6 +21,8 @@ export const listZipEntries = async (zipPath: string): Promise<ZipEntry[]> => {
}
}
zip.close();
return entryNames.map((entryName) => [zipPath, entryName]);
};
@ -34,7 +36,9 @@ export const pathOrZipEntrySize = async (
const [zipPath, entryName] = pathOrZipEntry;
const zip = new StreamZip.async({ file: zipPath });
const entry = await zip.entry(entryName);
return entry.size;
const size = entry.size;
zip.close();
return size;
}
};
@ -110,6 +114,8 @@ export const getElectronFilesFromGoogleZip = async (filePath: string) => {
}
}
zip.close();
return files;
};

View file

@ -100,6 +100,7 @@ const handleReadZip = async (zipPath: string, zipEntryPath: string) => {
const zip = new StreamZip.async({ file: zipPath });
const entry = await zip.entry(zipEntryPath);
const stream = await zip.stream(entry);
// TODO(MR): when to call zip.close()
return new Response(Readable.toWeb(new Readable(stream)), {
headers: {

View file

@ -1,7 +1,9 @@
import { app } from "electron/main";
import StreamZip from "node-stream-zip";
import { existsSync } from "node:fs";
import fs from "node:fs/promises";
import path from "path";
import type { ZipEntry } from "../types/ipc";
/**
* Our very own directory within the system temp directory. Go crazy, but
@ -61,3 +63,62 @@ export const deleteTempFile = async (tempFilePath: string) => {
throw new Error(`Attempting to delete a non-temp file ${tempFilePath}`);
await fs.rm(tempFilePath, { force: true });
};
/** The result of {@link makeFileForDataOrPathOrZipEntry}. */
interface FileForDataOrPathOrZipEntry {
/** The path to the file (possibly temporary) */
path: string;
/**
* `true` if {@link path} points to a temporary file which should be deleted
* once we are done processing.
*/
isFileTemporary: boolean;
/**
* If set, this'll be a function that can be called to actually write the
* contents of the source `Uint8Array | string | ZipEntry` into the file at
* {@link path}.
*
* It will be undefined if the source is already a path since nothing needs
* to be written in that case. In the other two cases this function will
* write the data or zip entry into the file at {@link path}.
*/
writeToTemporaryFile?: () => Promise<void>;
}
/**
* Return the path to a file, a boolean indicating if this is a temporary path
* that needs to be deleted after processing, and a function to write the given
* {@link dataOrPathOrZipEntry} into that temporary file if needed.
*
* @param dataOrPathOrZipEntry The contents of the file, or the path to an
* existing file, or a (path to a zip file, name of an entry within that zip
* file) tuple.
*/
export const makeFileForDataOrPathOrZipEntry = async (
dataOrPathOrZipEntry: Uint8Array | string | ZipEntry,
): Promise<FileForDataOrPathOrZipEntry> => {
let path: string;
let isFileTemporary: boolean;
let writeToTemporaryFile: () => Promise<void> | undefined;
if (typeof dataOrPathOrZipEntry == "string") {
path = dataOrPathOrZipEntry;
isFileTemporary = false;
} else {
path = await makeTempFilePath();
isFileTemporary = true;
if (dataOrPathOrZipEntry instanceof Uint8Array) {
writeToTemporaryFile = () =>
fs.writeFile(path, dataOrPathOrZipEntry);
} else {
writeToTemporaryFile = async () => {
const [zipPath, entryName] = dataOrPathOrZipEntry;
const zip = new StreamZip.async({ file: zipPath });
await zip.extract(entryName, path);
zip.close();
};
}
}
return { path, isFileTemporary, writeToTemporaryFile };
};