Initial commit: media-to-text app

This commit is contained in:
Gan, Jimmy
2026-02-08 23:26:40 +08:00
commit b155e85040
44 changed files with 1396 additions and 0 deletions
+162
View File
@@ -0,0 +1,162 @@
import { FSNode, FFMessageLoadConfig, OK, IsFirst, LogEventCallback, ProgressEventCallback, FileData, FFFSType, FFFSMountOptions, FFFSPath } from "./types.js";
type FFMessageOptions = {
signal?: AbortSignal;
};
/**
* Provides APIs to interact with ffmpeg web worker.
*
* @example
* ```ts
* const ffmpeg = new FFmpeg();
* ```
*/
export declare class FFmpeg {
#private;
loaded: boolean;
/**
* 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
*/
on(event: "log", callback: LogEventCallback): void;
on(event: "progress", callback: ProgressEventCallback): void;
/**
* Unlisten to log or progress events from `ffmpeg.exec()`.
*
* @category FFmpeg
*/
off(event: "log", callback: LogEventCallback): void;
off(event: "progress", callback: ProgressEventCallback): void;
/**
* 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.
*/
load: ({ classWorkerURL, ...config }?: FFMessageLoadConfig, { signal }?: FFMessageOptions) => 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
*/
exec: (args: string[], timeout?: number, { signal }?: FFMessageOptions) => 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
*/
ffprobe: (args: string[], timeout?: number, { signal }?: FFMessageOptions) => Promise<number>;
/**
* Terminate all ongoing API calls and terminate web worker.
* `FFmpeg.load()` must be called again before calling any other APIs.
*
* @category FFmpeg
*/
terminate: () => void;
/**
* 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
*/
writeFile: (path: string, data: FileData, { signal }?: FFMessageOptions) => Promise<OK>;
mount: (fsType: FFFSType, options: FFFSMountOptions, mountPoint: FFFSPath) => Promise<OK>;
unmount: (mountPoint: FFFSPath) => 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
*/
readFile: (path: string, encoding?: string, { signal }?: FFMessageOptions) => Promise<FileData>;
/**
* Delete a file.
*
* @category File System
*/
deleteFile: (path: string, { signal }?: FFMessageOptions) => Promise<OK>;
/**
* Rename a file or directory.
*
* @category File System
*/
rename: (oldPath: string, newPath: string, { signal }?: FFMessageOptions) => Promise<OK>;
/**
* Create a directory.
*
* @category File System
*/
createDir: (path: string, { signal }?: FFMessageOptions) => Promise<OK>;
/**
* List directory contents.
*
* @category File System
*/
listDir: (path: string, { signal }?: FFMessageOptions) => Promise<FSNode[]>;
/**
* Delete an empty directory.
*
* @category File System
*/
deleteDir: (path: string, { signal }?: FFMessageOptions) => Promise<OK>;
}
export {};
+306
View File
@@ -0,0 +1,306 @@
import { FFMessageType } from "./const.js";
import { getMessageID } from "./utils.js";
import { ERROR_TERMINATED, ERROR_NOT_LOADED } from "./errors.js";
/**
* Provides APIs to interact with ffmpeg web worker.
*
* @example
* ```ts
* const ffmpeg = new FFmpeg();
* ```
*/
export class FFmpeg {
#worker = null;
/**
* #resolves and #rejects tracks Promise resolves and rejects to
* be called when we receive message from web worker.
*/
#resolves = {};
#rejects = {};
#logEventCallbacks = [];
#progressEventCallbacks = [];
loaded = false;
/**
* register worker message event handlers.
*/
#registerHandlers = () => {
if (this.#worker) {
this.#worker.onmessage = ({ data: { id, type, data }, }) => {
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));
break;
case FFMessageType.PROGRESS:
this.#progressEventCallbacks.forEach((f) => f(data));
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 }, trans = [], signal) => {
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 });
});
};
on(event, callback) {
if (event === "log") {
this.#logEventCallbacks.push(callback);
}
else if (event === "progress") {
this.#progressEventCallbacks.push(callback);
}
}
off(event, callback) {
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.
*/
load = ({ classWorkerURL, ...config } = {}, { signal } = {}) => {
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 worekr.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);
};
/**
* 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
*/
exec = (
/** ffmpeg command line args */
args,
/**
* milliseconds to wait before stopping the command execution.
*
* @defaultValue -1
*/
timeout = -1, { signal } = {}) => this.#send({
type: FFMessageType.EXEC,
data: { args, timeout },
}, undefined, signal);
/**
* 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
*/
ffprobe = (
/** ffprobe command line args */
args,
/**
* milliseconds to wait before stopping the command execution.
*
* @defaultValue -1
*/
timeout = -1, { signal } = {}) => this.#send({
type: FFMessageType.FFPROBE,
data: { args, timeout },
}, undefined, signal);
/**
* Terminate all ongoing API calls and terminate web worker.
* `FFmpeg.load()` must be called again before calling any other APIs.
*
* @category FFmpeg
*/
terminate = () => {
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
*/
writeFile = (path, data, { signal } = {}) => {
const trans = [];
if (data instanceof Uint8Array) {
trans.push(data.buffer);
}
return this.#send({
type: FFMessageType.WRITE_FILE,
data: { path, data },
}, trans, signal);
};
mount = (fsType, options, mountPoint) => {
const trans = [];
return this.#send({
type: FFMessageType.MOUNT,
data: { fsType, options, mountPoint },
}, trans);
};
unmount = (mountPoint) => {
const trans = [];
return this.#send({
type: FFMessageType.UNMOUNT,
data: { mountPoint },
}, trans);
};
/**
* Read data from ffmpeg.wasm.
*
* @example
* ```ts
* const ffmpeg = new FFmpeg();
* await ffmpeg.load();
* const data = await ffmpeg.readFile("video.mp4");
* ```
*
* @category File System
*/
readFile = (path,
/**
* 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 } = {}) => this.#send({
type: FFMessageType.READ_FILE,
data: { path, encoding },
}, undefined, signal);
/**
* Delete a file.
*
* @category File System
*/
deleteFile = (path, { signal } = {}) => this.#send({
type: FFMessageType.DELETE_FILE,
data: { path },
}, undefined, signal);
/**
* Rename a file or directory.
*
* @category File System
*/
rename = (oldPath, newPath, { signal } = {}) => this.#send({
type: FFMessageType.RENAME,
data: { oldPath, newPath },
}, undefined, signal);
/**
* Create a directory.
*
* @category File System
*/
createDir = (path, { signal } = {}) => this.#send({
type: FFMessageType.CREATE_DIR,
data: { path },
}, undefined, signal);
/**
* List directory contents.
*
* @category File System
*/
listDir = (path, { signal } = {}) => this.#send({
type: FFMessageType.LIST_DIR,
data: { path },
}, undefined, signal);
/**
* Delete an empty directory.
*
* @category File System
*/
deleteDir = (path, { signal } = {}) => this.#send({
type: FFMessageType.DELETE_DIR,
data: { path },
}, undefined, signal);
}
+22
View File
@@ -0,0 +1,22 @@
export declare const MIME_TYPE_JAVASCRIPT = "text/javascript";
export declare const MIME_TYPE_WASM = "application/wasm";
export declare const CORE_VERSION = "0.12.9";
export declare const CORE_URL: string;
export declare 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"
}
+23
View File
@@ -0,0 +1,23 @@
export const MIME_TYPE_JAVASCRIPT = "text/javascript";
export const MIME_TYPE_WASM = "application/wasm";
export const CORE_VERSION = "0.12.9";
export const CORE_URL = `https://unpkg.com/@ffmpeg/core@${CORE_VERSION}/dist/umd/ffmpeg-core.js`;
export var FFMessageType;
(function (FFMessageType) {
FFMessageType["LOAD"] = "LOAD";
FFMessageType["EXEC"] = "EXEC";
FFMessageType["FFPROBE"] = "FFPROBE";
FFMessageType["WRITE_FILE"] = "WRITE_FILE";
FFMessageType["READ_FILE"] = "READ_FILE";
FFMessageType["DELETE_FILE"] = "DELETE_FILE";
FFMessageType["RENAME"] = "RENAME";
FFMessageType["CREATE_DIR"] = "CREATE_DIR";
FFMessageType["LIST_DIR"] = "LIST_DIR";
FFMessageType["DELETE_DIR"] = "DELETE_DIR";
FFMessageType["ERROR"] = "ERROR";
FFMessageType["DOWNLOAD"] = "DOWNLOAD";
FFMessageType["PROGRESS"] = "PROGRESS";
FFMessageType["LOG"] = "LOG";
FFMessageType["MOUNT"] = "MOUNT";
FFMessageType["UNMOUNT"] = "UNMOUNT";
})(FFMessageType || (FFMessageType = {}));
+3
View File
@@ -0,0 +1,3 @@
export declare class FFmpeg {
constructor();
}
+6
View File
@@ -0,0 +1,6 @@
// File to be imported in node enviroments
export class FFmpeg {
constructor() {
throw new Error("ffmpeg.wasm does not support nodejs");
}
}
+4
View File
@@ -0,0 +1,4 @@
export declare const ERROR_UNKNOWN_MESSAGE_TYPE: Error;
export declare const ERROR_NOT_LOADED: Error;
export declare const ERROR_TERMINATED: Error;
export declare const ERROR_IMPORT_FAILURE: Error;
+4
View File
@@ -0,0 +1,4 @@
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
View File
@@ -0,0 +1,2 @@
export * from "./classes.js";
export * from "./types.js";
+2
View File
@@ -0,0 +1,2 @@
export * from "./classes.js";
export * from "./types.js";
+131
View File
@@ -0,0 +1,131 @@
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 declare 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 | 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;
};
}
+9
View File
@@ -0,0 +1,9 @@
export var FFFSType;
(function (FFFSType) {
FFFSType["MEMFS"] = "MEMFS";
FFFSType["NODEFS"] = "NODEFS";
FFFSType["NODERAWFS"] = "NODERAWFS";
FFFSType["IDBFS"] = "IDBFS";
FFFSType["WORKERFS"] = "WORKERFS";
FFFSType["PROXYFS"] = "PROXYFS";
})(FFFSType || (FFFSType = {}));
+4
View File
@@ -0,0 +1,4 @@
/**
* Generate an unique message ID.
*/
export declare const getMessageID: () => number;
+7
View File
@@ -0,0 +1,7 @@
/**
* Generate an unique message ID.
*/
export const getMessageID = (() => {
let messageID = 0;
return () => messageID++;
})();
+9
View File
@@ -0,0 +1,9 @@
/// <reference no-default-lib="true"/>
/// <reference lib="esnext" />
/// <reference lib="webworker" />
import type { FFmpegCoreModuleFactory } from "@ffmpeg/types";
declare global {
interface WorkerGlobalScope {
createFFmpegCore: FFmpegCoreModuleFactory;
}
}
+161
View File
@@ -0,0 +1,161 @@
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
/// <reference lib="webworker" />
import { CORE_URL, FFMessageType } from "./const.js";
import { ERROR_UNKNOWN_MESSAGE_TYPE, ERROR_NOT_LOADED, ERROR_IMPORT_FAILURE, } from "./errors.js";
let ffmpeg;
const load = async ({ coreURL: _coreURL, wasmURL: _wasmURL, workerURL: _workerURL, }) => {
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.createFFmpegCore = (await import(
/* @vite-ignore */ _coreURL)).default;
if (!self.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.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 }) => {
ffmpeg.setTimeout(timeout);
ffmpeg.exec(...args);
const ret = ffmpeg.ret;
ffmpeg.reset();
return ret;
};
const ffprobe = ({ args, timeout = -1 }) => {
ffmpeg.setTimeout(timeout);
ffmpeg.ffprobe(...args);
const ret = ffmpeg.ret;
ffmpeg.reset();
return ret;
};
const writeFile = ({ path, data }) => {
ffmpeg.FS.writeFile(path, data);
return true;
};
const readFile = ({ path, encoding }) => ffmpeg.FS.readFile(path, { encoding });
// TODO: check if deletion works.
const deleteFile = ({ path }) => {
ffmpeg.FS.unlink(path);
return true;
};
const rename = ({ oldPath, newPath }) => {
ffmpeg.FS.rename(oldPath, newPath);
return true;
};
// TODO: check if creation works.
const createDir = ({ path }) => {
ffmpeg.FS.mkdir(path);
return true;
};
const listDir = ({ path }) => {
const names = ffmpeg.FS.readdir(path);
const nodes = [];
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 }) => {
ffmpeg.FS.rmdir(path);
return true;
};
const mount = ({ fsType, options, mountPoint }) => {
const str = fsType;
const fs = ffmpeg.FS.filesystems[str];
if (!fs)
return false;
ffmpeg.FS.mount(fs, options, mountPoint);
return true;
};
const unmount = ({ mountPoint }) => {
ffmpeg.FS.unmount(mountPoint);
return true;
};
self.onmessage = async ({ data: { id, type, data: _data }, }) => {
const trans = [];
let data;
try {
if (type !== FFMessageType.LOAD && !ffmpeg)
throw ERROR_NOT_LOADED; // eslint-disable-line
switch (type) {
case FFMessageType.LOAD:
data = await load(_data);
break;
case FFMessageType.EXEC:
data = exec(_data);
break;
case FFMessageType.FFPROBE:
data = ffprobe(_data);
break;
case FFMessageType.WRITE_FILE:
data = writeFile(_data);
break;
case FFMessageType.READ_FILE:
data = readFile(_data);
break;
case FFMessageType.DELETE_FILE:
data = deleteFile(_data);
break;
case FFMessageType.RENAME:
data = rename(_data);
break;
case FFMessageType.CREATE_DIR:
data = createDir(_data);
break;
case FFMessageType.LIST_DIR:
data = listDir(_data);
break;
case FFMessageType.DELETE_DIR:
data = deleteDir(_data);
break;
case FFMessageType.MOUNT:
data = mount(_data);
break;
case FFMessageType.UNMOUNT:
data = unmount(_data);
break;
default:
throw ERROR_UNKNOWN_MESSAGE_TYPE;
}
}
catch (e) {
self.postMessage({
id,
type: FFMessageType.ERROR,
data: e.toString(),
});
return;
}
if (data instanceof Uint8Array) {
trans.push(data.buffer);
}
self.postMessage({ id, type, data }, trans);
};
+2
View File
@@ -0,0 +1,2 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FFmpegWASM=t():e.FFmpegWASM=t()}(self,(()=>(()=>{var e={454:e=>{function t(e){return Promise.resolve().then((()=>{var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}))}t.keys=()=>[],t.resolve=t,t.id=454,e.exports=t}},t={};function r(o){var s=t[o];if(void 0!==s)return s.exports;var a=t[o]={exports:{}};return e[o](a,a.exports,r),a.exports}return r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{"use strict";const e="https://unpkg.com/@ffmpeg/core@0.12.9/dist/umd/ffmpeg-core.js";var t;!function(e){e.LOAD="LOAD",e.EXEC="EXEC",e.FFPROBE="FFPROBE",e.WRITE_FILE="WRITE_FILE",e.READ_FILE="READ_FILE",e.DELETE_FILE="DELETE_FILE",e.RENAME="RENAME",e.CREATE_DIR="CREATE_DIR",e.LIST_DIR="LIST_DIR",e.DELETE_DIR="DELETE_DIR",e.ERROR="ERROR",e.DOWNLOAD="DOWNLOAD",e.PROGRESS="PROGRESS",e.LOG="LOG",e.MOUNT="MOUNT",e.UNMOUNT="UNMOUNT"}(t||(t={}));const o=new Error("unknown message type"),s=new Error("ffmpeg is not loaded, call `await ffmpeg.load()` first"),a=(new Error("called FFmpeg.terminate()"),new Error("failed to import ffmpeg-core.js"));let n;self.onmessage=async({data:{id:E,type:c,data:i}})=>{const p=[];let f;try{if(c!==t.LOAD&&!n)throw s;switch(c){case t.LOAD:f=await(async({coreURL:o,wasmURL:s,workerURL:E})=>{const c=!n;try{o||(o=e),importScripts(o)}catch{if(o&&o!==e||(o=e.replace("/umd/","/esm/")),self.createFFmpegCore=(await r(454)(o)).default,!self.createFFmpegCore)throw a}const i=o,p=s||o.replace(/.js$/g,".wasm"),f=E||o.replace(/.js$/g,".worker.js");return n=await self.createFFmpegCore({mainScriptUrlOrBlob:`${i}#${btoa(JSON.stringify({wasmURL:p,workerURL:f}))}`}),n.setLogger((e=>self.postMessage({type:t.LOG,data:e}))),n.setProgress((e=>self.postMessage({type:t.PROGRESS,data:e}))),c})(i);break;case t.EXEC:f=(({args:e,timeout:t=-1})=>{n.setTimeout(t),n.exec(...e);const r=n.ret;return n.reset(),r})(i);break;case t.FFPROBE:f=(({args:e,timeout:t=-1})=>{n.setTimeout(t),n.ffprobe(...e);const r=n.ret;return n.reset(),r})(i);break;case t.WRITE_FILE:f=(({path:e,data:t})=>(n.FS.writeFile(e,t),!0))(i);break;case t.READ_FILE:f=(({path:e,encoding:t})=>n.FS.readFile(e,{encoding:t}))(i);break;case t.DELETE_FILE:f=(({path:e})=>(n.FS.unlink(e),!0))(i);break;case t.RENAME:f=(({oldPath:e,newPath:t})=>(n.FS.rename(e,t),!0))(i);break;case t.CREATE_DIR:f=(({path:e})=>(n.FS.mkdir(e),!0))(i);break;case t.LIST_DIR:f=(({path:e})=>{const t=n.FS.readdir(e),r=[];for(const o of t){const t=n.FS.stat(`${e}/${o}`),s=n.FS.isDir(t.mode);r.push({name:o,isDir:s})}return r})(i);break;case t.DELETE_DIR:f=(({path:e})=>(n.FS.rmdir(e),!0))(i);break;case t.MOUNT:f=(({fsType:e,options:t,mountPoint:r})=>{const o=e,s=n.FS.filesystems[o];return!!s&&(n.FS.mount(s,t,r),!0)})(i);break;case t.UNMOUNT:f=(({mountPoint:e})=>(n.FS.unmount(e),!0))(i);break;default:throw o}}catch(e){return void self.postMessage({id:E,type:t.ERROR,data:e.toString()})}f instanceof Uint8Array&&p.push(f.buffer),self.postMessage({id:E,type:c,data:f},p)}})(),{}})()));
//# sourceMappingURL=814.ffmpeg.js.map
File diff suppressed because one or more lines are too long
+2
View File
@@ -0,0 +1,2 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FFmpegWASM=t():e.FFmpegWASM=t()}(self,(()=>(()=>{"use strict";var e={m:{},d:(t,s)=>{for(var r in s)e.o(s,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:s[r]})},u:e=>e+".ffmpeg.js"};e.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),e.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),e.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{var t;e.g.importScripts&&(t=e.g.location+"");var s=e.g.document;if(!t&&s&&(s.currentScript&&(t=s.currentScript.src),!t)){var r=s.getElementsByTagName("script");if(r.length)for(var a=r.length-1;a>-1&&!t;)t=r[a--].src}if(!t)throw new Error("Automatic publicPath is not supported in this browser");t=t.replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),e.p=t})(),e.b=document.baseURI||self.location.href;var t,s={};e.r(s),e.d(s,{FFFSType:()=>n,FFmpeg:()=>i}),function(e){e.LOAD="LOAD",e.EXEC="EXEC",e.FFPROBE="FFPROBE",e.WRITE_FILE="WRITE_FILE",e.READ_FILE="READ_FILE",e.DELETE_FILE="DELETE_FILE",e.RENAME="RENAME",e.CREATE_DIR="CREATE_DIR",e.LIST_DIR="LIST_DIR",e.DELETE_DIR="DELETE_DIR",e.ERROR="ERROR",e.DOWNLOAD="DOWNLOAD",e.PROGRESS="PROGRESS",e.LOG="LOG",e.MOUNT="MOUNT",e.UNMOUNT="UNMOUNT"}(t||(t={}));const r=(()=>{let e=0;return()=>e++})(),a=(new Error("unknown message type"),new Error("ffmpeg is not loaded, call `await ffmpeg.load()` first")),o=new Error("called FFmpeg.terminate()");new Error("failed to import ffmpeg-core.js");class i{#e=null;#t={};#s={};#r=[];#a=[];loaded=!1;#o=()=>{this.#e&&(this.#e.onmessage=({data:{id:e,type:s,data:r}})=>{switch(s){case t.LOAD:this.loaded=!0,this.#t[e](r);break;case t.MOUNT:case t.UNMOUNT:case t.EXEC:case t.FFPROBE:case t.WRITE_FILE:case t.READ_FILE:case t.DELETE_FILE:case t.RENAME:case t.CREATE_DIR:case t.LIST_DIR:case t.DELETE_DIR:this.#t[e](r);break;case t.LOG:this.#r.forEach((e=>e(r)));break;case t.PROGRESS:this.#a.forEach((e=>e(r)));break;case t.ERROR:this.#s[e](r)}delete this.#t[e],delete this.#s[e]})};#i=({type:e,data:t},s=[],o)=>this.#e?new Promise(((a,i)=>{const n=r();this.#e&&this.#e.postMessage({id:n,type:e,data:t},s),this.#t[n]=a,this.#s[n]=i,o?.addEventListener("abort",(()=>{i(new DOMException(`Message # ${n} was aborted`,"AbortError"))}),{once:!0})})):Promise.reject(a);on(e,t){"log"===e?this.#r.push(t):"progress"===e&&this.#a.push(t)}off(e,t){"log"===e?this.#r=this.#r.filter((e=>e!==t)):"progress"===e&&(this.#a=this.#a.filter((e=>e!==t)))}load=({classWorkerURL:s,...r}={},{signal:a}={})=>(this.#e||(this.#e=s?new Worker(new URL(s,"file:///Users/focus/Projects/ffmpeg.wasm/packages/ffmpeg/dist/esm/classes.js"),{type:"module"}):new Worker(new URL(e.p+e.u(814),e.b),{type:void 0}),this.#o()),this.#i({type:t.LOAD,data:r},void 0,a));exec=(e,s=-1,{signal:r}={})=>this.#i({type:t.EXEC,data:{args:e,timeout:s}},void 0,r);ffprobe=(e,s=-1,{signal:r}={})=>this.#i({type:t.FFPROBE,data:{args:e,timeout:s}},void 0,r);terminate=()=>{const e=Object.keys(this.#s);for(const t of e)this.#s[t](o),delete this.#s[t],delete this.#t[t];this.#e&&(this.#e.terminate(),this.#e=null,this.loaded=!1)};writeFile=(e,s,{signal:r}={})=>{const a=[];return s instanceof Uint8Array&&a.push(s.buffer),this.#i({type:t.WRITE_FILE,data:{path:e,data:s}},a,r)};mount=(e,s,r)=>this.#i({type:t.MOUNT,data:{fsType:e,options:s,mountPoint:r}},[]);unmount=e=>this.#i({type:t.UNMOUNT,data:{mountPoint:e}},[]);readFile=(e,s="binary",{signal:r}={})=>this.#i({type:t.READ_FILE,data:{path:e,encoding:s}},void 0,r);deleteFile=(e,{signal:s}={})=>this.#i({type:t.DELETE_FILE,data:{path:e}},void 0,s);rename=(e,s,{signal:r}={})=>this.#i({type:t.RENAME,data:{oldPath:e,newPath:s}},void 0,r);createDir=(e,{signal:s}={})=>this.#i({type:t.CREATE_DIR,data:{path:e}},void 0,s);listDir=(e,{signal:s}={})=>this.#i({type:t.LIST_DIR,data:{path:e}},void 0,s);deleteDir=(e,{signal:s}={})=>this.#i({type:t.DELETE_DIR,data:{path:e}},void 0,s)}var n;return function(e){e.MEMFS="MEMFS",e.NODEFS="NODEFS",e.NODERAWFS="NODERAWFS",e.IDBFS="IDBFS",e.WORKERFS="WORKERFS",e.PROXYFS="PROXYFS"}(n||(n={})),s})()));
//# sourceMappingURL=ffmpeg.js.map
File diff suppressed because one or more lines are too long