Blob
This commit is contained in:
parent
bb094743f3
commit
4750caf156
5 changed files with 49 additions and 88 deletions
|
@ -13,17 +13,14 @@ const enteTempDirPath = async () => {
|
|||
return result;
|
||||
};
|
||||
|
||||
const randomPrefix = (length: number) => {
|
||||
const CHARACTERS =
|
||||
/** Generate a random string suitable for being used as a file name prefix */
|
||||
const randomPrefix = () => {
|
||||
const alphabet =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
let result = "";
|
||||
const charactersLength = CHARACTERS.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += CHARACTERS.charAt(
|
||||
Math.floor(Math.random() * charactersLength),
|
||||
);
|
||||
}
|
||||
for (let i = 0; i < 10; i++)
|
||||
result += alphabet[Math.floor(Math.random() * alphabet.length)];
|
||||
return result;
|
||||
};
|
||||
|
||||
|
@ -41,7 +38,7 @@ export const makeTempFilePath = async (formatSuffix: string) => {
|
|||
const tempDir = await enteTempDirPath();
|
||||
let result: string;
|
||||
do {
|
||||
result = path.join(tempDir, randomPrefix(10) + "-" + formatSuffix);
|
||||
result = path.join(tempDir, randomPrefix() + "-" + formatSuffix);
|
||||
} while (existsSync(result));
|
||||
return result;
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ElectronFile, type DesktopFilePath } from "@/next/types/file";
|
||||
import { ElectronFile } from "@/next/types/file";
|
||||
import { ComlinkWorker } from "@/next/worker/comlink-worker";
|
||||
import { validateAndGetCreationUnixTimeInMicroSeconds } from "@ente/shared/time";
|
||||
import { Remote } from "comlink";
|
||||
|
@ -17,12 +17,10 @@ import { type DedicatedFFmpegWorker } from "worker/ffmpeg.worker";
|
|||
* This function is called during upload, when we need to generate thumbnails
|
||||
* for the new files that the user is adding.
|
||||
*
|
||||
* @param fileOrPath The input video file or a path to it.
|
||||
* @returns JPEG data for the generated thumbnail.
|
||||
* @param blob The input video blob.
|
||||
* @returns JPEG data of the generated thumbnail.
|
||||
*/
|
||||
export const generateVideoThumbnail = async (
|
||||
fileOrPath: File | DesktopFilePath,
|
||||
): Promise<Uint8Array> => {
|
||||
export const generateVideoThumbnail = async (blob: Blob) => {
|
||||
const thumbnailAtTime = (seekTime: number) =>
|
||||
ffmpegExec(
|
||||
[
|
||||
|
@ -37,7 +35,7 @@ export const generateVideoThumbnail = async (
|
|||
"scale=-1:720",
|
||||
outputPathPlaceholder,
|
||||
],
|
||||
fileOrPath,
|
||||
blob,
|
||||
"thumb.jpeg",
|
||||
);
|
||||
|
||||
|
@ -171,20 +169,19 @@ export async function convertToMP4(file: File) {
|
|||
*/
|
||||
const ffmpegExec = async (
|
||||
command: string[],
|
||||
fileOrPath: File | DesktopFilePath,
|
||||
blob: Blob,
|
||||
outputFileName: string,
|
||||
timeoutMs: number = 0,
|
||||
): Promise<Uint8Array> => {
|
||||
if (fileOrPath instanceof File) {
|
||||
const electron = globalThis.electron;
|
||||
if (electron)
|
||||
return electron.ffmpegExec(command, blob, outputFileName, timeoutMs);
|
||||
else
|
||||
return workerFactory
|
||||
.lazy()
|
||||
.then((worker) =>
|
||||
worker.exec(command, fileOrPath, outputFileName, timeoutMs),
|
||||
worker.exec(command, blob, outputFileName, timeoutMs),
|
||||
);
|
||||
} else {
|
||||
const { path, electron } = fileOrPath;
|
||||
return electron.ffmpegExec(command, path, outputFileName, timeoutMs);
|
||||
}
|
||||
};
|
||||
|
||||
const ffmpegExec2 = async (
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import log from "@/next/log";
|
||||
import { ElectronFile } from "@/next/types/file";
|
||||
import { CustomErrorMessage, type Electron } from "@/next/types/ipc";
|
||||
import { withTimeout } from "@ente/shared/utils";
|
||||
import { FILE_TYPE } from "constants/file";
|
||||
|
@ -84,16 +83,11 @@ const generateImageThumbnail = async (
|
|||
blob: Blob,
|
||||
fileTypeInfo: FileTypeInfo,
|
||||
) => {
|
||||
let jpegData: Uint8Array | undefined;
|
||||
|
||||
const electron = globalThis.electron;
|
||||
const available = !moduleState.isNativeThumbnailCreationNotAvailable;
|
||||
if (electron && available) {
|
||||
// If we're running in our desktop app, try to make the thumbnail using
|
||||
// the native tools available there-in, it'll be faster than doing it on
|
||||
// the web layer.
|
||||
try {
|
||||
jpegData = await generateImageThumbnailInElectron(electron, file);
|
||||
return await generateImageThumbnailInElectron(electron, file);
|
||||
} catch (e) {
|
||||
if (e.message == CustomErrorMessage.NotAvailable) {
|
||||
moduleState.isNativeThumbnailCreationNotAvailable = true;
|
||||
|
@ -103,15 +97,12 @@ const generateImageThumbnail = async (
|
|||
}
|
||||
}
|
||||
|
||||
if (!jpegData) {
|
||||
jpegData = await generateImageThumbnailUsingCanvas(blob, fileTypeInfo);
|
||||
}
|
||||
return jpegData;
|
||||
return await generateImageThumbnailUsingCanvas(blob, fileTypeInfo);
|
||||
};
|
||||
|
||||
const generateImageThumbnailInElectron = async (
|
||||
electron: Electron,
|
||||
inputFile: File | ElectronFile,
|
||||
blob: Blob,
|
||||
): Promise<Uint8Array> => {
|
||||
const startTime = Date.now();
|
||||
const jpegData = await electron.generateImageThumbnail(
|
||||
|
@ -255,7 +246,7 @@ const compressedJPEGData = async (canvas: HTMLCanvasElement) => {
|
|||
percentageSizeDiff(blob.size, prevSize) >= 10
|
||||
);
|
||||
|
||||
return blob;
|
||||
return new Uint8Array(await blob.arrayBuffer());
|
||||
};
|
||||
|
||||
const percentageSizeDiff = (
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { nameAndExtension } from "@/next/file";
|
||||
import log from "@/next/log";
|
||||
import { withTimeout } from "@ente/shared/utils";
|
||||
import QueueProcessor from "@ente/shared/utils/queueProcessor";
|
||||
import { generateTempName } from "@ente/shared/utils/temp";
|
||||
import { expose } from "comlink";
|
||||
import {
|
||||
ffmpegPathPlaceholder,
|
||||
|
@ -23,21 +21,15 @@ export class DedicatedFFmpegWorker {
|
|||
}
|
||||
|
||||
/**
|
||||
* Execute a FFmpeg {@link command}.
|
||||
* Execute a FFmpeg {@link command} on {@link blob}.
|
||||
*
|
||||
* This is a sibling of {@link ffmpegExec} in ipc.ts exposed by the desktop
|
||||
* app. See [Note: FFmpeg in Electron].
|
||||
* This is a sibling of {@link ffmpegExec} exposed by the desktop app in
|
||||
* `ipc.ts`. See [Note: FFmpeg in Electron].
|
||||
*/
|
||||
async exec(
|
||||
command: string[],
|
||||
inputFile: File,
|
||||
outputFileName: string,
|
||||
timeoutMs,
|
||||
): Promise<Uint8Array> {
|
||||
async exec(command: string[], blob: Blob, timeoutMs): Promise<Uint8Array> {
|
||||
if (!this.ffmpeg.isLoaded()) await this.ffmpeg.load();
|
||||
|
||||
const go = () =>
|
||||
ffmpegExec(this.ffmpeg, command, inputFile, outputFileName);
|
||||
const go = () => ffmpegExec(this.ffmpeg, command, blob);
|
||||
|
||||
const request = this.ffmpegTaskQueue.queueUpRequest(() =>
|
||||
timeoutMs ? withTimeout(go(), timeoutMs) : go(),
|
||||
|
@ -49,48 +41,46 @@ export class DedicatedFFmpegWorker {
|
|||
|
||||
expose(DedicatedFFmpegWorker, self);
|
||||
|
||||
const ffmpegExec = async (
|
||||
ffmpeg: FFmpeg,
|
||||
command: string[],
|
||||
inputFile: File,
|
||||
outputFileName: string,
|
||||
) => {
|
||||
const [, extension] = nameAndExtension(inputFile.name);
|
||||
const tempNameSuffix = extension ? `input.${extension}` : "input";
|
||||
const tempInputFilePath = `${generateTempName(10, tempNameSuffix)}`;
|
||||
const tempOutputFilePath = `${generateTempName(10, outputFileName)}`;
|
||||
const ffmpegExec = async (ffmpeg: FFmpeg, command: string[], blob: Blob) => {
|
||||
const inputPath = `${randomPrefix()}.in`;
|
||||
const outputPath = `${randomPrefix()}.out`;
|
||||
|
||||
const cmd = substitutePlaceholders(
|
||||
command,
|
||||
tempInputFilePath,
|
||||
tempOutputFilePath,
|
||||
);
|
||||
const cmd = substitutePlaceholders(command, inputPath, outputPath);
|
||||
|
||||
const inputData = new Uint8Array(await blob.arrayBuffer());
|
||||
|
||||
try {
|
||||
ffmpeg.FS(
|
||||
"writeFile",
|
||||
tempInputFilePath,
|
||||
new Uint8Array(await inputFile.arrayBuffer()),
|
||||
);
|
||||
ffmpeg.FS("writeFile", inputPath, inputData);
|
||||
|
||||
log.info(`Running FFmpeg (wasm) command ${cmd}`);
|
||||
await ffmpeg.run(...cmd);
|
||||
|
||||
return ffmpeg.FS("readFile", tempOutputFilePath);
|
||||
return ffmpeg.FS("readFile", outputPath);
|
||||
} finally {
|
||||
try {
|
||||
ffmpeg.FS("unlink", tempInputFilePath);
|
||||
ffmpeg.FS("unlink", inputPath);
|
||||
} catch (e) {
|
||||
log.error("Failed to remove input ${tempInputFilePath}", e);
|
||||
log.error(`Failed to remove input ${inputPath}`, e);
|
||||
}
|
||||
try {
|
||||
ffmpeg.FS("unlink", tempOutputFilePath);
|
||||
ffmpeg.FS("unlink", outputPath);
|
||||
} catch (e) {
|
||||
log.error("Failed to remove output ${tempOutputFilePath}", e);
|
||||
log.error(`Failed to remove output ${outputPath}`, e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Generate a random string suitable for being used as a file name prefix */
|
||||
const randomPrefix = () => {
|
||||
const alphabet =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
let result = "";
|
||||
for (let i = 0; i < 10; i++)
|
||||
result += alphabet[Math.floor(Math.random() * alphabet.length)];
|
||||
return result;
|
||||
};
|
||||
|
||||
const substitutePlaceholders = (
|
||||
command: string[],
|
||||
inputFilePath: string,
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
const CHARACTERS =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
export function generateTempName(length: number, suffix: string) {
|
||||
let tempName = "";
|
||||
|
||||
const charactersLength = CHARACTERS.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
tempName += CHARACTERS.charAt(
|
||||
Math.floor(Math.random() * charactersLength),
|
||||
);
|
||||
}
|
||||
return `${tempName}-${suffix}`;
|
||||
}
|
Loading…
Add table
Reference in a new issue