mirror of
https://github.com/ente-io/ente.git
synced 2025-08-05 05:24:26 +00:00
Fit in
This commit is contained in:
parent
e0d41f6024
commit
4509d8c23f
@ -9,7 +9,7 @@ import {
|
||||
makeTempFilePath,
|
||||
} from "../utils/temp";
|
||||
|
||||
/* Duplicated in the web app's code (used by the WASM FFmpeg implementation). */
|
||||
/* Ditto in the web app's code (used by the WASM FFmpeg invocation). */
|
||||
const ffmpegPathPlaceholder = "FFMPEG";
|
||||
const inputPathPlaceholder = "INPUT";
|
||||
const outputPathPlaceholder = "OUTPUT";
|
||||
|
@ -1,3 +1,5 @@
|
||||
/* Ditto in the desktop app's code (used by the native FFmpeg invocation). */
|
||||
|
||||
export const ffmpegPathPlaceholder = "FFMPEG";
|
||||
export const inputPathPlaceholder = "INPUT";
|
||||
export const outputPathPlaceholder = "OUTPUT";
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { ensureElectron } from "@/base/electron";
|
||||
import log from "@/base/log";
|
||||
import type { Electron } from "@/base/types/ipc";
|
||||
import { ComlinkWorker } from "@/base/worker/comlink-worker";
|
||||
import {
|
||||
readConvertToMP4Done,
|
||||
readConvertToMP4Stream,
|
||||
@ -13,13 +12,12 @@ import {
|
||||
type DesktopUploadItem,
|
||||
type UploadItem,
|
||||
} from "@/new/photos/services/upload/types";
|
||||
import type { Remote } from "comlink";
|
||||
import {
|
||||
ffmpegPathPlaceholder,
|
||||
inputPathPlaceholder,
|
||||
outputPathPlaceholder,
|
||||
} from "./constants";
|
||||
import type { DedicatedFFmpegWorker } from "./worker";
|
||||
import { ffmpegExecWeb } from "./web";
|
||||
|
||||
/**
|
||||
* Generate a thumbnail for the given video using a wasm FFmpeg running in a web
|
||||
@ -236,21 +234,6 @@ const parseFFMetadataDate = (s: string | undefined) => {
|
||||
return d;
|
||||
};
|
||||
|
||||
/**
|
||||
* Run the given FFmpeg command using a wasm FFmpeg running in a web worker.
|
||||
*
|
||||
* As a rough ballpark, currently the native FFmpeg integration in the desktop
|
||||
* app is 10-20x faster than the wasm one. See: [Note: FFmpeg in Electron].
|
||||
*/
|
||||
const ffmpegExecWeb = async (
|
||||
command: string[],
|
||||
blob: Blob,
|
||||
outputFileExtension: string,
|
||||
) => {
|
||||
const worker = await workerFactory.lazy();
|
||||
return await worker.exec(command, blob, outputFileExtension);
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a video from a format that is not supported in the browser to MP4.
|
||||
*
|
||||
@ -285,21 +268,3 @@ const convertToMP4Native = async (electron: Electron, blob: Blob) => {
|
||||
await readConvertToMP4Done(electron, token);
|
||||
return mp4Blob;
|
||||
};
|
||||
|
||||
/** Lazily create a singleton instance of our worker */
|
||||
class WorkerFactory {
|
||||
private instance: Promise<Remote<DedicatedFFmpegWorker>> | undefined;
|
||||
|
||||
private createComlinkWorker = () =>
|
||||
new ComlinkWorker<typeof DedicatedFFmpegWorker>(
|
||||
"ffmpeg-worker",
|
||||
new Worker(new URL("worker.ts", import.meta.url)),
|
||||
);
|
||||
|
||||
async lazy() {
|
||||
if (!this.instance) this.instance = this.createComlinkWorker().remote;
|
||||
return this.instance;
|
||||
}
|
||||
}
|
||||
|
||||
const workerFactory = new WorkerFactory();
|
||||
|
119
web/packages/new/photos/services/ffmpeg/web.ts
Normal file
119
web/packages/new/photos/services/ffmpeg/web.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import log from "@/base/log";
|
||||
import { FFmpeg } from "@ffmpeg/ffmpeg";
|
||||
import {
|
||||
ffmpegPathPlaceholder,
|
||||
inputPathPlaceholder,
|
||||
outputPathPlaceholder,
|
||||
} from "./constants";
|
||||
|
||||
/** Lazily initialized and loaded FFmpeg instance. */
|
||||
let _ffmpeg: Promise<FFmpeg> | undefined;
|
||||
|
||||
/**
|
||||
* Return the shared {@link FFmpeg} instance, lazily creating and loading it if
|
||||
* needed.
|
||||
*/
|
||||
const ffmpegLazy = (): Promise<FFmpeg> => (_ffmpeg ??= createFFmpeg());
|
||||
|
||||
const createFFmpeg = async () => {
|
||||
const ffmpeg = new FFmpeg();
|
||||
// This loads @ffmpeg/core from its CDN:
|
||||
// https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd/ffmpeg-core.js
|
||||
// https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd/ffmpeg-core.wasm
|
||||
await ffmpeg.load();
|
||||
return ffmpeg;
|
||||
};
|
||||
|
||||
/**
|
||||
* Run the given FFmpeg command using a wasm FFmpeg running in a web worker.
|
||||
*
|
||||
* This is a sibling of {@link ffmpegExec} exposed by the desktop app in `ipc.ts`.
|
||||
* As a rough ballpark, currently the native FFmpeg integration in the desktop
|
||||
* app is 10-20x faster than the wasm one. See: [Note: FFmpeg in Electron].
|
||||
*
|
||||
* @param command The FFmpeg command to execute.
|
||||
*
|
||||
* @param blob The input data on which to run the command, provided as a blob.
|
||||
*
|
||||
* @param outputFileExtension The extension of the (temporary) output file which
|
||||
* will be generated by the command.
|
||||
*
|
||||
* @returns The contents of the output file generated as a result of executing
|
||||
* {@link command} on {@link blob}.
|
||||
*/
|
||||
export const ffmpegExecWeb = async (
|
||||
command: string[],
|
||||
blob: Blob,
|
||||
outputFileExtension: string,
|
||||
): Promise<Uint8Array> =>
|
||||
ffmpegExec(await ffmpegLazy(), command, outputFileExtension, blob);
|
||||
|
||||
const ffmpegExec = async (
|
||||
ffmpeg: FFmpeg,
|
||||
command: string[],
|
||||
outputFileExtension: string,
|
||||
blob: Blob,
|
||||
) => {
|
||||
const inputPath = randomPrefix();
|
||||
const outputSuffix = outputFileExtension ? "." + outputFileExtension : "";
|
||||
const outputPath = randomPrefix() + outputSuffix;
|
||||
|
||||
const cmd = substitutePlaceholders(command, inputPath, outputPath);
|
||||
|
||||
const inputData = new Uint8Array(await blob.arrayBuffer());
|
||||
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
|
||||
await ffmpeg.writeFile(inputPath, inputData);
|
||||
await ffmpeg.exec(cmd);
|
||||
|
||||
const result = await ffmpeg.readFile(outputPath);
|
||||
if (typeof result == "string") throw new Error("Expected binary data");
|
||||
|
||||
const ms = Date.now() - startTime;
|
||||
log.debug(() => `[wasm] ffmpeg ${cmd.join(" ")} (${ms} ms)`);
|
||||
return result;
|
||||
} finally {
|
||||
try {
|
||||
await ffmpeg.deleteFile(inputPath);
|
||||
} catch (e) {
|
||||
log.error(`Failed to remove input ${inputPath}`, e);
|
||||
}
|
||||
try {
|
||||
await ffmpeg.deleteFile(outputPath);
|
||||
} catch (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,
|
||||
outputFilePath: string,
|
||||
) =>
|
||||
command
|
||||
.map((segment) => {
|
||||
if (segment == ffmpegPathPlaceholder) {
|
||||
return undefined;
|
||||
} else if (segment == inputPathPlaceholder) {
|
||||
return inputFilePath;
|
||||
} else if (segment == outputPathPlaceholder) {
|
||||
return outputFilePath;
|
||||
} else {
|
||||
return segment;
|
||||
}
|
||||
})
|
||||
.filter((s) => s !== undefined);
|
Loading…
x
Reference in New Issue
Block a user