Expectation

This commit is contained in:
Manav Rathi 2024-04-21 10:26:17 +05:30
parent 36ccd3b202
commit cfced851c6
No known key found for this signature in database
4 changed files with 76 additions and 58 deletions

View file

@ -1,3 +1,3 @@
export const INPUT_PATH_PLACEHOLDER = "INPUT";
export const FFMPEG_PLACEHOLDER = "FFMPEG";
export const OUTPUT_PATH_PLACEHOLDER = "OUTPUT";
export const ffmpegPathPlaceholder = "FFMPEG";
export const inputPathPlaceholder = "INPUT";
export const outputPathPlaceholder = "OUTPUT";

View file

@ -2,9 +2,9 @@ import { ComlinkWorker } from "@/next/worker/comlink-worker";
import { validateAndGetCreationUnixTimeInMicroSeconds } from "@ente/shared/time";
import { Remote } from "comlink";
import {
FFMPEG_PLACEHOLDER,
INPUT_PATH_PLACEHOLDER,
OUTPUT_PATH_PLACEHOLDER,
ffmpegPathPlaceholder,
inputPathPlaceholder,
outputPathPlaceholder,
} from "constants/ffmpeg";
import { NULL_LOCATION } from "constants/upload";
import { ElectronFile, ParsedExtractedMetadata } from "types/upload";
@ -19,16 +19,16 @@ export async function generateVideoThumbnail(
try {
return await ffmpegExec(
[
FFMPEG_PLACEHOLDER,
ffmpegPathPlaceholder,
"-i",
INPUT_PATH_PLACEHOLDER,
inputPathPlaceholder,
"-ss",
`00:00:0${seekTime}`,
"-vframes",
"1",
"-vf",
"scale=-1:720",
OUTPUT_PATH_PLACEHOLDER,
outputPathPlaceholder,
],
file,
"thumb.jpeg",
@ -50,16 +50,16 @@ export async function extractVideoMetadata(file: File | ElectronFile) {
// -f ffmetadata [https://ffmpeg.org/ffmpeg-formats.html#Metadata-1] => dump metadata from media files into a simple UTF-8-encoded INI-like text file
const metadata = await ffmpegExec(
[
FFMPEG_PLACEHOLDER,
ffmpegPathPlaceholder,
"-i",
INPUT_PATH_PLACEHOLDER,
inputPathPlaceholder,
"-c",
"copy",
"-map_metadata",
"0",
"-f",
"ffmetadata",
OUTPUT_PATH_PLACEHOLDER,
outputPathPlaceholder,
],
file,
`metadata.txt`,
@ -137,16 +137,16 @@ function parseCreationTime(creationTime: string) {
export async function convertToMP4(file: File) {
return await ffmpegExec(
[
FFMPEG_PLACEHOLDER,
ffmpegPathPlaceholder,
"-i",
INPUT_PATH_PLACEHOLDER,
inputPathPlaceholder,
"-preset",
"ultrafast",
OUTPUT_PATH_PLACEHOLDER,
outputPathPlaceholder,
],
file,
"output.mp4",
true,
30 * 1000,
);
}
@ -164,21 +164,16 @@ const ffmpegExec = async (
cmd: string[],
inputFile: File | ElectronFile,
outputFilename: string,
dontTimeout?: boolean,
timeoutMS: number = 0,
): Promise<File | ElectronFile> => {
const electron = globalThis.electron;
if (electron) {
return electron.runFFmpegCmd(
cmd,
inputFile,
outputFilename,
dontTimeout,
);
return electron.runFFmpegCmd(cmd, inputFile, outputFilename, timeoutMS);
} else {
return workerFactory
.instance()
.then((worker) =>
worker.run(cmd, inputFile, outputFilename, dontTimeout),
worker.run(cmd, inputFile, outputFilename, timeoutMS),
);
}
};

View file

@ -3,29 +3,34 @@ 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 * as Comlink from "comlink";
import { expose } from "comlink";
import {
FFMPEG_PLACEHOLDER,
INPUT_PATH_PLACEHOLDER,
OUTPUT_PATH_PLACEHOLDER,
ffmpegPathPlaceholder,
inputPathPlaceholder,
outputPathPlaceholder,
} from "constants/ffmpeg";
import { FFmpeg, createFFmpeg } from "ffmpeg-wasm";
import { getUint8ArrayView } from "services/readerService";
export class DedicatedFFmpegWorker {
wasmFFmpeg: WasmFFmpeg;
private wasmFFmpeg: WasmFFmpeg;
constructor() {
this.wasmFFmpeg = new WasmFFmpeg();
}
run(cmd, inputFile, outputFileName, dontTimeout) {
return this.wasmFFmpeg.run(cmd, inputFile, outputFileName, dontTimeout);
/**
* Execute a FFMPEG {@link command}.
*
* This is a sibling of {@link ffmpegExec} in ipc.ts exposed by the desktop
* app. See [Note: FFMPEG in Electron].
*/
run(cmd, inputFile, outputFileName, timeoutMS) {
return this.wasmFFmpeg.run(cmd, inputFile, outputFileName, timeoutMS);
}
}
Comlink.expose(DedicatedFFmpegWorker, self);
const FFMPEG_EXECUTION_WAIT_TIME = 30 * 1000;
expose(DedicatedFFmpegWorker, self);
export class WasmFFmpeg {
private ffmpeg: FFmpeg;
@ -51,24 +56,13 @@ export class WasmFFmpeg {
cmd: string[],
inputFile: File,
outputFileName: string,
dontTimeout = false,
timeoutMS,
) {
const response = this.ffmpegTaskQueue.queueUpRequest(() => {
if (dontTimeout) {
return this.execute(cmd, inputFile, outputFileName);
} else {
return withTimeout<File>(
this.execute(cmd, inputFile, outputFileName),
FFMPEG_EXECUTION_WAIT_TIME,
);
}
});
try {
return await response.promise;
} catch (e) {
log.error("ffmpeg run failed", e);
throw e;
}
const exec = () => this.execute(cmd, inputFile, outputFileName);
const request = this.ffmpegTaskQueue.queueUpRequest(() =>
timeoutMS ? withTimeout<File>(exec(), timeoutMS) : exec(),
);
return await request.promise;
}
private async execute(
@ -91,11 +85,11 @@ export class WasmFFmpeg {
tempOutputFilePath = `${generateTempName(10, outputFileName)}`;
cmd = cmd.map((cmdPart) => {
if (cmdPart === FFMPEG_PLACEHOLDER) {
if (cmdPart === ffmpegPathPlaceholder) {
return "";
} else if (cmdPart === INPUT_PATH_PLACEHOLDER) {
} else if (cmdPart === inputPathPlaceholder) {
return tempInputFilePath;
} else if (cmdPart === OUTPUT_PATH_PLACEHOLDER) {
} else if (cmdPart === outputPathPlaceholder) {
return tempOutputFilePath;
} else {
return cmdPart;

View file

@ -236,11 +236,40 @@ export interface Electron {
maxSize: number,
) => Promise<Uint8Array>;
runFFmpegCmd: (
cmd: string[],
inputFile: File | ElectronFile,
/**
* Execute a FFMPEG {@link command}.
*
* This executes the command using the FFMPEG executable we bundle with our
* desktop app. There is also a FFMPEG WASM implementation that we use when
* running on the web, it also has a sibling function with the same
* parameters. See [Note: FFMPEG in Electron].
*
* @param command An array of strings, each representing one positional
* parameter in the command to execute. Placeholders for the input, output
* and ffmpeg's own path are replaced before executing the command
* (respectively {@link inputPathPlaceholder},
* {@link outputPathPlaceholder}, {@link ffmpegPathPlaceholder}).
*
* @param inputDataOrPath The bytes of the input file, or the path to the
* input file on the user's local disk. In both cases, the data gets
* serialized to a temporary file, and then that path gets substituted in
* the FFMPEG {@link command} by {@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 throw a timeout error if the FFMPEG
* command takes more than the given number of milliseconds.
*
* @returns The contents of the output file produced by the ffmpeg command
* at {@link outputFileName}.
*/
ffmpegExec: (
command: string[],
inputDataOrPath: Uint8Array | string,
outputFileName: string,
dontTimeout?: boolean,
timeoutMS: number,
) => Promise<File>;
// - ML