Update
This commit is contained in:
2
public/ffmpeg_packages/core-mt/.gitignore
vendored
Executable file
2
public/ffmpeg_packages/core-mt/.gitignore
vendored
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
dist/
|
||||||
|
types/
|
||||||
46
public/ffmpeg_packages/core-mt/package.json
Executable file
46
public/ffmpeg_packages/core-mt/package.json
Executable file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "@ffmpeg/core-mt",
|
||||||
|
"version": "0.12.10",
|
||||||
|
"description": "FFmpeg WebAssembly version (multi thread)",
|
||||||
|
"main": "./dist/umd/ffmpeg-core.js",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/esm/ffmpeg-core.js",
|
||||||
|
"require": "./dist/umd/ffmpeg-core.js"
|
||||||
|
},
|
||||||
|
"./wasm": {
|
||||||
|
"import": "./dist/esm/ffmpeg-core.wasm",
|
||||||
|
"require": "./dist/umd/ffmpeg-core.wasm"
|
||||||
|
},
|
||||||
|
"./worker": {
|
||||||
|
"import": "./dist/esm/ffmpeg-core.worker.js",
|
||||||
|
"require": "./dist/umd/ffmpeg-core.worker.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/ffmpegwasm/ffmpeg.wasm.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"ffmpeg",
|
||||||
|
"WebAssembly",
|
||||||
|
"video",
|
||||||
|
"audio",
|
||||||
|
"transcode"
|
||||||
|
],
|
||||||
|
"author": "Jerome Wu <jeromewus@gmail.com>",
|
||||||
|
"license": "GPL-2.0-or-later",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/ffmpegwasm/ffmpeg.wasm/issues"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.x"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/ffmpegwasm/ffmpeg.wasm#readme",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
||||||
2
public/ffmpeg_packages/core/.gitignore
vendored
Executable file
2
public/ffmpeg_packages/core/.gitignore
vendored
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
dist/
|
||||||
|
types/
|
||||||
42
public/ffmpeg_packages/core/package.json
Executable file
42
public/ffmpeg_packages/core/package.json
Executable file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "@ffmpeg/core",
|
||||||
|
"version": "0.12.10",
|
||||||
|
"description": "FFmpeg WebAssembly version (single thread)",
|
||||||
|
"main": "./dist/umd/ffmpeg-core.js",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/esm/ffmpeg-core.js",
|
||||||
|
"require": "./dist/umd/ffmpeg-core.js"
|
||||||
|
},
|
||||||
|
"./wasm": {
|
||||||
|
"import": "./dist/esm/ffmpeg-core.wasm",
|
||||||
|
"require": "./dist/umd/ffmpeg-core.wasm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/ffmpegwasm/ffmpeg.wasm.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"ffmpeg",
|
||||||
|
"WebAssembly",
|
||||||
|
"video",
|
||||||
|
"audio",
|
||||||
|
"transcode"
|
||||||
|
],
|
||||||
|
"author": "Jerome Wu <jeromewus@gmail.com>",
|
||||||
|
"license": "GPL-2.0-or-later",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/ffmpegwasm/ffmpeg.wasm/issues"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.x"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/ffmpegwasm/ffmpeg.wasm#readme",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
public/ffmpeg_packages/ffmpeg/.eslintignore
Executable file
1
public/ffmpeg_packages/ffmpeg/.eslintignore
Executable file
@@ -0,0 +1 @@
|
|||||||
|
.eslintrc.cjs
|
||||||
13
public/ffmpeg_packages/ffmpeg/.eslintrc.cjs
Executable file
13
public/ffmpeg_packages/ffmpeg/.eslintrc.cjs
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||||
|
],
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
parserOptions: {
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
project: ["./tsconfig.json", "./src/worker/tsconfig.json"],
|
||||||
|
},
|
||||||
|
plugins: ["@typescript-eslint"],
|
||||||
|
};
|
||||||
2
public/ffmpeg_packages/ffmpeg/.gitignore
vendored
Executable file
2
public/ffmpeg_packages/ffmpeg/.gitignore
vendored
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
dist/
|
||||||
|
docs/
|
||||||
68
public/ffmpeg_packages/ffmpeg/package.json
Executable file
68
public/ffmpeg_packages/ffmpeg/package.json
Executable file
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"name": "@ffmpeg/ffmpeg",
|
||||||
|
"version": "0.12.15",
|
||||||
|
"description": "FFmpeg WebAssembly version for browser",
|
||||||
|
"main": "./dist/umd/ffmpeg.js",
|
||||||
|
"types": "./dist/esm/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/esm/index.d.ts",
|
||||||
|
"node": "./dist/esm/empty.mjs",
|
||||||
|
"default": {
|
||||||
|
"import": "./dist/esm/index.js",
|
||||||
|
"require": "./dist/umd/ffmpeg.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"./worker": {
|
||||||
|
"types": "./dist/esm/worker.d.ts",
|
||||||
|
"default": "./dist/esm/worker.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "webpack -w --mode development",
|
||||||
|
"lint": "eslint src",
|
||||||
|
"clean": "rimraf dist",
|
||||||
|
"build:esm": "tsc -p tsconfig.esm.json",
|
||||||
|
"build:umd": "webpack",
|
||||||
|
"build": "npm run clean && npm run build:esm && npm run build:umd",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist",
|
||||||
|
"types/ffmpeg.d.ts"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/ffmpegwasm/ffmpeg.wasm.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"ffmpeg",
|
||||||
|
"WebAssembly",
|
||||||
|
"video",
|
||||||
|
"audio",
|
||||||
|
"transcode"
|
||||||
|
],
|
||||||
|
"author": "Jerome Wu <jeromewus@gmail.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/ffmpegwasm/ffmpeg.wasm/issues"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.x"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/ffmpegwasm/ffmpeg.wasm#readme",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||||
|
"@typescript-eslint/parser": "^6.1.0",
|
||||||
|
"eslint": "^8.45.0",
|
||||||
|
"rimraf": "^5.0.1",
|
||||||
|
"typescript": "^5.1.6",
|
||||||
|
"webpack-cli": "^5.1.4"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@ffmpeg/types": "^0.12.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
488
public/ffmpeg_packages/ffmpeg/src/classes.ts
Executable file
488
public/ffmpeg_packages/ffmpeg/src/classes.ts
Executable file
@@ -0,0 +1,488 @@
|
|||||||
|
import { FFMessageType } from "./const.js";
|
||||||
|
import {
|
||||||
|
CallbackData,
|
||||||
|
Callbacks,
|
||||||
|
FSNode,
|
||||||
|
FFMessageEventCallback,
|
||||||
|
FFMessageLoadConfig,
|
||||||
|
OK,
|
||||||
|
IsFirst,
|
||||||
|
LogEvent,
|
||||||
|
Message,
|
||||||
|
ProgressEvent,
|
||||||
|
LogEventCallback,
|
||||||
|
ProgressEventCallback,
|
||||||
|
FileData,
|
||||||
|
FFFSType,
|
||||||
|
FFFSMountOptions,
|
||||||
|
FFFSPath,
|
||||||
|
} from "./types.js";
|
||||||
|
import { getMessageID } from "./utils.js";
|
||||||
|
import { ERROR_TERMINATED, ERROR_NOT_LOADED } from "./errors.js";
|
||||||
|
|
||||||
|
type FFMessageOptions = {
|
||||||
|
signal?: AbortSignal;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides APIs to interact with ffmpeg web worker.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const ffmpeg = new FFmpeg();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export class FFmpeg {
|
||||||
|
#worker: Worker | null = null;
|
||||||
|
/**
|
||||||
|
* #resolves and #rejects tracks Promise resolves and rejects to
|
||||||
|
* be called when we receive message from web worker.
|
||||||
|
*/
|
||||||
|
#resolves: Callbacks = {};
|
||||||
|
#rejects: Callbacks = {};
|
||||||
|
|
||||||
|
#logEventCallbacks: LogEventCallback[] = [];
|
||||||
|
#progressEventCallbacks: ProgressEventCallback[] = [];
|
||||||
|
|
||||||
|
public loaded = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* register worker message event handlers.
|
||||||
|
*/
|
||||||
|
#registerHandlers = () => {
|
||||||
|
if (this.#worker) {
|
||||||
|
this.#worker.onmessage = ({
|
||||||
|
data: { id, type, data },
|
||||||
|
}: FFMessageEventCallback) => {
|
||||||
|
switch (type) {
|
||||||
|
case FFMessageType.LOAD:
|
||||||
|
this.loaded = true;
|
||||||
|
this.#resolves[id](data);
|
||||||
|
break;
|
||||||
|
case FFMessageType.MOUNT:
|
||||||
|
case FFMessageType.UNMOUNT:
|
||||||
|
case FFMessageType.EXEC:
|
||||||
|
case FFMessageType.FFPROBE:
|
||||||
|
case FFMessageType.WRITE_FILE:
|
||||||
|
case FFMessageType.READ_FILE:
|
||||||
|
case FFMessageType.DELETE_FILE:
|
||||||
|
case FFMessageType.RENAME:
|
||||||
|
case FFMessageType.CREATE_DIR:
|
||||||
|
case FFMessageType.LIST_DIR:
|
||||||
|
case FFMessageType.DELETE_DIR:
|
||||||
|
this.#resolves[id](data);
|
||||||
|
break;
|
||||||
|
case FFMessageType.LOG:
|
||||||
|
this.#logEventCallbacks.forEach((f) => f(data as LogEvent));
|
||||||
|
break;
|
||||||
|
case FFMessageType.PROGRESS:
|
||||||
|
this.#progressEventCallbacks.forEach((f) =>
|
||||||
|
f(data as ProgressEvent)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case FFMessageType.ERROR:
|
||||||
|
this.#rejects[id](data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
delete this.#resolves[id];
|
||||||
|
delete this.#rejects[id];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic function to send messages to web worker.
|
||||||
|
*/
|
||||||
|
#send = (
|
||||||
|
{ type, data }: Message,
|
||||||
|
trans: Transferable[] = [],
|
||||||
|
signal?: AbortSignal
|
||||||
|
): Promise<CallbackData> => {
|
||||||
|
if (!this.#worker) {
|
||||||
|
return Promise.reject(ERROR_NOT_LOADED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const id = getMessageID();
|
||||||
|
this.#worker && this.#worker.postMessage({ id, type, data }, trans);
|
||||||
|
this.#resolves[id] = resolve;
|
||||||
|
this.#rejects[id] = reject;
|
||||||
|
|
||||||
|
signal?.addEventListener(
|
||||||
|
"abort",
|
||||||
|
() => {
|
||||||
|
reject(new DOMException(`Message # ${id} was aborted`, "AbortError"));
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listen to log or prgress events from `ffmpeg.exec()`.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* ffmpeg.on("log", ({ type, message }) => {
|
||||||
|
* // ...
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* ffmpeg.on("progress", ({ progress, time }) => {
|
||||||
|
* // ...
|
||||||
|
* })
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* - log includes output to stdout and stderr.
|
||||||
|
* - The progress events are accurate only when the length of
|
||||||
|
* input and output video/audio file are the same.
|
||||||
|
*
|
||||||
|
* @category FFmpeg
|
||||||
|
*/
|
||||||
|
public on(event: "log", callback: LogEventCallback): void;
|
||||||
|
public on(event: "progress", callback: ProgressEventCallback): void;
|
||||||
|
public on(
|
||||||
|
event: "log" | "progress",
|
||||||
|
callback: LogEventCallback | ProgressEventCallback
|
||||||
|
) {
|
||||||
|
if (event === "log") {
|
||||||
|
this.#logEventCallbacks.push(callback as LogEventCallback);
|
||||||
|
} else if (event === "progress") {
|
||||||
|
this.#progressEventCallbacks.push(callback as ProgressEventCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlisten to log or progress events from `ffmpeg.exec()`.
|
||||||
|
*
|
||||||
|
* @category FFmpeg
|
||||||
|
*/
|
||||||
|
public off(event: "log", callback: LogEventCallback): void;
|
||||||
|
public off(event: "progress", callback: ProgressEventCallback): void;
|
||||||
|
public off(
|
||||||
|
event: "log" | "progress",
|
||||||
|
callback: LogEventCallback | ProgressEventCallback
|
||||||
|
) {
|
||||||
|
if (event === "log") {
|
||||||
|
this.#logEventCallbacks = this.#logEventCallbacks.filter(
|
||||||
|
(f) => f !== callback
|
||||||
|
);
|
||||||
|
} else if (event === "progress") {
|
||||||
|
this.#progressEventCallbacks = this.#progressEventCallbacks.filter(
|
||||||
|
(f) => f !== callback
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads ffmpeg-core inside web worker. It is required to call this method first
|
||||||
|
* as it initializes WebAssembly and other essential variables.
|
||||||
|
*
|
||||||
|
* @category FFmpeg
|
||||||
|
* @returns `true` if ffmpeg core is loaded for the first time.
|
||||||
|
*/
|
||||||
|
public load = (
|
||||||
|
{ classWorkerURL, ...config }: FFMessageLoadConfig = {},
|
||||||
|
{ signal }: FFMessageOptions = {}
|
||||||
|
): Promise<IsFirst> => {
|
||||||
|
if (!this.#worker) {
|
||||||
|
this.#worker = classWorkerURL ?
|
||||||
|
new Worker(new URL(classWorkerURL, import.meta.url), {
|
||||||
|
type: "module",
|
||||||
|
}) :
|
||||||
|
// We need to duplicated the code here to enable webpack
|
||||||
|
// to bundle worker.js here.
|
||||||
|
new Worker(new URL("./worker.js", import.meta.url), {
|
||||||
|
type: "module",
|
||||||
|
});
|
||||||
|
this.#registerHandlers();
|
||||||
|
}
|
||||||
|
return this.#send(
|
||||||
|
{
|
||||||
|
type: FFMessageType.LOAD,
|
||||||
|
data: config,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
signal
|
||||||
|
) as Promise<IsFirst>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute ffmpeg command.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* To avoid common I/O issues, ["-nostdin", "-y"] are prepended to the args
|
||||||
|
* by default.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const ffmpeg = new FFmpeg();
|
||||||
|
* await ffmpeg.load();
|
||||||
|
* await ffmpeg.writeFile("video.avi", ...);
|
||||||
|
* // ffmpeg -i video.avi video.mp4
|
||||||
|
* await ffmpeg.exec(["-i", "video.avi", "video.mp4"]);
|
||||||
|
* const data = ffmpeg.readFile("video.mp4");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @returns `0` if no error, `!= 0` if timeout (1) or error.
|
||||||
|
* @category FFmpeg
|
||||||
|
*/
|
||||||
|
public exec = (
|
||||||
|
/** ffmpeg command line args */
|
||||||
|
args: string[],
|
||||||
|
/**
|
||||||
|
* milliseconds to wait before stopping the command execution.
|
||||||
|
*
|
||||||
|
* @defaultValue -1
|
||||||
|
*/
|
||||||
|
timeout = -1,
|
||||||
|
{ signal }: FFMessageOptions = {}
|
||||||
|
): Promise<number> =>
|
||||||
|
this.#send(
|
||||||
|
{
|
||||||
|
type: FFMessageType.EXEC,
|
||||||
|
data: { args, timeout },
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
signal
|
||||||
|
) as Promise<number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute ffprobe command.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const ffmpeg = new FFmpeg();
|
||||||
|
* await ffmpeg.load();
|
||||||
|
* await ffmpeg.writeFile("video.avi", ...);
|
||||||
|
* // Getting duration of a video in seconds: ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 video.avi -o output.txt
|
||||||
|
* await ffmpeg.ffprobe(["-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", "video.avi", "-o", "output.txt"]);
|
||||||
|
* const data = ffmpeg.readFile("output.txt");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @returns `0` if no error, `!= 0` if timeout (1) or error.
|
||||||
|
* @category FFmpeg
|
||||||
|
*/
|
||||||
|
public ffprobe = (
|
||||||
|
/** ffprobe command line args */
|
||||||
|
args: string[],
|
||||||
|
/**
|
||||||
|
* milliseconds to wait before stopping the command execution.
|
||||||
|
*
|
||||||
|
* @defaultValue -1
|
||||||
|
*/
|
||||||
|
timeout = -1,
|
||||||
|
{ signal }: FFMessageOptions = {}
|
||||||
|
): Promise<number> =>
|
||||||
|
this.#send(
|
||||||
|
{
|
||||||
|
type: FFMessageType.FFPROBE,
|
||||||
|
data: { args, timeout },
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
signal
|
||||||
|
) as Promise<number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminate all ongoing API calls and terminate web worker.
|
||||||
|
* `FFmpeg.load()` must be called again before calling any other APIs.
|
||||||
|
*
|
||||||
|
* @category FFmpeg
|
||||||
|
*/
|
||||||
|
public terminate = (): void => {
|
||||||
|
const ids = Object.keys(this.#rejects);
|
||||||
|
// rejects all incomplete Promises.
|
||||||
|
for (const id of ids) {
|
||||||
|
this.#rejects[id](ERROR_TERMINATED);
|
||||||
|
delete this.#rejects[id];
|
||||||
|
delete this.#resolves[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#worker) {
|
||||||
|
this.#worker.terminate();
|
||||||
|
this.#worker = null;
|
||||||
|
this.loaded = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write data to ffmpeg.wasm.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const ffmpeg = new FFmpeg();
|
||||||
|
* await ffmpeg.load();
|
||||||
|
* await ffmpeg.writeFile("video.avi", await fetchFile("../video.avi"));
|
||||||
|
* await ffmpeg.writeFile("text.txt", "hello world");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @category File System
|
||||||
|
*/
|
||||||
|
public writeFile = (
|
||||||
|
path: string,
|
||||||
|
data: FileData,
|
||||||
|
{ signal }: FFMessageOptions = {}
|
||||||
|
): Promise<OK> => {
|
||||||
|
const trans: Transferable[] = [];
|
||||||
|
if (data instanceof Uint8Array) {
|
||||||
|
trans.push(data.buffer);
|
||||||
|
}
|
||||||
|
return this.#send(
|
||||||
|
{
|
||||||
|
type: FFMessageType.WRITE_FILE,
|
||||||
|
data: { path, data },
|
||||||
|
},
|
||||||
|
trans,
|
||||||
|
signal
|
||||||
|
) as Promise<OK>;
|
||||||
|
};
|
||||||
|
|
||||||
|
public mount = (fsType: FFFSType, options: FFFSMountOptions, mountPoint: FFFSPath, ): Promise<OK> => {
|
||||||
|
const trans: Transferable[] = [];
|
||||||
|
return this.#send(
|
||||||
|
{
|
||||||
|
type: FFMessageType.MOUNT,
|
||||||
|
data: { fsType, options, mountPoint },
|
||||||
|
},
|
||||||
|
trans
|
||||||
|
) as Promise<OK>;
|
||||||
|
};
|
||||||
|
|
||||||
|
public unmount = (mountPoint: FFFSPath): Promise<OK> => {
|
||||||
|
const trans: Transferable[] = [];
|
||||||
|
return this.#send(
|
||||||
|
{
|
||||||
|
type: FFMessageType.UNMOUNT,
|
||||||
|
data: { mountPoint },
|
||||||
|
},
|
||||||
|
trans
|
||||||
|
) as Promise<OK>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read data from ffmpeg.wasm.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* const ffmpeg = new FFmpeg();
|
||||||
|
* await ffmpeg.load();
|
||||||
|
* const data = await ffmpeg.readFile("video.mp4");
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @category File System
|
||||||
|
*/
|
||||||
|
public readFile = (
|
||||||
|
path: string,
|
||||||
|
/**
|
||||||
|
* File content encoding, supports two encodings:
|
||||||
|
* - utf8: read file as text file, return data in string type.
|
||||||
|
* - binary: read file as binary file, return data in Uint8Array type.
|
||||||
|
*
|
||||||
|
* @defaultValue binary
|
||||||
|
*/
|
||||||
|
encoding = "binary",
|
||||||
|
{ signal }: FFMessageOptions = {}
|
||||||
|
): Promise<FileData> =>
|
||||||
|
this.#send(
|
||||||
|
{
|
||||||
|
type: FFMessageType.READ_FILE,
|
||||||
|
data: { path, encoding },
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
signal
|
||||||
|
) as Promise<FileData>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a file.
|
||||||
|
*
|
||||||
|
* @category File System
|
||||||
|
*/
|
||||||
|
public deleteFile = (
|
||||||
|
path: string,
|
||||||
|
{ signal }: FFMessageOptions = {}
|
||||||
|
): Promise<OK> =>
|
||||||
|
this.#send(
|
||||||
|
{
|
||||||
|
type: FFMessageType.DELETE_FILE,
|
||||||
|
data: { path },
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
signal
|
||||||
|
) as Promise<OK>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rename a file or directory.
|
||||||
|
*
|
||||||
|
* @category File System
|
||||||
|
*/
|
||||||
|
public rename = (
|
||||||
|
oldPath: string,
|
||||||
|
newPath: string,
|
||||||
|
{ signal }: FFMessageOptions = {}
|
||||||
|
): Promise<OK> =>
|
||||||
|
this.#send(
|
||||||
|
{
|
||||||
|
type: FFMessageType.RENAME,
|
||||||
|
data: { oldPath, newPath },
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
signal
|
||||||
|
) as Promise<OK>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a directory.
|
||||||
|
*
|
||||||
|
* @category File System
|
||||||
|
*/
|
||||||
|
public createDir = (
|
||||||
|
path: string,
|
||||||
|
{ signal }: FFMessageOptions = {}
|
||||||
|
): Promise<OK> =>
|
||||||
|
this.#send(
|
||||||
|
{
|
||||||
|
type: FFMessageType.CREATE_DIR,
|
||||||
|
data: { path },
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
signal
|
||||||
|
) as Promise<OK>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List directory contents.
|
||||||
|
*
|
||||||
|
* @category File System
|
||||||
|
*/
|
||||||
|
public listDir = (
|
||||||
|
path: string,
|
||||||
|
{ signal }: FFMessageOptions = {}
|
||||||
|
): Promise<FSNode[]> =>
|
||||||
|
this.#send(
|
||||||
|
{
|
||||||
|
type: FFMessageType.LIST_DIR,
|
||||||
|
data: { path },
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
signal
|
||||||
|
) as Promise<FSNode[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an empty directory.
|
||||||
|
*
|
||||||
|
* @category File System
|
||||||
|
*/
|
||||||
|
public deleteDir = (
|
||||||
|
path: string,
|
||||||
|
{ signal }: FFMessageOptions = {}
|
||||||
|
): Promise<OK> =>
|
||||||
|
this.#send(
|
||||||
|
{
|
||||||
|
type: FFMessageType.DELETE_DIR,
|
||||||
|
data: { path },
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
signal
|
||||||
|
) as Promise<OK>;
|
||||||
|
}
|
||||||
25
public/ffmpeg_packages/ffmpeg/src/const.ts
Executable file
25
public/ffmpeg_packages/ffmpeg/src/const.ts
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
export const MIME_TYPE_JAVASCRIPT = "text/javascript";
|
||||||
|
export const MIME_TYPE_WASM = "application/wasm";
|
||||||
|
|
||||||
|
export const CORE_VERSION = "0.12.10";
|
||||||
|
export const CORE_URL = `https://unpkg.com/@ffmpeg/core@${CORE_VERSION}/dist/umd/ffmpeg-core.js`;
|
||||||
|
|
||||||
|
export enum FFMessageType {
|
||||||
|
LOAD = "LOAD",
|
||||||
|
EXEC = "EXEC",
|
||||||
|
FFPROBE = "FFPROBE",
|
||||||
|
WRITE_FILE = "WRITE_FILE",
|
||||||
|
READ_FILE = "READ_FILE",
|
||||||
|
DELETE_FILE = "DELETE_FILE",
|
||||||
|
RENAME = "RENAME",
|
||||||
|
CREATE_DIR = "CREATE_DIR",
|
||||||
|
LIST_DIR = "LIST_DIR",
|
||||||
|
DELETE_DIR = "DELETE_DIR",
|
||||||
|
ERROR = "ERROR",
|
||||||
|
|
||||||
|
DOWNLOAD = "DOWNLOAD",
|
||||||
|
PROGRESS = "PROGRESS",
|
||||||
|
LOG = "LOG",
|
||||||
|
MOUNT = "MOUNT",
|
||||||
|
UNMOUNT = "UNMOUNT",
|
||||||
|
}
|
||||||
7
public/ffmpeg_packages/ffmpeg/src/empty.mts
Executable file
7
public/ffmpeg_packages/ffmpeg/src/empty.mts
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
// File to be imported in node enviroments
|
||||||
|
|
||||||
|
export class FFmpeg {
|
||||||
|
constructor() {
|
||||||
|
throw new Error("ffmpeg.wasm does not support nodejs");
|
||||||
|
}
|
||||||
|
}
|
||||||
8
public/ffmpeg_packages/ffmpeg/src/errors.ts
Executable file
8
public/ffmpeg_packages/ffmpeg/src/errors.ts
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
export const ERROR_UNKNOWN_MESSAGE_TYPE = new Error("unknown message type");
|
||||||
|
export const ERROR_NOT_LOADED = new Error(
|
||||||
|
"ffmpeg is not loaded, call `await ffmpeg.load()` first"
|
||||||
|
);
|
||||||
|
export const ERROR_TERMINATED = new Error("called FFmpeg.terminate()");
|
||||||
|
export const ERROR_IMPORT_FAILURE = new Error(
|
||||||
|
"failed to import ffmpeg-core.js"
|
||||||
|
);
|
||||||
2
public/ffmpeg_packages/ffmpeg/src/index.ts
Executable file
2
public/ffmpeg_packages/ffmpeg/src/index.ts
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./classes.js";
|
||||||
|
export * from "./types.js";
|
||||||
182
public/ffmpeg_packages/ffmpeg/src/types.ts
Executable file
182
public/ffmpeg_packages/ffmpeg/src/types.ts
Executable file
@@ -0,0 +1,182 @@
|
|||||||
|
export type FFFSPath = string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ffmpeg-core loading configuration.
|
||||||
|
*/
|
||||||
|
export interface FFMessageLoadConfig {
|
||||||
|
/**
|
||||||
|
* `ffmpeg-core.js` URL.
|
||||||
|
*
|
||||||
|
* @defaultValue `https://unpkg.com/@ffmpeg/core@${CORE_VERSION}/dist/umd/ffmpeg-core.js`;
|
||||||
|
*/
|
||||||
|
coreURL?: string;
|
||||||
|
/**
|
||||||
|
* `ffmpeg-core.wasm` URL.
|
||||||
|
*
|
||||||
|
* @defaultValue `https://unpkg.com/@ffmpeg/core@${CORE_VERSION}/dist/umd/ffmpeg-core.wasm`;
|
||||||
|
*/
|
||||||
|
wasmURL?: string;
|
||||||
|
/**
|
||||||
|
* `ffmpeg-core.worker.js` URL. This worker is spawned when using multithread version of ffmpeg-core.
|
||||||
|
*
|
||||||
|
* @ref: https://ffmpegwasm.netlify.app/docs/overview#architecture
|
||||||
|
* @defaultValue `https://unpkg.com/@ffmpeg/core-mt@${CORE_VERSION}/dist/umd/ffmpeg-core.worker.js`;
|
||||||
|
*/
|
||||||
|
workerURL?: string;
|
||||||
|
/**
|
||||||
|
* `ffmpeg.worker.js` URL. This worker is spawned when FFmpeg.load() is called, it is an essential worker and usually you don't need to update this config.
|
||||||
|
*
|
||||||
|
* @ref: https://ffmpegwasm.netlify.app/docs/overview#architecture
|
||||||
|
* @defaultValue `./worker.js`
|
||||||
|
*/
|
||||||
|
classWorkerURL?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FFMessageExecData {
|
||||||
|
args: string[];
|
||||||
|
timeout?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FFMessageWriteFileData {
|
||||||
|
path: FFFSPath;
|
||||||
|
data: FileData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FFMessageReadFileData {
|
||||||
|
path: FFFSPath;
|
||||||
|
encoding: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FFMessageDeleteFileData {
|
||||||
|
path: FFFSPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FFMessageRenameData {
|
||||||
|
oldPath: FFFSPath;
|
||||||
|
newPath: FFFSPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FFMessageCreateDirData {
|
||||||
|
path: FFFSPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FFMessageListDirData {
|
||||||
|
path: FFFSPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @remarks
|
||||||
|
* Only deletes empty directory.
|
||||||
|
*/
|
||||||
|
export interface FFMessageDeleteDirData {
|
||||||
|
path: FFFSPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum FFFSType {
|
||||||
|
MEMFS = "MEMFS",
|
||||||
|
NODEFS = "NODEFS",
|
||||||
|
NODERAWFS = "NODERAWFS",
|
||||||
|
IDBFS = "IDBFS",
|
||||||
|
WORKERFS = "WORKERFS",
|
||||||
|
PROXYFS = "PROXYFS",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WorkerFSFileEntry =
|
||||||
|
| File;
|
||||||
|
|
||||||
|
export interface WorkerFSBlobEntry {
|
||||||
|
name: string;
|
||||||
|
data: Blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkerFSMountData {
|
||||||
|
blobs?: WorkerFSBlobEntry[];
|
||||||
|
files?: WorkerFSFileEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FFFSMountOptions =
|
||||||
|
| WorkerFSMountData;
|
||||||
|
|
||||||
|
export interface FFMessageMountData {
|
||||||
|
fsType: FFFSType;
|
||||||
|
options: FFFSMountOptions;
|
||||||
|
mountPoint: FFFSPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FFMessageUnmountData {
|
||||||
|
mountPoint: FFFSPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FFMessageData =
|
||||||
|
| FFMessageLoadConfig
|
||||||
|
| FFMessageExecData
|
||||||
|
| FFMessageWriteFileData
|
||||||
|
| FFMessageReadFileData
|
||||||
|
| FFMessageDeleteFileData
|
||||||
|
| FFMessageRenameData
|
||||||
|
| FFMessageCreateDirData
|
||||||
|
| FFMessageListDirData
|
||||||
|
| FFMessageDeleteDirData
|
||||||
|
| FFMessageMountData
|
||||||
|
| FFMessageUnmountData;
|
||||||
|
|
||||||
|
export interface Message {
|
||||||
|
type: string;
|
||||||
|
data?: FFMessageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FFMessage extends Message {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FFMessageEvent extends MessageEvent {
|
||||||
|
data: FFMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LogEvent {
|
||||||
|
type: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProgressEvent {
|
||||||
|
progress: number;
|
||||||
|
time: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExitCode = number;
|
||||||
|
export type ErrorMessage = string;
|
||||||
|
export type FileData = Uint8Array | string;
|
||||||
|
export type IsFirst = boolean;
|
||||||
|
export type OK = boolean;
|
||||||
|
|
||||||
|
export interface FSNode {
|
||||||
|
name: string;
|
||||||
|
isDir: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CallbackData =
|
||||||
|
| FileData
|
||||||
|
| ExitCode
|
||||||
|
| ErrorMessage
|
||||||
|
| LogEvent
|
||||||
|
| ProgressEvent
|
||||||
|
| IsFirst
|
||||||
|
| OK // eslint-disable-line
|
||||||
|
| Error
|
||||||
|
| FSNode[]
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
export interface Callbacks {
|
||||||
|
[id: number | string]: (data: CallbackData) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LogEventCallback = (event: LogEvent) => void;
|
||||||
|
export type ProgressEventCallback = (event: ProgressEvent) => void;
|
||||||
|
|
||||||
|
export interface FFMessageEventCallback {
|
||||||
|
data: {
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
data: CallbackData;
|
||||||
|
};
|
||||||
|
}
|
||||||
7
public/ffmpeg_packages/ffmpeg/src/utils.ts
Executable file
7
public/ffmpeg_packages/ffmpeg/src/utils.ts
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Generate an unique message ID.
|
||||||
|
*/
|
||||||
|
export const getMessageID = (() => {
|
||||||
|
let messageID = 0;
|
||||||
|
return () => messageID++;
|
||||||
|
})();
|
||||||
226
public/ffmpeg_packages/ffmpeg/src/worker.ts
Executable file
226
public/ffmpeg_packages/ffmpeg/src/worker.ts
Executable file
@@ -0,0 +1,226 @@
|
|||||||
|
/// <reference no-default-lib="true" />
|
||||||
|
/// <reference lib="esnext" />
|
||||||
|
/// <reference lib="webworker" />
|
||||||
|
|
||||||
|
import type { FFmpegCoreModule, FFmpegCoreModuleFactory } from "@ffmpeg/types";
|
||||||
|
import type {
|
||||||
|
FFMessageEvent,
|
||||||
|
FFMessageLoadConfig,
|
||||||
|
FFMessageExecData,
|
||||||
|
FFMessageWriteFileData,
|
||||||
|
FFMessageReadFileData,
|
||||||
|
FFMessageDeleteFileData,
|
||||||
|
FFMessageRenameData,
|
||||||
|
FFMessageCreateDirData,
|
||||||
|
FFMessageListDirData,
|
||||||
|
FFMessageDeleteDirData,
|
||||||
|
FFMessageMountData,
|
||||||
|
FFMessageUnmountData,
|
||||||
|
CallbackData,
|
||||||
|
IsFirst,
|
||||||
|
OK,
|
||||||
|
ExitCode,
|
||||||
|
FSNode,
|
||||||
|
FileData,
|
||||||
|
} from "./types";
|
||||||
|
import { CORE_URL, FFMessageType } from "./const.js";
|
||||||
|
import {
|
||||||
|
ERROR_UNKNOWN_MESSAGE_TYPE,
|
||||||
|
ERROR_NOT_LOADED,
|
||||||
|
ERROR_IMPORT_FAILURE,
|
||||||
|
} from "./errors.js";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface WorkerGlobalScope {
|
||||||
|
createFFmpegCore: FFmpegCoreModuleFactory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportedFFmpegCoreModuleFactory {
|
||||||
|
default: FFmpegCoreModuleFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
let ffmpeg: FFmpegCoreModule;
|
||||||
|
|
||||||
|
const load = async ({
|
||||||
|
coreURL: _coreURL,
|
||||||
|
wasmURL: _wasmURL,
|
||||||
|
workerURL: _workerURL,
|
||||||
|
}: FFMessageLoadConfig): Promise<IsFirst> => {
|
||||||
|
const first = !ffmpeg;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!_coreURL) _coreURL = CORE_URL;
|
||||||
|
// when web worker type is `classic`.
|
||||||
|
importScripts(_coreURL);
|
||||||
|
} catch {
|
||||||
|
if (!_coreURL || _coreURL === CORE_URL) _coreURL = CORE_URL.replace('/umd/', '/esm/');
|
||||||
|
// when web worker type is `module`.
|
||||||
|
(self as WorkerGlobalScope).createFFmpegCore = (
|
||||||
|
(await import(
|
||||||
|
/* @vite-ignore */ _coreURL
|
||||||
|
)) as ImportedFFmpegCoreModuleFactory
|
||||||
|
).default;
|
||||||
|
|
||||||
|
if (!(self as WorkerGlobalScope).createFFmpegCore) {
|
||||||
|
throw ERROR_IMPORT_FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const coreURL = _coreURL;
|
||||||
|
const wasmURL = _wasmURL ? _wasmURL : _coreURL.replace(/.js$/g, ".wasm");
|
||||||
|
const workerURL = _workerURL
|
||||||
|
? _workerURL
|
||||||
|
: _coreURL.replace(/.js$/g, ".worker.js");
|
||||||
|
|
||||||
|
ffmpeg = await (self as WorkerGlobalScope).createFFmpegCore({
|
||||||
|
// Fix `Overload resolution failed.` when using multi-threaded ffmpeg-core.
|
||||||
|
// Encoded wasmURL and workerURL in the URL as a hack to fix locateFile issue.
|
||||||
|
mainScriptUrlOrBlob: `${coreURL}#${btoa(
|
||||||
|
JSON.stringify({ wasmURL, workerURL })
|
||||||
|
)}`,
|
||||||
|
});
|
||||||
|
ffmpeg.setLogger((data) =>
|
||||||
|
self.postMessage({ type: FFMessageType.LOG, data })
|
||||||
|
);
|
||||||
|
ffmpeg.setProgress((data) =>
|
||||||
|
self.postMessage({
|
||||||
|
type: FFMessageType.PROGRESS,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return first;
|
||||||
|
};
|
||||||
|
|
||||||
|
const exec = ({ args, timeout = -1 }: FFMessageExecData): ExitCode => {
|
||||||
|
ffmpeg.setTimeout(timeout);
|
||||||
|
ffmpeg.exec(...args);
|
||||||
|
const ret = ffmpeg.ret;
|
||||||
|
ffmpeg.reset();
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ffprobe = ({ args, timeout = -1 }: FFMessageExecData): ExitCode => {
|
||||||
|
ffmpeg.setTimeout(timeout);
|
||||||
|
ffmpeg.ffprobe(...args);
|
||||||
|
const ret = ffmpeg.ret;
|
||||||
|
ffmpeg.reset();
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeFile = ({ path, data }: FFMessageWriteFileData): OK => {
|
||||||
|
ffmpeg.FS.writeFile(path, data);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const readFile = ({ path, encoding }: FFMessageReadFileData): FileData =>
|
||||||
|
ffmpeg.FS.readFile(path, { encoding });
|
||||||
|
|
||||||
|
// TODO: check if deletion works.
|
||||||
|
const deleteFile = ({ path }: FFMessageDeleteFileData): OK => {
|
||||||
|
ffmpeg.FS.unlink(path);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const rename = ({ oldPath, newPath }: FFMessageRenameData): OK => {
|
||||||
|
ffmpeg.FS.rename(oldPath, newPath);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: check if creation works.
|
||||||
|
const createDir = ({ path }: FFMessageCreateDirData): OK => {
|
||||||
|
ffmpeg.FS.mkdir(path);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const listDir = ({ path }: FFMessageListDirData): FSNode[] => {
|
||||||
|
const names = ffmpeg.FS.readdir(path);
|
||||||
|
const nodes: FSNode[] = [];
|
||||||
|
for (const name of names) {
|
||||||
|
const stat = ffmpeg.FS.stat(`${path}/${name}`);
|
||||||
|
const isDir = ffmpeg.FS.isDir(stat.mode);
|
||||||
|
nodes.push({ name, isDir });
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: check if deletion works.
|
||||||
|
const deleteDir = ({ path }: FFMessageDeleteDirData): OK => {
|
||||||
|
ffmpeg.FS.rmdir(path);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mount = ({ fsType, options, mountPoint }: FFMessageMountData): OK => {
|
||||||
|
const str = fsType as keyof typeof ffmpeg.FS.filesystems;
|
||||||
|
const fs = ffmpeg.FS.filesystems[str];
|
||||||
|
if (!fs) return false;
|
||||||
|
ffmpeg.FS.mount(fs, options, mountPoint);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const unmount = ({ mountPoint }: FFMessageUnmountData): OK => {
|
||||||
|
ffmpeg.FS.unmount(mountPoint);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.onmessage = async ({
|
||||||
|
data: { id, type, data: _data },
|
||||||
|
}: FFMessageEvent): Promise<void> => {
|
||||||
|
const trans = [];
|
||||||
|
let data: CallbackData;
|
||||||
|
try {
|
||||||
|
if (type !== FFMessageType.LOAD && !ffmpeg) throw ERROR_NOT_LOADED; // eslint-disable-line
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case FFMessageType.LOAD:
|
||||||
|
data = await load(_data as FFMessageLoadConfig);
|
||||||
|
break;
|
||||||
|
case FFMessageType.EXEC:
|
||||||
|
data = exec(_data as FFMessageExecData);
|
||||||
|
break;
|
||||||
|
case FFMessageType.FFPROBE:
|
||||||
|
data = ffprobe(_data as FFMessageExecData);
|
||||||
|
break;
|
||||||
|
case FFMessageType.WRITE_FILE:
|
||||||
|
data = writeFile(_data as FFMessageWriteFileData);
|
||||||
|
break;
|
||||||
|
case FFMessageType.READ_FILE:
|
||||||
|
data = readFile(_data as FFMessageReadFileData);
|
||||||
|
break;
|
||||||
|
case FFMessageType.DELETE_FILE:
|
||||||
|
data = deleteFile(_data as FFMessageDeleteFileData);
|
||||||
|
break;
|
||||||
|
case FFMessageType.RENAME:
|
||||||
|
data = rename(_data as FFMessageRenameData);
|
||||||
|
break;
|
||||||
|
case FFMessageType.CREATE_DIR:
|
||||||
|
data = createDir(_data as FFMessageCreateDirData);
|
||||||
|
break;
|
||||||
|
case FFMessageType.LIST_DIR:
|
||||||
|
data = listDir(_data as FFMessageListDirData);
|
||||||
|
break;
|
||||||
|
case FFMessageType.DELETE_DIR:
|
||||||
|
data = deleteDir(_data as FFMessageDeleteDirData);
|
||||||
|
break;
|
||||||
|
case FFMessageType.MOUNT:
|
||||||
|
data = mount(_data as FFMessageMountData);
|
||||||
|
break;
|
||||||
|
case FFMessageType.UNMOUNT:
|
||||||
|
data = unmount(_data as FFMessageUnmountData);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw ERROR_UNKNOWN_MESSAGE_TYPE;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
self.postMessage({
|
||||||
|
id,
|
||||||
|
type: FFMessageType.ERROR,
|
||||||
|
data: (e as Error).toString(),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data instanceof Uint8Array) {
|
||||||
|
trans.push(data.buffer);
|
||||||
|
}
|
||||||
|
self.postMessage({ id, type, data }, trans);
|
||||||
|
};
|
||||||
10
public/ffmpeg_packages/ffmpeg/tsconfig.esm.json
Executable file
10
public/ffmpeg_packages/ffmpeg/tsconfig.esm.json
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src",
|
||||||
|
"declaration": true,
|
||||||
|
"outDir": "./dist/esm",
|
||||||
|
"target": "esnext",
|
||||||
|
"moduleResolution": "nodenext"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
public/ffmpeg_packages/ffmpeg/tsconfig.json
Executable file
7
public/ffmpeg_packages/ffmpeg/tsconfig.json
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "esnext",
|
||||||
|
"rootDir": "src"
|
||||||
|
}
|
||||||
|
}
|
||||||
19
public/ffmpeg_packages/ffmpeg/webpack.config.js
Executable file
19
public/ffmpeg_packages/ffmpeg/webpack.config.js
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mode: "production",
|
||||||
|
devtool: "source-map",
|
||||||
|
entry: "./dist/esm/index.js",
|
||||||
|
resolve: {
|
||||||
|
extensions: [".js"],
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, "dist/umd"),
|
||||||
|
filename: "ffmpeg.js",
|
||||||
|
library: "FFmpegWASM",
|
||||||
|
libraryTarget: "umd",
|
||||||
|
},
|
||||||
|
stats: {
|
||||||
|
warnings:false
|
||||||
|
}
|
||||||
|
};
|
||||||
32
public/ffmpeg_packages/types/package.json
Executable file
32
public/ffmpeg_packages/types/package.json
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "@ffmpeg/types",
|
||||||
|
"version": "0.12.4",
|
||||||
|
"description": "ffmpeg.wasm types",
|
||||||
|
"types": "types",
|
||||||
|
"files": [
|
||||||
|
"types"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/ffmpegwasm/ffmpeg.wasm.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"ffmpeg",
|
||||||
|
"WebAssembly",
|
||||||
|
"video",
|
||||||
|
"audio",
|
||||||
|
"transcode"
|
||||||
|
],
|
||||||
|
"author": "Jerome Wu <jeromewus@gmail.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/ffmpegwasm/ffmpeg.wasm/issues"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.x"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/ffmpegwasm/ffmpeg.wasm#readme",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
||||||
142
public/ffmpeg_packages/types/types/index.d.ts
vendored
Executable file
142
public/ffmpeg_packages/types/types/index.d.ts
vendored
Executable file
@@ -0,0 +1,142 @@
|
|||||||
|
// TODO: Add lint and test.
|
||||||
|
|
||||||
|
export type Pointer = number;
|
||||||
|
|
||||||
|
export type StringPointer = Pointer;
|
||||||
|
export type StringArrayPointer = Pointer;
|
||||||
|
export type DateString = string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for readFile.
|
||||||
|
*
|
||||||
|
* @see [Emscripten File System API](https://emscripten.org/docs/api_reference/Filesystem-API.html#FS.readFile)
|
||||||
|
* @category File System
|
||||||
|
*/
|
||||||
|
export interface ReadFileOptions {
|
||||||
|
/** encoding of the file, must be `binary` or `utf8` */
|
||||||
|
encdoing: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Describes attributes of a node. (a.k.a file, directory)
|
||||||
|
*
|
||||||
|
* @see [Emscripten File System API](https://emscripten.org/docs/api_reference/Filesystem-API.html#FS.stat)
|
||||||
|
* @category File System
|
||||||
|
*/
|
||||||
|
export interface Stat {
|
||||||
|
dev: number;
|
||||||
|
ino: number;
|
||||||
|
mode: number;
|
||||||
|
nlink: number;
|
||||||
|
uid: number;
|
||||||
|
gid: number;
|
||||||
|
rdev: number;
|
||||||
|
size: number;
|
||||||
|
atime: DateString;
|
||||||
|
mtime: DateString;
|
||||||
|
ctime: DateString;
|
||||||
|
blksize: number;
|
||||||
|
blocks: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FSFilesystemWORKERFS {}
|
||||||
|
|
||||||
|
export interface FSFilesystemMEMFS {}
|
||||||
|
|
||||||
|
export interface FSFilesystems {
|
||||||
|
WORKERFS: FSFilesystemWORKERFS;
|
||||||
|
MEMFS: FSFilesystemMEMFS;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FSFilesystem = FSFilesystemWORKERFS | FSFilesystemMEMFS;
|
||||||
|
|
||||||
|
export interface OptionReadFile {
|
||||||
|
encoding: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkerFSMountConfig {
|
||||||
|
blobs?: {
|
||||||
|
name: string;
|
||||||
|
data: Blob;
|
||||||
|
}[];
|
||||||
|
files?: File[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functions to interact with Emscripten FS library.
|
||||||
|
*
|
||||||
|
* @see [Emscripten File System API](https://emscripten.org/docs/api_reference/Filesystem-API.html)
|
||||||
|
* @category File System
|
||||||
|
*/
|
||||||
|
export interface FS {
|
||||||
|
mkdir: (path: string) => void;
|
||||||
|
rmdir: (path: string) => void;
|
||||||
|
rename: (oldPath: string, newPath: string) => void;
|
||||||
|
writeFile: (path: string, data: Uint8Array | string) => void;
|
||||||
|
readFile: (path: string, opts: OptionReadFile) => Uint8Array | string;
|
||||||
|
readdir: (path: string) => string[];
|
||||||
|
unlink: (path: string) => void;
|
||||||
|
stat: (path: string) => Stat;
|
||||||
|
/** mode is a numeric notation of permission, @see [Numeric Notation](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation) */
|
||||||
|
isFile: (mode: number) => boolean;
|
||||||
|
/** mode is a numeric notation of permission, @see [Numeric Notation](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation) */
|
||||||
|
isDir: (mode: number) => boolean;
|
||||||
|
mount: (
|
||||||
|
fileSystemType: FSFilesystem,
|
||||||
|
data: WorkerFSMountConfig,
|
||||||
|
path: string
|
||||||
|
) => void;
|
||||||
|
unmount: (path: string) => void;
|
||||||
|
filesystems: FSFilesystems;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arguments passed to setLogger callback function.
|
||||||
|
*/
|
||||||
|
export interface Log {
|
||||||
|
/** file descriptor of the log, must be `stdout` or `stderr` */
|
||||||
|
type: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arguments passed to setProgress callback function.
|
||||||
|
*/
|
||||||
|
export interface Progress {
|
||||||
|
/** progress of the operation, interval = [0, 1] */
|
||||||
|
progress: number;
|
||||||
|
/** time of transcoded media in microseconds, ex: if a video is 10 seconds long, when time is 1000000 means 1 second of the video is transcoded already. */
|
||||||
|
time: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FFmpeg core module, an object to interact with ffmpeg.
|
||||||
|
*/
|
||||||
|
export interface FFmpegCoreModule {
|
||||||
|
/** default arguments prepend when running exec() */
|
||||||
|
DEFAULT_ARGS: string[];
|
||||||
|
FS: FS;
|
||||||
|
NULL: Pointer;
|
||||||
|
SIZE_I32: number;
|
||||||
|
|
||||||
|
/** return code of the ffmpeg exec, error when ret != 0 */
|
||||||
|
ret: number;
|
||||||
|
timeout: number;
|
||||||
|
mainScriptUrlOrBlob: string;
|
||||||
|
|
||||||
|
exec: (...args: string[]) => number;
|
||||||
|
ffprobe: (...args: string[]) => number;
|
||||||
|
reset: () => void;
|
||||||
|
setLogger: (logger: (log: Log) => void) => void;
|
||||||
|
setTimeout: (timeout: number) => void;
|
||||||
|
setProgress: (handler: (progress: Progress) => void) => void;
|
||||||
|
|
||||||
|
locateFile: (path: string, prefix: string) => string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory of FFmpegCoreModule.
|
||||||
|
*/
|
||||||
|
export type FFmpegCoreModuleFactory = (
|
||||||
|
moduleOverrides?: Partial<FFmpegCoreModule>
|
||||||
|
) => Promise<FFmpegCoreModule>;
|
||||||
13
public/ffmpeg_packages/util/.eslintrc.cjs
Executable file
13
public/ffmpeg_packages/util/.eslintrc.cjs
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
module.exports = {
|
||||||
|
extends: [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||||
|
],
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
parserOptions: {
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
project: ["./tsconfig.json"],
|
||||||
|
},
|
||||||
|
plugins: ["@typescript-eslint"],
|
||||||
|
};
|
||||||
1
public/ffmpeg_packages/util/.gitignore
vendored
Executable file
1
public/ffmpeg_packages/util/.gitignore
vendored
Executable file
@@ -0,0 +1 @@
|
|||||||
|
dist/
|
||||||
57
public/ffmpeg_packages/util/package.json
Executable file
57
public/ffmpeg_packages/util/package.json
Executable file
@@ -0,0 +1,57 @@
|
|||||||
|
{
|
||||||
|
"name": "@ffmpeg/util",
|
||||||
|
"version": "0.12.2",
|
||||||
|
"description": "browser utils for @ffmpeg/*",
|
||||||
|
"main": "./dist/cjs/index.js",
|
||||||
|
"type": "module",
|
||||||
|
"types": "./dist/cjs/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/cjs/index.d.ts",
|
||||||
|
"import": "./dist/esm/index.js",
|
||||||
|
"require": "./dist/cjs/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tsc -p tsconfig-esm.json --watch",
|
||||||
|
"lint": "eslint src",
|
||||||
|
"clean": "rimraf dist",
|
||||||
|
"build:esm": "tsc -p tsconfig.esm.json",
|
||||||
|
"build:umd": "tsc -p tsconfig.cjs.json && webpack",
|
||||||
|
"build": "npm run clean && npm run build:esm && npm run build:umd",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/ffmpegwasm/ffmpeg.wasm.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"ffmpeg",
|
||||||
|
"video",
|
||||||
|
"audio",
|
||||||
|
"transcode"
|
||||||
|
],
|
||||||
|
"author": "Jerome Wu <jeromewus@gmail.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/ffmpegwasm/ffmpeg.wasm/issues"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.x"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/ffmpegwasm/ffmpeg.wasm#readme",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||||
|
"@typescript-eslint/parser": "^6.1.0",
|
||||||
|
"eslint": "^8.45.0",
|
||||||
|
"rimraf": "^5.0.1",
|
||||||
|
"typescript": "^5.1.6",
|
||||||
|
"webpack-cli": "^5.1.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
public/ffmpeg_packages/util/src/const.ts
Executable file
1
public/ffmpeg_packages/util/src/const.ts
Executable file
@@ -0,0 +1 @@
|
|||||||
|
export const HeaderContentLength = "Content-Length";
|
||||||
6
public/ffmpeg_packages/util/src/errors.ts
Executable file
6
public/ffmpeg_packages/util/src/errors.ts
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
export const ERROR_RESPONSE_BODY_READER = new Error(
|
||||||
|
"failed to get response body reader"
|
||||||
|
);
|
||||||
|
export const ERROR_INCOMPLETED_DOWNLOAD = new Error(
|
||||||
|
"failed to complete download"
|
||||||
|
);
|
||||||
180
public/ffmpeg_packages/util/src/index.ts
Executable file
180
public/ffmpeg_packages/util/src/index.ts
Executable file
@@ -0,0 +1,180 @@
|
|||||||
|
import {
|
||||||
|
ERROR_RESPONSE_BODY_READER,
|
||||||
|
ERROR_INCOMPLETED_DOWNLOAD,
|
||||||
|
} from "./errors.js";
|
||||||
|
import { HeaderContentLength } from "./const.js";
|
||||||
|
import { ProgressCallback } from "./types.js";
|
||||||
|
|
||||||
|
const readFromBlobOrFile = (blob: Blob | File): Promise<Uint8Array> =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const fileReader = new FileReader();
|
||||||
|
fileReader.onload = () => {
|
||||||
|
const { result } = fileReader;
|
||||||
|
if (result instanceof ArrayBuffer) {
|
||||||
|
resolve(new Uint8Array(result));
|
||||||
|
} else {
|
||||||
|
resolve(new Uint8Array());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fileReader.onerror = (event) => {
|
||||||
|
reject(
|
||||||
|
Error(
|
||||||
|
`File could not be read! Code=${event?.target?.error?.code || -1}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
fileReader.readAsArrayBuffer(blob);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An util function to fetch data from url string, base64, URL, File or Blob format.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* ```ts
|
||||||
|
* // URL
|
||||||
|
* await fetchFile("http://localhost:3000/video.mp4");
|
||||||
|
* // base64
|
||||||
|
* await fetchFile("data:<type>;base64,wL2dvYWwgbW9yZ...");
|
||||||
|
* // URL
|
||||||
|
* await fetchFile(new URL("video.mp4", import.meta.url));
|
||||||
|
* // File
|
||||||
|
* fileInput.addEventListener('change', (e) => {
|
||||||
|
* await fetchFile(e.target.files[0]);
|
||||||
|
* });
|
||||||
|
* // Blob
|
||||||
|
* const blob = new Blob(...);
|
||||||
|
* await fetchFile(blob);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const fetchFile = async (
|
||||||
|
file?: string | File | Blob
|
||||||
|
): Promise<Uint8Array> => {
|
||||||
|
let data: ArrayBuffer | number[];
|
||||||
|
|
||||||
|
if (typeof file === "string") {
|
||||||
|
/* From base64 format */
|
||||||
|
if (/data:_data\/([a-zA-Z]*);base64,([^"]*)/.test(file)) {
|
||||||
|
data = atob(file.split(",")[1])
|
||||||
|
.split("")
|
||||||
|
.map((c) => c.charCodeAt(0));
|
||||||
|
/* From remote server/URL */
|
||||||
|
} else {
|
||||||
|
data = await (await fetch(file)).arrayBuffer();
|
||||||
|
}
|
||||||
|
} else if (file instanceof URL) {
|
||||||
|
data = await (await fetch(file)).arrayBuffer();
|
||||||
|
} else if (file instanceof File || file instanceof Blob) {
|
||||||
|
data = await readFromBlobOrFile(file);
|
||||||
|
} else {
|
||||||
|
return new Uint8Array();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uint8Array(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* importScript dynamically import a script, useful when you
|
||||||
|
* want to use different versions of ffmpeg.wasm based on environment.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* await importScript("http://localhost:3000/ffmpeg.js");
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const importScript = async (url: string): Promise<void> =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
const script = document.createElement("script");
|
||||||
|
const eventHandler = () => {
|
||||||
|
script.removeEventListener("load", eventHandler);
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
script.src = url;
|
||||||
|
script.type = "text/javascript";
|
||||||
|
script.addEventListener("load", eventHandler);
|
||||||
|
document.getElementsByTagName("head")[0].appendChild(script);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download content of a URL with progress.
|
||||||
|
*
|
||||||
|
* Progress only works when Content-Length is provided by the server.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const downloadWithProgress = async (
|
||||||
|
url: string | URL,
|
||||||
|
cb?: ProgressCallback
|
||||||
|
): Promise<ArrayBuffer> => {
|
||||||
|
const resp = await fetch(url);
|
||||||
|
let buf;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Set total to -1 to indicate that there is not Content-Type Header.
|
||||||
|
const total = parseInt(resp.headers.get(HeaderContentLength) || "-1");
|
||||||
|
|
||||||
|
const reader = resp.body?.getReader();
|
||||||
|
if (!reader) throw ERROR_RESPONSE_BODY_READER;
|
||||||
|
|
||||||
|
const chunks = [];
|
||||||
|
let received = 0;
|
||||||
|
for (;;) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
const delta = value ? value.length : 0;
|
||||||
|
|
||||||
|
if (done) {
|
||||||
|
if (total != -1 && total !== received) throw ERROR_INCOMPLETED_DOWNLOAD;
|
||||||
|
cb && cb({ url, total, received, delta, done });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
chunks.push(value);
|
||||||
|
received += delta;
|
||||||
|
cb && cb({ url, total, received, delta, done });
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new Uint8Array(received);
|
||||||
|
let position = 0;
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
data.set(chunk, position);
|
||||||
|
position += chunk.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = data.buffer;
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`failed to send download progress event: `, e);
|
||||||
|
// Fetch arrayBuffer directly when it is not possible to get progress.
|
||||||
|
buf = await resp.arrayBuffer();
|
||||||
|
cb &&
|
||||||
|
cb({
|
||||||
|
url,
|
||||||
|
total: buf.byteLength,
|
||||||
|
received: buf.byteLength,
|
||||||
|
delta: 0,
|
||||||
|
done: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* toBlobURL fetches data from an URL and return a blob URL.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* ```ts
|
||||||
|
* await toBlobURL("http://localhost:3000/ffmpeg.js", "text/javascript");
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export const toBlobURL = async (
|
||||||
|
url: string,
|
||||||
|
mimeType: string,
|
||||||
|
progress = false,
|
||||||
|
cb?: ProgressCallback
|
||||||
|
): Promise<string> => {
|
||||||
|
const buf = progress
|
||||||
|
? await downloadWithProgress(url, cb)
|
||||||
|
: await (await fetch(url)).arrayBuffer();
|
||||||
|
const blob = new Blob([buf], { type: mimeType });
|
||||||
|
return URL.createObjectURL(blob);
|
||||||
|
};
|
||||||
9
public/ffmpeg_packages/util/src/types.ts
Executable file
9
public/ffmpeg_packages/util/src/types.ts
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
export interface DownloadProgressEvent {
|
||||||
|
url: string | URL;
|
||||||
|
total: number;
|
||||||
|
received: number;
|
||||||
|
delta: number;
|
||||||
|
done: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProgressCallback = (event: DownloadProgressEvent) => void;
|
||||||
6
public/ffmpeg_packages/util/tests/.eslintrc
Executable file
6
public/ffmpeg_packages/util/tests/.eslintrc
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"rules": {
|
||||||
|
"no-undef": 0,
|
||||||
|
"camelcase": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
19
public/ffmpeg_packages/util/tests/constants.js
Executable file
19
public/ffmpeg_packages/util/tests/constants.js
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
const TIMEOUT = 60000;
|
||||||
|
const IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
||||||
|
const OPTIONS = {
|
||||||
|
corePath: IS_BROWSER ? 'http://localhost:3000/node_modules/@ffmpeg/core/dist/ffmpeg-core.js' : '@ffmpeg/core',
|
||||||
|
};
|
||||||
|
const FLAME_MP4_LENGTH = 100374;
|
||||||
|
const META_FLAME_MP4_LENGTH = 100408;
|
||||||
|
const META_FLAME_MP4_LENGTH_NO_SPACE = 100404;
|
||||||
|
|
||||||
|
if (typeof module !== 'undefined') {
|
||||||
|
module.exports = {
|
||||||
|
TIMEOUT,
|
||||||
|
IS_BROWSER,
|
||||||
|
OPTIONS,
|
||||||
|
FLAME_MP4_LENGTH,
|
||||||
|
META_FLAME_MP4_LENGTH,
|
||||||
|
META_FLAME_MP4_LENGTH_NO_SPACE,
|
||||||
|
};
|
||||||
|
}
|
||||||
21
public/ffmpeg_packages/util/tests/ffmpeg.test.html
Executable file
21
public/ffmpeg_packages/util/tests/ffmpeg.test.html
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>FFmpeg Unit Test</title>
|
||||||
|
<link rel="stylesheet" href="../node_modules/mocha/mocha.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="mocha"></div>
|
||||||
|
<script src="../node_modules/mocha/mocha.js"></script>
|
||||||
|
<script src="../node_modules/chai/chai.js"></script>
|
||||||
|
<script src="../dist/ffmpeg.dev.js"></script>
|
||||||
|
<script src="./constants.js"></script>
|
||||||
|
<script>mocha.setup('bdd');</script>
|
||||||
|
<script src="./ffmpeg.test.js"></script>
|
||||||
|
<script>
|
||||||
|
window.expect = chai.expect;
|
||||||
|
mocha.run();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
69
public/ffmpeg_packages/util/tests/ffmpeg.test.js
Executable file
69
public/ffmpeg_packages/util/tests/ffmpeg.test.js
Executable file
@@ -0,0 +1,69 @@
|
|||||||
|
const { createFFmpeg } = FFmpeg;
|
||||||
|
|
||||||
|
describe('FS()', () => {
|
||||||
|
const ffmpeg = createFFmpeg(OPTIONS);
|
||||||
|
before(async function cb() {
|
||||||
|
this.timeout(0);
|
||||||
|
await ffmpeg.load();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error when readdir for invalid path ', () => {
|
||||||
|
expect(() => ffmpeg.FS('readdir', '/invalid')).to.throw(/readdir/);
|
||||||
|
});
|
||||||
|
it('should throw error when readFile for invalid path ', () => {
|
||||||
|
expect(() => ffmpeg.FS('readFile', '/invalid')).to.throw(/readFile/);
|
||||||
|
});
|
||||||
|
it('should throw an default error ', () => {
|
||||||
|
expect(() => ffmpeg.FS('unlink', '/invalid')).to.throw(/Oops/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('load()', () => {
|
||||||
|
it('should throw error when corePath is not a string', async () => {
|
||||||
|
const ffmpeg = createFFmpeg({ ...OPTIONS, corePath: null });
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ffmpeg.load();
|
||||||
|
} catch (e) {
|
||||||
|
expect(e).to.be.an('Error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('should throw error when not called before FS() and run()', () => {
|
||||||
|
const ffmpeg = createFFmpeg(OPTIONS);
|
||||||
|
expect(() => ffmpeg.FS('readdir', 'dummy')).to.throw();
|
||||||
|
expect(() => ffmpeg.run('-h')).to.throw();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error when running load() more than once', async () => {
|
||||||
|
const ffmpeg = createFFmpeg(OPTIONS);
|
||||||
|
await ffmpeg.load();
|
||||||
|
try {
|
||||||
|
await ffmpeg.load();
|
||||||
|
} catch (e) {
|
||||||
|
expect(e).to.be.an('Error');
|
||||||
|
}
|
||||||
|
}).timeout(TIMEOUT);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isLoaded()', () => {
|
||||||
|
it('should return true when loaded', async () => {
|
||||||
|
const ffmpeg = createFFmpeg(OPTIONS);
|
||||||
|
await ffmpeg.load();
|
||||||
|
expect(ffmpeg.isLoaded()).to.equal(true);
|
||||||
|
}).timeout(TIMEOUT);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('run()', () => {
|
||||||
|
it('should not allow to run two command at the same time', async () => {
|
||||||
|
const ffmpeg = createFFmpeg(OPTIONS);
|
||||||
|
await ffmpeg.load();
|
||||||
|
ffmpeg.run('-h');
|
||||||
|
setTimeout(() => {
|
||||||
|
try {
|
||||||
|
ffmpeg.run('-h');
|
||||||
|
} catch (e) {
|
||||||
|
expect(e).to.be.an(Error);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
}).timeout(TIMEOUT);
|
||||||
|
});
|
||||||
8
public/ffmpeg_packages/util/tsconfig.cjs.json
Executable file
8
public/ffmpeg_packages/util/tsconfig.cjs.json
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"outDir": "dist/cjs",
|
||||||
|
"target": "es2015"
|
||||||
|
}
|
||||||
|
}
|
||||||
9
public/ffmpeg_packages/util/tsconfig.esm.json
Executable file
9
public/ffmpeg_packages/util/tsconfig.esm.json
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "esnext",
|
||||||
|
"outDir": "dist/esm",
|
||||||
|
"target": "esnext",
|
||||||
|
"moduleResolution": "nodenext"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
public/ffmpeg_packages/util/tsconfig.json
Executable file
6
public/ffmpeg_packages/util/tsconfig.json
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"declaration": true
|
||||||
|
}
|
||||||
|
}
|
||||||
12
public/ffmpeg_packages/util/webpack.config.cjs
Executable file
12
public/ffmpeg_packages/util/webpack.config.cjs
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
mode: "production",
|
||||||
|
entry: "./dist/cjs/index.js",
|
||||||
|
output: {
|
||||||
|
library: "FFmpegUtil",
|
||||||
|
libraryTarget: "umd",
|
||||||
|
filename: "index.js",
|
||||||
|
path: path.resolve(__dirname, "dist", "umd"),
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -391,7 +391,8 @@ const useVideoExport = ({ timelineElements, dimensions, totalDuration }) => {
|
|||||||
showConsoleLogs && console.log('FFmpeg Log:', message);
|
showConsoleLogs && console.log('FFmpeg Log:', message);
|
||||||
});
|
});
|
||||||
|
|
||||||
const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.15/dist/esm';
|
//const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.10/dist/esm';
|
||||||
|
const baseURL = window.location.origin + '/ffmpeg_packages/core/dist/esm';
|
||||||
const coreURL = `${baseURL}/ffmpeg-core.js`;
|
const coreURL = `${baseURL}/ffmpeg-core.js`;
|
||||||
const wasmURL = `${baseURL}/ffmpeg-core.wasm`;
|
const wasmURL = `${baseURL}/ffmpeg-core.wasm`;
|
||||||
|
|
||||||
@@ -504,6 +505,8 @@ const useVideoExport = ({ timelineElements, dimensions, totalDuration }) => {
|
|||||||
|
|
||||||
showConsoleLogs && console.log('Generated FFmpeg arguments:', args);
|
showConsoleLogs && console.log('Generated FFmpeg arguments:', args);
|
||||||
|
|
||||||
|
showConsoleLogs && console.log(generateFFmpegCommand(true, true));
|
||||||
|
|
||||||
setExportProgress(70);
|
setExportProgress(70);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user