Extract
This commit is contained in:
parent
77fe4f9f03
commit
e9bf26e421
5 changed files with 97 additions and 42 deletions
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue