Add tests
This commit is contained in:
parent
b496cf1f98
commit
e17812a999
@ -21,8 +21,8 @@
|
||||
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.PROGRESS, ({ progress }) => {
|
||||
message.innerHTML = `${progress * 100} %`;
|
||||
});
|
||||
ffmpeg.on(FFmpeg.LOG, ({ message }) => {
|
||||
console.log(message);
|
||||
|
918
package-lock.json
generated
918
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@ -5,13 +5,18 @@
|
||||
"lint": "npm-run-all lint:*",
|
||||
"lint:packages": "lerna run lint",
|
||||
"lint:root": "eslint tests",
|
||||
"test": "npm-run-all test:*:*",
|
||||
"test:browser": "mocha-headless-chrome -a allow-file-access-from-files -a enable-features=SharedArrayBuffer",
|
||||
"test:browser:mt": "npm run test:browser -- -f tests/*-mt.test.html",
|
||||
"test:browser:st": "npm run test:browser -- -f tests/*-st.test.html",
|
||||
"pretest": "lerna run build",
|
||||
"test": "server-test test:browser:server http://localhost:3000 test:all",
|
||||
"test:all": "npm-run-all test:*:*:*",
|
||||
"test:browser": "mocha-headless-chrome -t 60000 -a allow-file-access-from-files -a enable-features=SharedArrayBuffer",
|
||||
"test:browser:core:mt": "npm run test:browser -- -f tests/ffmpeg-core-mt.test.html",
|
||||
"test:browser:core:st": "npm run test:browser -- -f tests/ffmpeg-core-st.test.html",
|
||||
"test:browser:ffmpeg:mt": "npm run test:browser -- -f tests/ffmpeg-mt.test.html",
|
||||
"test:browser:ffmpeg:st": "npm run test:browser -- -f tests/ffmpeg-st.test.html",
|
||||
"test:browser:server": "http-server -c-1 --cors -p 3000 .",
|
||||
"test:node": "mocha --exit --bail",
|
||||
"test:node:mt": "npm run test:node -- --require tests/test-helper-mt.js tests/*.test.js",
|
||||
"test:node:st": "npm run test:node -- --require tests/test-helper-st.js tests/*.test.js"
|
||||
"test:node:core:mt": "npm run test:node -- --require tests/test-helper-mt.js tests/ffmpeg-core.test.js",
|
||||
"test:node:core:st": "npm run test:node -- --require tests/test-helper-st.js tests/ffmpeg-core.test.js"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
@ -19,9 +24,11 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"chai": "^4.3.6",
|
||||
"http-server": "^14.1.1",
|
||||
"lerna": "^5.4.3",
|
||||
"mocha": "^10.0.0",
|
||||
"mocha-headless-chrome": "^4.0.0",
|
||||
"npm-run-all": "^4.1.5"
|
||||
"npm-run-all": "^4.1.5",
|
||||
"start-server-and-test": "^1.14.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@ffmpeg/ffmpeg",
|
||||
"version": "0.11.5",
|
||||
"description": "FFmpeg WebAssembly version",
|
||||
"description": "FFmpeg WebAssembly version for browser",
|
||||
"main": "./dist/umd/ffmpeg.js",
|
||||
"types": "./dist/umd/ffmpeg.d.ts",
|
||||
"exports": {
|
||||
@ -12,14 +12,15 @@
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "webpack --watch -c webpack.dev.config.js",
|
||||
"dev": "webpack -w --mode development",
|
||||
"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"
|
||||
"docs": "typedoc --entryPointStrategy expand ./src",
|
||||
"docs:serve": "http-server docs"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
@ -52,6 +53,7 @@
|
||||
"@typescript-eslint/eslint-plugin": "^5.37.0",
|
||||
"@typescript-eslint/parser": "^5.37.0",
|
||||
"eslint": "^8.23.1",
|
||||
"http-server": "^14.1.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-loader": "^9.4.1",
|
||||
|
@ -4,15 +4,73 @@ import {
|
||||
CallbackData,
|
||||
Callbacks,
|
||||
DownloadProgressEvent,
|
||||
FFFSPaths,
|
||||
FFMessageEventCallback,
|
||||
FFMessageLoadConfig,
|
||||
IsDone,
|
||||
OK,
|
||||
IsFirst,
|
||||
LogEvent,
|
||||
Message,
|
||||
Progress,
|
||||
FileData,
|
||||
} from "./types";
|
||||
import { getMessageID } from "./utils";
|
||||
import { ERROR_TERMINATED, ERROR_NOT_LOADED } from "./errors";
|
||||
|
||||
export declare interface FFmpeg {
|
||||
/**
|
||||
* Listen to download progress events from `ffmpeg.load()`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* ffmpeg.on(FFmpeg.DOWNLOAD, ({ url, total, received, delta, done }) => {
|
||||
* // ...
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @category Event
|
||||
*/
|
||||
on(
|
||||
event: typeof FFmpeg.DOWNLOAD,
|
||||
listener: (data: DownloadProgressEvent) => void
|
||||
): this;
|
||||
/**
|
||||
* Listen to log events from `ffmpeg.exec()`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* ffmpeg.on(FFmpeg.LOG, ({ message }) => {
|
||||
* // ...
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @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()`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* ffmpeg.on(FFmpeg.PROGRESS, ({ progress }) => {
|
||||
* // ...
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides APIs to interact with ffmpeg web worker.
|
||||
@ -27,80 +85,56 @@ export class FFmpeg extends EventEmitter {
|
||||
/** @event */ static readonly LOG = "log" as const;
|
||||
/** @event */ static readonly PROGRESS = "progress" as const;
|
||||
|
||||
#worker: Worker | null = null;
|
||||
/**
|
||||
* Listen to download progress events from `ffmpeg.load()`.
|
||||
* #resolves and #rejects tracks Promise resolves and rejects to
|
||||
* be called when we receive message from web worker.
|
||||
*
|
||||
* @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.
|
||||
/**
|
||||
* 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];
|
||||
};
|
||||
if (this.#worker) {
|
||||
this.#worker.onmessage = ({
|
||||
data: { id, type, data },
|
||||
}: FFMessageEventCallback) => {
|
||||
switch (type) {
|
||||
case FFMessageType.LOAD:
|
||||
case FFMessageType.EXEC:
|
||||
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.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];
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -109,13 +143,18 @@ export class FFmpeg extends EventEmitter {
|
||||
#send = (
|
||||
{ type, data }: Message,
|
||||
trans: Transferable[] = []
|
||||
): Promise<CallbackData> =>
|
||||
new Promise((resolve, reject) => {
|
||||
): Promise<CallbackData> => {
|
||||
if (!this.#worker) {
|
||||
return Promise.reject(ERROR_NOT_LOADED);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = getMessageID();
|
||||
this.#worker.postMessage({ id, type, data }, trans);
|
||||
this.#worker && 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
|
||||
@ -124,11 +163,16 @@ export class FFmpeg extends EventEmitter {
|
||||
* @category FFmpeg
|
||||
* @returns `true` if ffmpeg core is loaded for the first time.
|
||||
*/
|
||||
public load = (config: FFMessageLoadConfig): Promise<IsFirst> =>
|
||||
this.#send({
|
||||
public load = (config: FFMessageLoadConfig = {}): Promise<IsFirst> => {
|
||||
if (!this.#worker) {
|
||||
this.#worker = new Worker(new URL("./worker.ts", import.meta.url));
|
||||
this.#registerHandlers();
|
||||
}
|
||||
return this.#send({
|
||||
type: FFMessageType.LOAD,
|
||||
data: config,
|
||||
}) as Promise<IsFirst>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Execute ffmpeg command.
|
||||
@ -166,7 +210,28 @@ export class FFmpeg extends EventEmitter {
|
||||
}) as Promise<number>;
|
||||
|
||||
/**
|
||||
* Write data to ffmpeg.wasm in memory file system.
|
||||
* 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;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Write data to ffmpeg.wasm.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
@ -178,25 +243,22 @@ export class FFmpeg extends EventEmitter {
|
||||
*
|
||||
* @category File System
|
||||
*/
|
||||
public writeFile = (
|
||||
path: string,
|
||||
bin: Uint8Array | string
|
||||
): Promise<IsDone> => {
|
||||
public writeFile = (path: string, data: FileData): Promise<OK> => {
|
||||
const trans: Transferable[] = [];
|
||||
if (bin instanceof Uint8Array) {
|
||||
trans.push(bin.buffer);
|
||||
if (data instanceof Uint8Array) {
|
||||
trans.push(data.buffer);
|
||||
}
|
||||
return this.#send(
|
||||
{
|
||||
type: FFMessageType.WRITE_FILE,
|
||||
data: { path, bin },
|
||||
data: { path, data },
|
||||
},
|
||||
trans
|
||||
) as Promise<IsDone>;
|
||||
) as Promise<OK>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Read data from ffmpeg.wasm in memory file system.
|
||||
* Read data from ffmpeg.wasm.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
@ -207,9 +269,74 @@ export class FFmpeg extends EventEmitter {
|
||||
*
|
||||
* @category File System
|
||||
*/
|
||||
public readFile = (path: string): Promise<Uint8Array> =>
|
||||
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"
|
||||
): Promise<FileData> =>
|
||||
this.#send({
|
||||
type: FFMessageType.READ_FILE,
|
||||
data: { path, encoding },
|
||||
}) as Promise<FileData>;
|
||||
|
||||
/**
|
||||
* Delete a file.
|
||||
*
|
||||
* @category File System
|
||||
*/
|
||||
public deleteFile = (path: string): Promise<OK> =>
|
||||
this.#send({
|
||||
type: FFMessageType.DELETE_FILE,
|
||||
data: { path },
|
||||
}) as Promise<Uint8Array>;
|
||||
}) as Promise<OK>;
|
||||
|
||||
/**
|
||||
* Rename a file or directory.
|
||||
*
|
||||
* @category File System
|
||||
*/
|
||||
public rename = (oldPath: string, newPath: string): Promise<OK> =>
|
||||
this.#send({
|
||||
type: FFMessageType.RENAME,
|
||||
data: { oldPath, newPath },
|
||||
}) as Promise<OK>;
|
||||
|
||||
/**
|
||||
* Create a directory.
|
||||
*
|
||||
* @category File System
|
||||
*/
|
||||
public createDir = (path: string): Promise<OK> =>
|
||||
this.#send({
|
||||
type: FFMessageType.CREATE_DIR,
|
||||
data: { path },
|
||||
}) as Promise<OK>;
|
||||
|
||||
/**
|
||||
* List directory contents.
|
||||
*
|
||||
* @category File System
|
||||
*/
|
||||
public listDir = (path: string): Promise<FFFSPaths> =>
|
||||
this.#send({
|
||||
type: FFMessageType.LIST_DIR,
|
||||
data: { path },
|
||||
}) as Promise<FFFSPaths>;
|
||||
|
||||
/**
|
||||
* Delete an empty directory.
|
||||
*
|
||||
* @category File System
|
||||
*/
|
||||
public deleteDir = (path: string): Promise<OK> =>
|
||||
this.#send({
|
||||
type: FFMessageType.DELETE_DIR,
|
||||
data: { path },
|
||||
}) as Promise<OK>;
|
||||
}
|
||||
|
@ -3,13 +3,18 @@ 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 const CORE_URL = `https://unpkg.com/@ffmpeg/core@${CORE_VERSION}/dist/ffmpeg-core.js`;
|
||||
|
||||
export enum FFMessageType {
|
||||
LOAD = "load",
|
||||
WRITE_FILE = "WRITE_FILE",
|
||||
EXEC = "EXEC",
|
||||
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",
|
||||
|
@ -11,3 +11,4 @@ export const ERROR_NOT_LOADED = new Error(
|
||||
export const ERROR_INCOMPLETED_DOWNLOAD = new Error(
|
||||
"failed to complete download"
|
||||
);
|
||||
export const ERROR_TERMINATED = new Error("called FFmpeg.terminate()");
|
||||
|
@ -1,4 +1,5 @@
|
||||
export type FFFSPath = string;
|
||||
export type FFFSPaths = FFFSPath[];
|
||||
|
||||
/**
|
||||
* ffmpeg-core loading configuration.
|
||||
@ -48,25 +49,56 @@ export interface FFMessageLoadConfig {
|
||||
thread?: boolean;
|
||||
}
|
||||
|
||||
export interface FFMessageWriteFileData {
|
||||
path: FFFSPath;
|
||||
bin: Uint8Array | 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 type FFMessageData =
|
||||
| FFMessageLoadConfig
|
||||
| FFMessageWriteFileData
|
||||
| FFMessageExecData
|
||||
| FFMessageReadFileData;
|
||||
| FFMessageWriteFileData
|
||||
| FFMessageReadFileData
|
||||
| FFMessageDeleteFileData
|
||||
| FFMessageRenameData
|
||||
| FFMessageCreateDirData
|
||||
| FFMessageListDirData
|
||||
| FFMessageDeleteDirData;
|
||||
|
||||
export interface Message {
|
||||
type: string;
|
||||
@ -94,12 +126,15 @@ export interface LogEvent {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface Progress {
|
||||
progress: number;
|
||||
}
|
||||
|
||||
export type ExitCode = number;
|
||||
export type ErrorMessage = string;
|
||||
export type FileData = Uint8Array;
|
||||
export type Progress = number;
|
||||
export type FileData = Uint8Array | string;
|
||||
export type IsFirst = boolean;
|
||||
export type IsDone = boolean;
|
||||
export type OK = boolean;
|
||||
|
||||
export type CallbackData =
|
||||
| FileData
|
||||
@ -109,8 +144,9 @@ export type CallbackData =
|
||||
| LogEvent
|
||||
| Progress
|
||||
| IsFirst
|
||||
| IsDone
|
||||
| OK
|
||||
| Error
|
||||
| FFFSPaths
|
||||
| undefined;
|
||||
|
||||
export interface Callbacks {
|
||||
|
@ -16,36 +16,56 @@ export const getMessageID = (() => {
|
||||
|
||||
/**
|
||||
* 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<Uint8Array> => {
|
||||
): Promise<ArrayBuffer> => {
|
||||
const resp = await fetch(url);
|
||||
const reader = resp.body?.getReader();
|
||||
if (!reader) throw ERROR_RESPONSE_BODY_READER;
|
||||
let buf;
|
||||
|
||||
const total = parseInt(resp.headers.get(HeaderContentLength) || "0");
|
||||
if (total === 0) throw ERROR_ZERO_CONTENT_LENGTH;
|
||||
try {
|
||||
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;
|
||||
const reader = resp.body?.getReader();
|
||||
if (!reader) throw ERROR_RESPONSE_BODY_READER;
|
||||
|
||||
if (done) {
|
||||
if (total !== received) throw ERROR_INCOMPLETED_DOWNLOAD;
|
||||
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 });
|
||||
break;
|
||||
}
|
||||
|
||||
data.set(value, received);
|
||||
received += delta;
|
||||
cb({ url, total, received, delta, done });
|
||||
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({
|
||||
url,
|
||||
total: buf.byteLength,
|
||||
received: buf.byteLength,
|
||||
delta: 0,
|
||||
done: true,
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
return buf;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -58,5 +78,7 @@ export const toBlobURL = async (
|
||||
cb: ProgressCallback
|
||||
): Promise<string> =>
|
||||
URL.createObjectURL(
|
||||
new Blob([(await downloadWithProgress(url, cb)).buffer], { type: mimeType })
|
||||
new Blob([await downloadWithProgress(url, cb)], {
|
||||
type: mimeType,
|
||||
})
|
||||
);
|
||||
|
@ -6,13 +6,20 @@ import type { FFmpegCoreModule, FFmpegCoreModuleFactory } from "@ffmpeg/types";
|
||||
import type {
|
||||
FFMessageEvent,
|
||||
FFMessageLoadConfig,
|
||||
FFMessageWriteFileData,
|
||||
FFMessageExecData,
|
||||
FFMessageWriteFileData,
|
||||
FFMessageReadFileData,
|
||||
FFMessageDeleteFileData,
|
||||
FFMessageRenameData,
|
||||
FFMessageCreateDirData,
|
||||
FFMessageListDirData,
|
||||
FFMessageDeleteDirData,
|
||||
CallbackData,
|
||||
IsFirst,
|
||||
IsDone,
|
||||
OK,
|
||||
ExitCode,
|
||||
FFFSPaths,
|
||||
FileData,
|
||||
} from "./types";
|
||||
import { toBlobURL } from "./utils";
|
||||
import {
|
||||
@ -53,18 +60,15 @@ const load = async ({
|
||||
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) {}
|
||||
workerURL = await toBlobURL(workerURL, MIME_TYPE_JAVASCRIPT, (data) =>
|
||||
self.postMessage({ type: FFMessageType.DOWNLOAD, data })
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
importScripts(coreURL);
|
||||
ffmpeg = await (self as WorkerGlobalScope).createFFmpegCore({
|
||||
// Fixed `Overload resolution failed.` when using multi-threaded ffmpeg-core.
|
||||
// Fix `Overload resolution failed.` when using multi-threaded ffmpeg-core.
|
||||
mainScriptUrlOrBlob: coreURL,
|
||||
locateFile: (path: string, prefix: string): string => {
|
||||
if (path.endsWith(".wasm")) return wasmURL;
|
||||
@ -75,17 +79,12 @@ const load = async ({
|
||||
ffmpeg.setLogger((data) =>
|
||||
self.postMessage({ type: FFMessageType.LOG, data })
|
||||
);
|
||||
ffmpeg.setProgress((data: number) =>
|
||||
self.postMessage({ type: FFMessageType.PROGRESS, data })
|
||||
ffmpeg.setProgress((progress: number) =>
|
||||
self.postMessage({ type: FFMessageType.PROGRESS, data: { progress } })
|
||||
);
|
||||
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);
|
||||
@ -94,8 +93,40 @@ const exec = ({ args, timeout = -1 }: FFMessageExecData): ExitCode => {
|
||||
return ret;
|
||||
};
|
||||
|
||||
const readFile = ({ path }: FFMessageReadFileData): Uint8Array =>
|
||||
ffmpeg.FS.readFile(path);
|
||||
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): FFFSPaths => {
|
||||
return ffmpeg.FS.readdir(path);
|
||||
};
|
||||
|
||||
// TODO: check if deletion works.
|
||||
const deleteDir = ({ path }: FFMessageDeleteDirData): OK => {
|
||||
ffmpeg.FS.rmdir(path);
|
||||
return true;
|
||||
};
|
||||
|
||||
self.onmessage = async ({
|
||||
data: { id, type, data: _data },
|
||||
@ -109,15 +140,30 @@ self.onmessage = async ({
|
||||
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.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;
|
||||
default:
|
||||
throw ERROR_UNKNOWN_MESSAGE_TYPE;
|
||||
}
|
||||
|
@ -1,27 +0,0 @@
|
||||
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",
|
||||
},
|
||||
};
|
16
packages/types/types/index.d.ts
vendored
16
packages/types/types/index.d.ts
vendored
@ -4,12 +4,18 @@ export type Pointer = number;
|
||||
export type StringPointer = Pointer;
|
||||
export type StringArrayPointer = Pointer;
|
||||
|
||||
export interface ReadFileOptions {
|
||||
encdoing: string;
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
export interface Log {
|
||||
|
@ -1,10 +1,13 @@
|
||||
{
|
||||
"extends": "eslint:recommended",
|
||||
"globals": {
|
||||
"expect": true,
|
||||
"createFFmpegCore": true,
|
||||
"CORE_URL": true,
|
||||
"FFMPEG_TYPE": true,
|
||||
"FFmpegWASM": true,
|
||||
"VIDEO_1S_MP4": true,
|
||||
"FFMPEG_TYPE": true
|
||||
"b64ToUint8Array": true,
|
||||
"createFFmpegCore": true,
|
||||
"expect": true
|
||||
},
|
||||
"env": {
|
||||
"node": true,
|
||||
|
@ -10,7 +10,7 @@
|
||||
<script src="../node_modules/mocha/mocha.js"></script>
|
||||
<script src="../node_modules/chai/chai.js"></script>
|
||||
<script src="../packages/core-mt/dist/umd/ffmpeg-core.js"></script>
|
||||
<script src="./constants.js"></script>
|
||||
<script src="./test-helper-browser.js"></script>
|
||||
<script type="text/javascript">
|
||||
window.FFMPEG_TYPE = "mt";
|
||||
</script>
|
||||
|
@ -10,7 +10,7 @@
|
||||
<script src="../node_modules/mocha/mocha.js"></script>
|
||||
<script src="../node_modules/chai/chai.js"></script>
|
||||
<script src="../packages/core/dist/umd/ffmpeg-core.js"></script>
|
||||
<script src="./constants.js"></script>
|
||||
<script src="./test-helper-browser.js"></script>
|
||||
<script type="text/javascript">
|
||||
window.FFMPEG_TYPE = "st";
|
||||
</script>
|
||||
|
@ -1,31 +1,21 @@
|
||||
let ffmpeg;
|
||||
let core;
|
||||
|
||||
const genName = (name) => `[ffmpeg-core][${FFMPEG_TYPE}] ${name}`;
|
||||
|
||||
const b64ToUint8Array = (b64) => {
|
||||
const bin = atob(b64);
|
||||
const len = bin.length;
|
||||
const bytes = new Uint8Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
bytes[i] = bin.charCodeAt(i);
|
||||
}
|
||||
return bytes;
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
ffmpeg.reset();
|
||||
ffmpeg.setLogger(() => {});
|
||||
ffmpeg.setProgress(() => {});
|
||||
core.reset();
|
||||
core.setLogger(() => {});
|
||||
core.setProgress(() => {});
|
||||
};
|
||||
|
||||
before(async () => {
|
||||
ffmpeg = await createFFmpegCore();
|
||||
ffmpeg.FS.writeFile("video.mp4", b64ToUint8Array(VIDEO_1S_MP4));
|
||||
core = await createFFmpegCore();
|
||||
core.FS.writeFile("video.mp4", b64ToUint8Array(VIDEO_1S_MP4));
|
||||
});
|
||||
|
||||
describe(genName("createFFmpeg()"), () => {
|
||||
it("should be OK", () => {
|
||||
expect(ffmpeg).to.be.ok;
|
||||
expect(core).to.be.ok;
|
||||
});
|
||||
});
|
||||
|
||||
@ -33,16 +23,16 @@ describe(genName("reset()"), () => {
|
||||
beforeEach(reset);
|
||||
|
||||
it("should exist", () => {
|
||||
expect("reset" in ffmpeg).to.be.true;
|
||||
expect("reset" in core).to.be.true;
|
||||
});
|
||||
it("should reset ret and timeout", () => {
|
||||
ffmpeg.ret = 1024;
|
||||
ffmpeg.timeout = 1024;
|
||||
core.ret = 1024;
|
||||
core.timeout = 1024;
|
||||
|
||||
ffmpeg.reset();
|
||||
core.reset();
|
||||
|
||||
expect(ffmpeg.ret).to.equal(-1);
|
||||
expect(ffmpeg.timeout).to.equal(-1);
|
||||
expect(core.ret).to.equal(-1);
|
||||
expect(core.timeout).to.equal(-1);
|
||||
});
|
||||
});
|
||||
|
||||
@ -50,18 +40,18 @@ describe(genName("exec()"), () => {
|
||||
beforeEach(reset);
|
||||
|
||||
it("should exist", () => {
|
||||
expect("exec" in ffmpeg).to.be.true;
|
||||
expect("exec" in core).to.be.true;
|
||||
});
|
||||
|
||||
it("should output help", () => {
|
||||
expect(ffmpeg.exec("-h")).to.equal(0);
|
||||
expect(core.exec("-h")).to.equal(0);
|
||||
});
|
||||
|
||||
it("should transcode", () => {
|
||||
expect(ffmpeg.exec("-i", "video.mp4", "video.avi")).to.equal(0);
|
||||
const out = ffmpeg.FS.readFile("video.avi");
|
||||
expect(core.exec("-i", "video.mp4", "video.avi")).to.equal(0);
|
||||
const out = core.FS.readFile("video.avi");
|
||||
expect(out.length).to.not.equal(0);
|
||||
ffmpeg.FS.unlink("video.avi");
|
||||
core.FS.unlink("video.avi");
|
||||
});
|
||||
});
|
||||
|
||||
@ -69,12 +59,12 @@ describe(genName("setTimeout()"), () => {
|
||||
beforeEach(reset);
|
||||
|
||||
it("should exist", () => {
|
||||
expect("setTimeout" in ffmpeg).to.be.true;
|
||||
expect("setTimeout" in core).to.be.true;
|
||||
});
|
||||
|
||||
it("should timeout", () => {
|
||||
ffmpeg.setTimeout(1); // timeout after 1ms
|
||||
expect(ffmpeg.exec("-i", "video.mp4", "video.avi")).to.equal(1);
|
||||
core.setTimeout(1); // timeout after 1ms
|
||||
expect(core.exec("-i", "video.mp4", "video.avi")).to.equal(1);
|
||||
});
|
||||
});
|
||||
|
||||
@ -82,13 +72,13 @@ describe(genName("setLogger()"), () => {
|
||||
beforeEach(reset);
|
||||
|
||||
it("should exist", () => {
|
||||
expect("setLogger" in ffmpeg).to.be.true;
|
||||
expect("setLogger" in core).to.be.true;
|
||||
});
|
||||
|
||||
it("should handle logs", () => {
|
||||
const logs = [];
|
||||
ffmpeg.setLogger(({ message }) => logs.push(message));
|
||||
ffmpeg.exec("-h");
|
||||
core.setLogger(({ message }) => logs.push(message));
|
||||
core.exec("-h");
|
||||
expect(logs.length).to.not.equal(0);
|
||||
});
|
||||
});
|
||||
@ -97,14 +87,14 @@ describe(genName("setProgress()"), () => {
|
||||
beforeEach(reset);
|
||||
|
||||
it("should exist", () => {
|
||||
expect("setProgress" in ffmpeg).to.be.true;
|
||||
expect("setProgress" in core).to.be.true;
|
||||
});
|
||||
|
||||
it("should handle progress", () => {
|
||||
let progress = 0;
|
||||
ffmpeg.setProgress((_progress) => (progress = _progress));
|
||||
expect(ffmpeg.exec("-i", "video.mp4", "video.avi")).to.equal(0);
|
||||
core.setProgress((_progress) => (progress = _progress));
|
||||
expect(core.exec("-i", "video.mp4", "video.avi")).to.equal(0);
|
||||
expect(progress).to.equal(1);
|
||||
ffmpeg.FS.unlink("video.avi");
|
||||
core.FS.unlink("video.avi");
|
||||
});
|
||||
});
|
||||
|
25
tests/ffmpeg-mt.test.html
Normal file
25
tests/ffmpeg-mt.test.html
Normal file
@ -0,0 +1,25 @@
|
||||
<!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="../packages/ffmpeg/dist/umd/ffmpeg.js"></script>
|
||||
<script src="./test-helper-browser.js"></script>
|
||||
<script type="text/javascript">
|
||||
window.FFMPEG_TYPE = "mt";
|
||||
window.CORE_URL = "http://localhost:3000/packages/core-mt/dist/umd/ffmpeg-core.js";
|
||||
</script>
|
||||
<script>mocha.setup('bdd');</script>
|
||||
<script src="./ffmpeg.test.js"></script>
|
||||
<script>
|
||||
window.expect = chai.expect;
|
||||
mocha.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
25
tests/ffmpeg-st.test.html
Normal file
25
tests/ffmpeg-st.test.html
Normal file
@ -0,0 +1,25 @@
|
||||
<!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="../packages/ffmpeg/dist/umd/ffmpeg.js"></script>
|
||||
<script src="./test-helper-browser.js"></script>
|
||||
<script type="text/javascript">
|
||||
window.FFMPEG_TYPE = "st";
|
||||
window.CORE_URL = "http://localhost:3000/packages/core/dist/umd/ffmpeg-core.js";
|
||||
</script>
|
||||
<script>mocha.setup('bdd');</script>
|
||||
<script src="./ffmpeg.test.js"></script>
|
||||
<script>
|
||||
window.expect = chai.expect;
|
||||
mocha.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
171
tests/ffmpeg.test.js
Normal file
171
tests/ffmpeg.test.js
Normal file
@ -0,0 +1,171 @@
|
||||
const { FFmpeg } = FFmpegWASM;
|
||||
|
||||
const genName = (name) => `[ffmpeg][${FFMPEG_TYPE}] ${name}`;
|
||||
|
||||
const createFFmpeg = async () => {
|
||||
const ffmpeg = new FFmpeg();
|
||||
await ffmpeg.load({
|
||||
coreURL: CORE_URL,
|
||||
thread: FFMPEG_TYPE === "mt",
|
||||
});
|
||||
return ffmpeg;
|
||||
};
|
||||
|
||||
describe(genName("new FFmpeg()"), () => {
|
||||
it("should be OK", () => {
|
||||
expect(new FFmpeg()).to.be.ok;
|
||||
});
|
||||
});
|
||||
|
||||
describe(genName("FFmpeg.load()"), function () {
|
||||
// it("should work without any args", async () => {
|
||||
// const ffmpeg = new FFmpeg();
|
||||
// await ffmpeg.load();
|
||||
// expect(ffmpeg).to.be.ok;
|
||||
// });
|
||||
|
||||
it("should work when blob is false", async () => {
|
||||
const ffmpeg = new FFmpeg();
|
||||
await ffmpeg.load({
|
||||
coreURL: CORE_URL,
|
||||
blob: false,
|
||||
thread: FFMPEG_TYPE === "mt",
|
||||
});
|
||||
expect(ffmpeg).to.be.ok;
|
||||
ffmpeg.terminate();
|
||||
});
|
||||
|
||||
it("should receive download progress events", async () => {
|
||||
const ffmpeg = new FFmpeg();
|
||||
let done = false;
|
||||
ffmpeg.on(FFmpeg.DOWNLOAD, ({ done: _done }) => {
|
||||
done = _done;
|
||||
});
|
||||
await ffmpeg.load({
|
||||
coreURL: CORE_URL,
|
||||
thread: FFMPEG_TYPE === "mt",
|
||||
});
|
||||
expect(done).to.be.true;
|
||||
ffmpeg.terminate();
|
||||
});
|
||||
});
|
||||
|
||||
describe(
|
||||
genName(
|
||||
"FFmpeg directory APIs (createDir(), listDir(), deleteDir(), rename())"
|
||||
),
|
||||
function () {
|
||||
let ffmpeg;
|
||||
|
||||
before(async () => {
|
||||
ffmpeg = await createFFmpeg();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
ffmpeg.terminate();
|
||||
});
|
||||
|
||||
it("should list root dir", async () => {
|
||||
const files = await ffmpeg.listDir("/");
|
||||
expect(files).to.have.lengthOf(6);
|
||||
});
|
||||
|
||||
it("should create a dir", async () => {
|
||||
await ffmpeg.createDir("/dir1");
|
||||
const files = await ffmpeg.listDir("/");
|
||||
expect(files).to.include("dir1");
|
||||
});
|
||||
|
||||
it("should delete a dir", async () => {
|
||||
await ffmpeg.createDir("/dir2");
|
||||
await ffmpeg.deleteDir("/dir2");
|
||||
const files = await ffmpeg.listDir("/");
|
||||
expect(files).to.not.include("dir2");
|
||||
});
|
||||
|
||||
it("should rename a dir", async () => {
|
||||
await ffmpeg.createDir("/dir3");
|
||||
await ffmpeg.rename("/dir3", "/dir4");
|
||||
const files = await ffmpeg.listDir("/");
|
||||
expect(files).to.not.include("dir3");
|
||||
expect(files).to.include("dir4");
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
describe(
|
||||
genName(
|
||||
"FFmpeg files APIs (readFile(), writeFile(), deleteFile(), rename())"
|
||||
),
|
||||
function () {
|
||||
let ffmpeg;
|
||||
|
||||
before(async () => {
|
||||
ffmpeg = await createFFmpeg();
|
||||
});
|
||||
|
||||
after(() => {
|
||||
ffmpeg.terminate();
|
||||
});
|
||||
|
||||
it("should write/read a text file", async () => {
|
||||
const text = "foo";
|
||||
await ffmpeg.writeFile("/file1", text);
|
||||
const data = await ffmpeg.readFile("/file1", "utf8");
|
||||
const files = await ffmpeg.listDir("/");
|
||||
expect(files).to.include("file1");
|
||||
expect(data).to.equal(text);
|
||||
});
|
||||
|
||||
it("should write a binary file", async () => {
|
||||
const bin = [1, 2, 3];
|
||||
await ffmpeg.writeFile("/file2", Uint8Array.from(bin));
|
||||
const data = await ffmpeg.readFile("/file2");
|
||||
const files = await ffmpeg.listDir("/");
|
||||
expect(files).to.include("file2");
|
||||
expect(data).to.deep.equal(Uint8Array.from(bin));
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
describe(genName("FFmpeg.exec()"), function () {
|
||||
let ffmpeg;
|
||||
|
||||
before(async () => {
|
||||
ffmpeg = await createFFmpeg();
|
||||
await ffmpeg.writeFile("video.mp4", b64ToUint8Array(VIDEO_1S_MP4));
|
||||
});
|
||||
|
||||
after(() => {
|
||||
ffmpeg.terminate();
|
||||
});
|
||||
|
||||
it("should output help with exit code 0", async () => {
|
||||
let m;
|
||||
const listener = ({ message }) => {
|
||||
m = message;
|
||||
};
|
||||
ffmpeg.on(FFmpeg.LOG, listener);
|
||||
const ret = await ffmpeg.exec(["-h"]);
|
||||
expect(ret).to.equal(0);
|
||||
expect(m).to.be.a("string");
|
||||
ffmpeg.removeListener(FFmpeg.LOG, listener);
|
||||
});
|
||||
|
||||
it("should transcode mp4 to avi", async () => {
|
||||
let p;
|
||||
const listener = ({ progress }) => {
|
||||
p = progress;
|
||||
};
|
||||
ffmpeg.on(FFmpeg.PROGRESS, listener);
|
||||
const ret = await ffmpeg.exec(["-i", "video.mp4", "video.avi"]);
|
||||
expect(ret).to.equal(0);
|
||||
expect(p).to.equal(1);
|
||||
ffmpeg.removeListener(FFmpeg.PROGRESS, listener);
|
||||
});
|
||||
|
||||
it("should stop if timeout", async () => {
|
||||
const ret = await ffmpeg.exec(["-i", "video.mp4", "video.avi"], 1);
|
||||
expect(ret).to.equal(1);
|
||||
});
|
||||
});
|
@ -1,7 +1,19 @@
|
||||
const VIDEO_1S_MP4 =
|
||||
"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";
|
||||
|
||||
const b64ToUint8Array = (b64) => {
|
||||
const bin = atob(b64);
|
||||
const len = bin.length;
|
||||
const bytes = new Uint8Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
bytes[i] = bin.charCodeAt(i);
|
||||
}
|
||||
return bytes;
|
||||
};
|
||||
|
||||
if (typeof module !== "undefined") {
|
||||
module.exports = {
|
||||
VIDEO_1S_MP4,
|
||||
b64ToUint8Array,
|
||||
};
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
const chai = require("chai");
|
||||
const constants = require("./constants");
|
||||
const browser = require("./test-helper-browser");
|
||||
|
||||
global.expect = chai.expect;
|
||||
global.createFFmpegCore = require("../packages/core-mt");
|
||||
global.atob = require("./util").atob;
|
||||
global.FFMPEG_TYPE = "mt";
|
||||
|
||||
Object.keys(constants).forEach((key) => {
|
||||
global[key] = constants[key];
|
||||
Object.keys(browser).forEach((key) => {
|
||||
global[key] = browser[key];
|
||||
});
|
||||
|
@ -1,11 +1,11 @@
|
||||
const chai = require("chai");
|
||||
const constants = require("./constants");
|
||||
const browser = require("./test-helper-browser");
|
||||
|
||||
global.expect = chai.expect;
|
||||
global.createFFmpegCore = require("../packages/core");
|
||||
global.atob = require("./util").atob;
|
||||
global.FFMPEG_TYPE = "st";
|
||||
|
||||
Object.keys(constants).forEach((key) => {
|
||||
global[key] = constants[key];
|
||||
Object.keys(browser).forEach((key) => {
|
||||
global[key] = browser[key];
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user