Complete basic rewrite

This commit is contained in:
Jerome Wu 2022-09-30 17:21:03 +08:00
parent 2f18a2d806
commit a136b8b1bb
39 changed files with 3586 additions and 8769 deletions

View File

@ -37,11 +37,11 @@ RUN bash -x /src/build.sh
# Build ffmpeg.wasm
FROM ffmpeg-builder AS ffmpeg-wasm-builder
COPY src/bind /src/wasm/bind
COPY src/fftools /src/wasm/fftools
COPY src/bind /src/src/bind
COPY src/fftools /src/src/fftools
COPY build/ffmpeg-wasm.sh build.sh
RUN mkdir -p /src/dist/umd && bash -x /src/build.sh -o dist/umd/ffmpeg.js
RUN mkdir -p /src/dist/esm && bash -x /src/build.sh -sEXPORT_ES6 -o dist/esm/ffmpeg.js
RUN mkdir -p /src/dist/umd && bash -x /src/build.sh -o dist/umd/ffmpeg-core.js
RUN mkdir -p /src/dist/esm && bash -x /src/build.sh -sEXPORT_ES6 -o dist/esm/ffmpeg-core.js
# Export ffmpeg-core.wasm to dist/, use `docker buildx build -o . .` to get assets
FROM scratch AS exportor

View File

@ -9,13 +9,13 @@ PROD_CFLAGS := -O3 -msimd128
PROD_MT_CFLAGS := $(PROD_CFLAGS) $(MT_FLAGS)
clean:
rm -rf ./packages/ffmpeg$(PKG_SUFFIX)/dist
rm -rf ./packages/ffmpeg$(PKG_SUFFIX)/types
rm -rf ./packages/core$(PKG_SUFFIX)/dist
rm -rf ./packages/core$(PKG_SUFFIX)/types
.PHONY: build
build:
make clean PKG_SUFFIX="$(PKG_SUFFIX)"
cp -r src/types/ffmpeg packages/ffmpeg$(PKG_SUFFIX)/types
cp -r src/types/ffmpeg-core packages/core$(PKG_SUFFIX)/types
EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \
EXTRA_LDFLAGS="$(EXTRA_LDFLAGS)" \
FFMPEG_ST="$(FFMPEG_ST)" \
@ -25,7 +25,7 @@ build:
--build-arg EXTRA_LDFLAGS \
--build-arg FFMPEG_MT \
--build-arg FFMPEG_ST \
-o ./packages/ffmpeg$(PKG_SUFFIX) \
-o ./packages/core$(PKG_SUFFIX) \
$(EXTRA_ARGS) \
.
@ -49,3 +49,6 @@ prd:
prd-mt:
make build-mt EXTRA_CFLAGS="$(PROD_MT_CFLAGS)"
test:
npm run test

View File

@ -3,7 +3,7 @@
"version": "0.0.1",
"description": "browser example",
"scripts": {
"start": "npx http-server ../../"
"start": "npx http-server -c-1 ../../"
},
"author": "Jerome Wu <jeromewus@gmail.com>",
"license": "MIT"

View File

@ -11,20 +11,32 @@
<p id="message"></p>
<script>
const { fetchFile } = FFmpegUtil;
const { FFmpeg } = FFmpegWASM;
let ffmpeg = null;
const transcode = async ({ target: { files } }) => {
const message = document.getElementById('message');
if (ffmpeg === null) {
ffmpeg = await createFFmpeg();
ffmpeg.setProgress((progress) => { console.log(progress * 100); });
ffmpeg = new FFmpeg();
ffmpeg.on(FFmpeg.DOWNLOAD, ({ url, total, received, done }) => {
console.log(`downloading ${url}, progress: ${received / total * 100} %, done: ${done}`);
});
ffmpeg.on(FFmpeg.PROGRESS, (p) => {
message.innerHTML = `${p * 100} %`;
});
ffmpeg.on(FFmpeg.LOG, ({ message }) => {
console.log(message);
});
await ffmpeg.load({
coreURL: "/packages/core/dist/umd/ffmpeg-core.js",
});
}
const { name } = files[0];
ffmpeg.FS.writeFile(name, await fetchFile(files[0]));
await ffmpeg.writeFile(name, await fetchFile(files[0]));
message.innerHTML = 'Start transcoding';
await ffmpeg.exec('-i', name, 'output.mp4');
await ffmpeg.exec(['-i', name, 'output.mp4']);
message.innerHTML = 'Complete transcoding';
const data = ffmpeg.FS.readFile('output.mp4');
const data = await ffmpeg.readFile('output.mp4');
const video = document.getElementById('output-video');
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));

View File

@ -3,11 +3,11 @@
# ex:
# bash ffmpeg-wasm.sh -o ffmpeg.js
EXPORT_NAME="createFFmpeg"
EXPORT_NAME="createFFmpegCore"
CONF_FLAGS=(
-I.
-I./wasm/fftools
-I./src/fftools
-I$INSTALL_DIR/include
-L$INSTALL_DIR/lib
-Llibavcodec
@ -35,17 +35,17 @@ CONF_FLAGS=(
${FFMPEG_MT:+ -sPTHREAD_POOL_SIZE=32} # use 32 threads
${FFMPEG_ST:+ -sINITIAL_MEMORY=32MB -sALLOW_MEMORY_GROWTH} # Use just enough memory as memory usage can grow
-sEXPORT_NAME="$EXPORT_NAME" # required in browser env, so that user can access this module from window.createFFmpeg
-sEXPORTED_FUNCTIONS=$(node wasm/bind/ffmpeg/export.js) # exported functions
-sEXPORTED_RUNTIME_METHODS=$(node wasm/bind/ffmpeg/export-runtime.js) # exported built-in functions
--pre-js wasm/bind/ffmpeg/bind.js # extra bindings, contains most of the ffmpeg.wasm javascript code
-sEXPORTED_FUNCTIONS=$(node src/bind/ffmpeg/export.js) # exported functions
-sEXPORTED_RUNTIME_METHODS=$(node src/bind/ffmpeg/export-runtime.js) # exported built-in functions
--pre-js src/bind/ffmpeg/bind.js # extra bindings, contains most of the ffmpeg.wasm javascript code
# ffmpeg source code
wasm/fftools/cmdutils.c
wasm/fftools/ffmpeg.c
wasm/fftools/ffmpeg_filter.c
wasm/fftools/ffmpeg_hw.c
wasm/fftools/ffmpeg_mux.c
wasm/fftools/ffmpeg_opt.c
wasm/fftools/opt_common.c
src/fftools/cmdutils.c
src/fftools/ffmpeg.c
src/fftools/ffmpeg_filter.c
src/fftools/ffmpeg_hw.c
src/fftools/ffmpeg_mux.c
src/fftools/ffmpeg_opt.c
src/fftools/opt_common.c
)
emcc "${CONF_FLAGS[@]}" $@

11372
package-lock.json generated

File diff suppressed because it is too large Load Diff

2
packages/core-mt/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
dist/
types/

View File

@ -0,0 +1,50 @@
{
"name": "@ffmpeg/core-mt",
"version": "0.11.5",
"description": "FFmpeg WebAssembly version",
"main": "./dist/umd/ffmpeg-core.js",
"types": "./types/ffmpeg-core.d.ts",
"exports": {
".": {
"types": "./types/ffmpeg-core.d.ts",
"import": "./dist/esm/ffmpeg-core.js",
"require": "./dist/umd/ffmpeg-core.js"
}
},
"scripts": {
"lint": "eslint types"
},
"files": [
"dist",
"types/ffmpeg-core.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": ">=16.6.0"
},
"homepage": "https://github.com/ffmpegwasm/ffmpeg.wasm#readme",
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.37.0",
"@typescript-eslint/parser": "^5.37.0",
"eslint": "^8.23.1",
"typescript": "^4.8.3"
}
}

2
packages/core/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
dist/
types/

View File

@ -0,0 +1,50 @@
{
"name": "@ffmpeg/core",
"version": "0.11.5",
"description": "FFmpeg WebAssembly version",
"main": "./dist/umd/ffmpeg-core.js",
"types": "./types/ffmpeg-core.d.ts",
"exports": {
".": {
"types": "./types/ffmpeg-core.d.ts",
"import": "./dist/esm/ffmpeg-core.js",
"require": "./dist/umd/ffmpeg-core.js"
}
},
"scripts": {
"lint": "eslint types"
},
"files": [
"dist",
"types/ffmpeg-core.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": ">=16.6.0"
},
"homepage": "https://github.com/ffmpegwasm/ffmpeg.wasm#readme",
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.37.0",
"@typescript-eslint/parser": "^5.37.0",
"eslint": "^8.23.1",
"typescript": "^4.8.3"
}
}

View File

@ -0,0 +1 @@
.eslintrc.cjs

View 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"],
};

View File

@ -1,2 +1,2 @@
dist/
types/
docs/

View File

@ -3,16 +3,23 @@
"version": "0.11.5",
"description": "FFmpeg WebAssembly version",
"main": "./dist/umd/ffmpeg.js",
"types": "./types/ffmpeg.d.ts",
"types": "./dist/umd/ffmpeg.d.ts",
"exports": {
".": {
"types": "./types/ffmpeg.d.ts",
"import": "./dist/esm/ffmpeg.js",
"types": "./dist/umd/ffmpeg.d.ts",
"import": "./dist/esm/index.js",
"require": "./dist/umd/ffmpeg.js"
}
},
"scripts": {
"lint": "eslint types"
"dev": "webpack --watch -c webpack.dev.config.js",
"lint": "eslint src",
"clean": "rimraf dist",
"build:umd": "webpack",
"build:d": "tsc -p tsconfig.d.json",
"build:esm": "tsc -p tsconfig.esm.json",
"build": "npm-run-all clean build:*",
"docs": "typedoc --entryPointStrategy expand ./src"
},
"files": [
"dist",
@ -45,6 +52,16 @@
"@typescript-eslint/eslint-plugin": "^5.37.0",
"@typescript-eslint/parser": "^5.37.0",
"eslint": "^8.23.1",
"typescript": "^4.8.3"
"npm-run-all": "^4.1.5",
"rimraf": "^3.0.2",
"ts-loader": "^9.4.1",
"typedoc": "^0.23.15",
"typescript": "^4.8.3",
"webpack-cli": "^4.10.0",
"worker-loader": "^3.0.8"
},
"dependencies": {
"@ffmpeg/types": "^0.11.5",
"events": "^3.3.0"
}
}

View File

@ -0,0 +1,215 @@
import EventEmitter from "events";
import { FFMessageType } from "./const";
import {
CallbackData,
Callbacks,
DownloadProgressEvent,
FFMessageEventCallback,
FFMessageLoadConfig,
IsDone,
IsFirst,
LogEvent,
Message,
Progress,
} from "./types";
import { getMessageID } from "./utils";
/**
* Provides APIs to interact with ffmpeg web worker.
*
* @example
* ```ts
* const ffmpeg = new FFmpeg();
* ```
*/
export class FFmpeg extends EventEmitter {
/** @event */ static readonly DOWNLOAD = "download" as const;
/** @event */ static readonly LOG = "log" as const;
/** @event */ static readonly PROGRESS = "progress" as const;
/**
* Listen to download progress events from `ffmpeg.load()`.
*
* @category Event
*/
on(
event: typeof FFmpeg.DOWNLOAD,
listener: (data: DownloadProgressEvent) => void
): this;
/**
* Listen to log events from `ffmpeg.exec()`.
*
* @remarks
* log includes output to stdout and stderr.
*
* @category Event
*/
on(event: typeof FFmpeg.LOG, listener: (log: LogEvent) => void): this;
/**
* Listen to progress events from `ffmpeg.exec()`.
*
* @remarks
* The progress events are accurate only when the length of
* input and output video/audio file are the same.
*
* @category Event
*/
on(
event: typeof FFmpeg.PROGRESS,
listener: (progress: Progress) => void
): this;
on(event: string, listener: any): this {
return this;
}
#worker: Worker;
#resolves: Callbacks = {};
#rejects: Callbacks = {};
constructor() {
super();
this.#worker = new Worker(new URL("./worker.ts", import.meta.url));
this.#registerHandlers();
}
/** register worker message event handlers.
*/
#registerHandlers = () => {
this.#worker.onmessage = ({
data: { id, type, data },
}: FFMessageEventCallback) => {
switch (type) {
case FFMessageType.LOAD:
case FFMessageType.EXEC:
case FFMessageType.WRITE_FILE:
case FFMessageType.READ_FILE:
this.#resolves[id](data);
break;
case FFMessageType.DOWNLOAD:
this.emit(FFmpeg.DOWNLOAD, data as DownloadProgressEvent);
break;
case FFMessageType.LOG:
this.emit(FFmpeg.LOG, data as LogEvent);
break;
case FFMessageType.PROGRESS:
this.emit(FFmpeg.PROGRESS, data as Progress);
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[] = []
): Promise<CallbackData> =>
new Promise((resolve, reject) => {
const id = getMessageID();
this.#worker.postMessage({ id, type, data }, trans);
this.#resolves[id] = resolve;
this.#rejects[id] = reject;
});
/**
* 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 = (config: FFMessageLoadConfig): Promise<IsFirst> =>
this.#send({
type: FFMessageType.LOAD,
data: config,
}) 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
): Promise<number> =>
this.#send({
type: FFMessageType.EXEC,
data: { args, timeout },
}) as Promise<number>;
/**
* Write data to ffmpeg.wasm in memory file system.
*
* @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,
bin: Uint8Array | string
): Promise<IsDone> => {
const trans: Transferable[] = [];
if (bin instanceof Uint8Array) {
trans.push(bin.buffer);
}
return this.#send(
{
type: FFMessageType.WRITE_FILE,
data: { path, bin },
},
trans
) as Promise<IsDone>;
};
/**
* Read data from ffmpeg.wasm in memory file system.
*
* @example
* ```ts
* const ffmpeg = new FFmpeg();
* await ffmpeg.load();
* const data = await ffmpeg.readFile("video.mp4");
* ```
*
* @category File System
*/
public readFile = (path: string): Promise<Uint8Array> =>
this.#send({
type: FFMessageType.READ_FILE,
data: { path },
}) as Promise<Uint8Array>;
}

View File

@ -0,0 +1,18 @@
export const HeaderContentLength = "Content-Length";
export const MIME_TYPE_JAVASCRIPT = "text/javascript";
export const MIME_TYPE_WASM = "application/wasm";
export const CORE_VERSION = "0.12.0";
export const CORE_URL = `https://unpkg.com/@ffmpeg/core@${CORE_VERSION}/dist/umd/ffmpeg-core.js`;
export enum FFMessageType {
LOAD = "load",
WRITE_FILE = "WRITE_FILE",
EXEC = "EXEC",
READ_FILE = "READ_FILE",
ERROR = "ERROR",
DOWNLOAD = "DOWNLOAD",
PROGRESS = "PROGRESS",
LOG = "LOG",
}

View File

@ -0,0 +1,13 @@
export const ERROR_RESPONSE_BODY_READER = new Error(
"failed to get response body reader"
);
export const ERROR_ZERO_CONTENT_LENGTH = new Error(
"failed to get Content-Length"
);
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_INCOMPLETED_DOWNLOAD = new Error(
"failed to complete download"
);

View File

@ -0,0 +1 @@
export * from "./classes";

View File

@ -0,0 +1,128 @@
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, only being loaded when `thread` is `true`.
*
* @defaultValue `https://unpkg.com/@ffmpeg/core-mt@${CORE_VERSION}/dist/umd/ffmpeg-core.worker.js`;
*/
workerURL?: string;
/**
* When `blob` is true, the content of `coreURL`, `wasmURL` and `workerURL`
* will be fetched and convert to blob URL. This avoids problems like CORS
* and provides download progress than can be listened like below:
*
* @example
* ```ts
* const ffmpeg = new FFmpeg();
* ffmpeg.on(FFmpeg.DOWNLOAD, (ev) => {
* console.log(ev);
* })
* await ffmpeg.load();
* ```
*
* @defaultValue `true`
*/
blob?: boolean;
/**
* When `thread` is true, ffmpeg imports `ffmpeg-core.worker.js` and thus
* makes multi-threaded core work.
*
* @defaultValue `false`
*/
thread?: boolean;
}
export interface FFMessageWriteFileData {
path: FFFSPath;
bin: Uint8Array | string;
}
export interface FFMessageExecData {
args: string[];
timeout?: number;
}
export interface FFMessageReadFileData {
path: FFFSPath;
}
export type FFMessageData =
| FFMessageLoadConfig
| FFMessageWriteFileData
| FFMessageExecData
| FFMessageReadFileData;
export interface Message {
type: string;
data?: FFMessageData;
}
export interface FFMessage extends Message {
id: number;
}
export interface FFMessageEvent extends MessageEvent {
data: FFMessage;
}
export interface DownloadProgressEvent {
url: string | URL;
total: number;
received: number;
delta: number;
done: boolean;
}
export interface LogEvent {
type: string;
message: string;
}
export type ExitCode = number;
export type ErrorMessage = string;
export type FileData = Uint8Array;
export type Progress = number;
export type IsFirst = boolean;
export type IsDone = boolean;
export type CallbackData =
| FileData
| ExitCode
| ErrorMessage
| DownloadProgressEvent
| LogEvent
| Progress
| IsFirst
| IsDone
| Error
| undefined;
export interface Callbacks {
[id: number | string]: (data: CallbackData) => void;
}
export interface FFMessageEventCallback {
data: {
id: number;
type: string;
data: CallbackData;
};
}
export type ProgressCallback = (event: DownloadProgressEvent) => void;

View File

@ -0,0 +1,62 @@
import {
ERROR_RESPONSE_BODY_READER,
ERROR_ZERO_CONTENT_LENGTH,
ERROR_INCOMPLETED_DOWNLOAD,
} from "./errors";
import { HeaderContentLength } from "./const";
import { ProgressCallback } from "./types";
/**
* Generate an unique message ID.
*/
export const getMessageID = (() => {
let messageID = 0;
return () => messageID++;
})();
/**
* Download content of a URL with progress.
*/
export const downloadWithProgress = async (
url: string | URL,
cb: ProgressCallback
): Promise<Uint8Array> => {
const resp = await fetch(url);
const reader = resp.body?.getReader();
if (!reader) throw ERROR_RESPONSE_BODY_READER;
const total = parseInt(resp.headers.get(HeaderContentLength) || "0");
if (total === 0) throw ERROR_ZERO_CONTENT_LENGTH;
const data = new Uint8Array(total);
let received = 0;
for (;;) {
const { done, value } = await reader.read();
const delta = value ? value.length : 0;
if (done) {
if (total !== received) throw ERROR_INCOMPLETED_DOWNLOAD;
cb({ url, total, received, delta, done });
break;
}
data.set(value, received);
received += delta;
cb({ url, total, received, delta, done });
}
return data;
};
/**
* Convert an URL to an Blob URL to avoid issues like CORS.
*/
export const toBlobURL = async (
url: string,
/** mime type like `text/javascript` and `application/wasm` */
mimeType: string,
cb: ProgressCallback
): Promise<string> =>
URL.createObjectURL(
new Blob([(await downloadWithProgress(url, cb)).buffer], { type: mimeType })
);

View File

@ -0,0 +1,132 @@
/// <reference no-default-lib="true" />
/// <reference lib="esnext" />
/// <reference lib="webworker" />
import type { FFmpegCoreModule, FFmpegCoreModuleFactory } from "@ffmpeg/types";
import type {
FFMessageEvent,
FFMessageLoadConfig,
FFMessageWriteFileData,
FFMessageExecData,
FFMessageReadFileData,
CallbackData,
IsFirst,
IsDone,
ExitCode,
} from "./types";
import { toBlobURL } from "./utils";
import {
CORE_URL,
FFMessageType,
MIME_TYPE_JAVASCRIPT,
MIME_TYPE_WASM,
} from "./const";
import { ERROR_UNKNOWN_MESSAGE_TYPE, ERROR_NOT_LOADED } from "./errors";
declare global {
interface WorkerGlobalScope {
createFFmpegCore: FFmpegCoreModuleFactory;
}
}
let ffmpeg: FFmpegCoreModule;
const load = async ({
coreURL: _coreURL = CORE_URL,
wasmURL: _wasmURL,
workerURL: _workerURL,
blob = true,
thread = false,
}: FFMessageLoadConfig): Promise<IsFirst> => {
const first = !ffmpeg;
let coreURL = _coreURL;
let wasmURL = _wasmURL ? _wasmURL : _coreURL.replace(/.js$/g, ".wasm");
let workerURL = _workerURL
? _workerURL
: _coreURL.replace(/.js$/g, ".worker.js");
if (blob) {
coreURL = await toBlobURL(coreURL, MIME_TYPE_JAVASCRIPT, (data) =>
self.postMessage({ type: FFMessageType.DOWNLOAD, data })
);
wasmURL = await toBlobURL(wasmURL, MIME_TYPE_WASM, (data) =>
self.postMessage({ type: FFMessageType.DOWNLOAD, data })
);
if (thread) {
try {
workerURL = await toBlobURL(workerURL, MIME_TYPE_JAVASCRIPT, (data) =>
self.postMessage({ type: FFMessageType.DOWNLOAD, data })
);
// eslint-disable-next-line
} catch (e) {}
}
}
importScripts(coreURL);
ffmpeg = await (self as WorkerGlobalScope).createFFmpegCore({
// Fixed `Overload resolution failed.` when using multi-threaded ffmpeg-core.
mainScriptUrlOrBlob: coreURL,
locateFile: (path: string, prefix: string): string => {
if (path.endsWith(".wasm")) return wasmURL;
if (path.endsWith(".worker.js")) return workerURL;
return prefix + path;
},
});
ffmpeg.setLogger((data) =>
self.postMessage({ type: FFMessageType.LOG, data })
);
ffmpeg.setProgress((data: number) =>
self.postMessage({ type: FFMessageType.PROGRESS, data })
);
return first;
};
const writeFile = ({ path, bin }: FFMessageWriteFileData): IsDone => {
ffmpeg.FS.writeFile(path, bin);
return true;
};
const exec = ({ args, timeout = -1 }: FFMessageExecData): ExitCode => {
ffmpeg.setTimeout(timeout);
ffmpeg.exec(...args);
const ret = ffmpeg.ret;
ffmpeg.reset();
return ret;
};
const readFile = ({ path }: FFMessageReadFileData): Uint8Array =>
ffmpeg.FS.readFile(path);
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;
switch (type) {
case FFMessageType.LOAD:
data = await load(_data as FFMessageLoadConfig);
break;
case FFMessageType.WRITE_FILE:
data = writeFile(_data as FFMessageWriteFileData);
break;
case FFMessageType.EXEC:
data = exec(_data as FFMessageExecData);
break;
case FFMessageType.READ_FILE:
data = readFile(_data as FFMessageReadFileData);
break;
default:
throw ERROR_UNKNOWN_MESSAGE_TYPE;
}
} catch (e) {
self.postMessage({ id, type: FFMessageType.ERROR, data: e as Error });
return;
}
if (data instanceof Uint8Array) {
trans.push(data.buffer);
}
self.postMessage({ id, type, data }, trans);
};

View File

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"declaration": true,
"declarationMap": true,
"emitDeclarationOnly": true,
"outFile": "dist/umd/ffmpeg.d.ts"
}
}

View File

@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"rootDir": "src",
"declaration": true,
"outDir": "./dist/esm",
"target": "esnext"
}
}

View File

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"module": "esnext",
"rootDir": "src"
}
}

View File

@ -0,0 +1,27 @@
const path = require("path");
module.exports = {
mode: "production",
devtool: "source-map",
entry: "./src/index.ts",
module: {
rules: [
{
test: /\.ts$/,
loader: "ts-loader",
options: {
transpileOnly: false,
},
},
],
},
resolve: {
extensions: [".ts"],
},
output: {
path: path.resolve(__dirname, "dist/umd"),
filename: "ffmpeg.js",
library: "FFmpegWASM",
libraryTarget: "umd",
},
};

View File

@ -0,0 +1,27 @@
const path = require("path");
module.exports = {
mode: "development",
devtool: "source-map",
entry: "./src/index.ts",
module: {
rules: [
{
test: /\.ts$/,
loader: "ts-loader",
options: {
transpileOnly: false,
},
},
],
},
resolve: {
extensions: [".ts"],
},
output: {
path: path.resolve(__dirname, "dist/umd"),
filename: "ffmpeg.js",
library: "FFmpegWASM",
libraryTarget: "umd",
},
};

View File

@ -0,0 +1,38 @@
{
"name": "@ffmpeg/types",
"version": "0.11.5",
"description": "ffmpeg.wasm types",
"types": "types",
"scripts": {
"lint": "dtslint 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.6.0"
},
"homepage": "https://github.com/ffmpegwasm/ffmpeg.wasm#readme",
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@definitelytyped/dtslint": "^0.0.133"
}
}

39
packages/types/types/index.d.ts vendored Normal file
View File

@ -0,0 +1,39 @@
export type Pointer = number;
export type StringPointer = Pointer;
export type StringArrayPointer = Pointer;
export interface FS {
mkdir: (fileName: string) => void;
readFile: (fileName: string) => Uint8Array;
readdir: (pathName: string) => string[];
unlink: (fileName: string) => void;
writeFile: (fileName: string, binaryData: Uint8Array | string) => void;
}
export interface Log {
type: string;
message: string;
}
export interface FFmpegCoreModule {
DEFAULT_ARGS: string[];
FS: FS;
NULL: Pointer;
SIZE_I32: number;
ret: number;
timeout: number;
mainScriptUrlOrBlob: string;
exec: (...args: string[]) => number;
reset: () => void;
setLogger: (logger: (log: Log) => void) => void;
setTimeout: (timeout: number) => void;
setProgress: (handler: (progress: number) => void) => void;
locateFile: (path: string, prefix: string) => string;
}
export type FFmpegCoreModuleFactory = (
moduleOverrides?: Partial<FFmpegCoreModule>
) => Promise<FFmpegCoreModule>;

View File

@ -0,0 +1,18 @@
{
"compilerOptions": {
"module": "commonjs",
"lib": ["es6"],
"noImplicitAny": true,
"noImplicitThis": true,
"strictFunctionTypes": true,
"strictNullChecks": true,
"types": [],
"noEmit": true,
"forceConsistentCasingInFileNames": true,
// If the library is an external module (uses `export`), this allows your test file to import "mylib" instead of "./index".
// If the library is global (cannot be imported via `import` or `require`), leave this out.
"baseUrl": ".",
"paths": { "@ffmpeg/types": ["."] }
}
}

View File

@ -1707,7 +1707,8 @@ static void print_report(int is_last_report, int64_t timer_start, int64_t cur_ti
}
if (print_stats || is_last_report) {
const char end = is_last_report ? '\n' : '\r';
// Always print a new line of message.
const char end = '\n'; //is_last_report ? '\n' : '\r';
if (print_stats==1 && AV_LOG_INFO > av_log_get_level()) {
fprintf(stderr, "%s %c", buf.str, end);
} else

View File

@ -2,7 +2,7 @@
"extends": "eslint:recommended",
"globals": {
"expect": true,
"createFFmpeg": true,
"createFFmpegCore": true,
"VIDEO_1S_MP4": true,
"FFMPEG_TYPE": true
},

View File

@ -9,13 +9,13 @@
<div id="mocha"></div>
<script src="../node_modules/mocha/mocha.js"></script>
<script src="../node_modules/chai/chai.js"></script>
<script src="../packages/ffmpeg-mt/dist/umd/ffmpeg.js"></script>
<script src="../packages/core-mt/dist/umd/ffmpeg-core.js"></script>
<script src="./constants.js"></script>
<script type="text/javascript">
window.FFMPEG_TYPE = "mt";
</script>
<script>mocha.setup('bdd');</script>
<script src="./ffmpeg.test.js"></script>
<script src="./ffmpeg-core.test.js"></script>
<script>
window.expect = chai.expect;
mocha.run();

View File

@ -9,13 +9,13 @@
<div id="mocha"></div>
<script src="../node_modules/mocha/mocha.js"></script>
<script src="../node_modules/chai/chai.js"></script>
<script src="../packages/ffmpeg/dist/umd/ffmpeg.js"></script>
<script src="../packages/core/dist/umd/ffmpeg-core.js"></script>
<script src="./constants.js"></script>
<script type="text/javascript">
window.FFMPEG_TYPE = "st";
</script>
<script>mocha.setup('bdd');</script>
<script src="./ffmpeg.test.js"></script>
<script src="./ffmpeg-core.test.js"></script>
<script>
window.expect = chai.expect;
mocha.run();

View File

@ -1,6 +1,6 @@
let ffmpeg;
const genName = (name) => `[ffmpeg][${FFMPEG_TYPE}] ${name}`;
const genName = (name) => `[ffmpeg-core][${FFMPEG_TYPE}] ${name}`;
const b64ToUint8Array = (b64) => {
const bin = atob(b64);
@ -19,7 +19,7 @@ const reset = () => {
};
before(async () => {
ffmpeg = await createFFmpeg();
ffmpeg = await createFFmpegCore();
ffmpeg.FS.writeFile("video.mp4", b64ToUint8Array(VIDEO_1S_MP4));
});

View File

@ -2,7 +2,7 @@ const chai = require("chai");
const constants = require("./constants");
global.expect = chai.expect;
global.createFFmpeg = require("../packages/ffmpeg-mt");
global.createFFmpegCore = require("../packages/core-mt");
global.atob = require("./util").atob;
global.FFMPEG_TYPE = "mt";

View File

@ -2,7 +2,7 @@ const chai = require("chai");
const constants = require("./constants");
global.expect = chai.expect;
global.createFFmpeg = require("../packages/ffmpeg");
global.createFFmpegCore = require("../packages/core");
global.atob = require("./util").atob;
global.FFMPEG_TYPE = "st";

View File

@ -4,7 +4,7 @@
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "composite": true, [> Enable constraints that allow a TypeScript project to be used with project references. <]
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
@ -46,7 +46,7 @@
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declaration": true, [> Generate .d.ts files from TypeScript and JavaScript files in your project. <]
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */