Update
This commit is contained in:
13
public/ffmpeg_packages/util/.eslintrc.cjs
Executable file
13
public/ffmpeg_packages/util/.eslintrc.cjs
Executable file
@@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/recommended-requiring-type-checking",
|
||||
],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ["./tsconfig.json"],
|
||||
},
|
||||
plugins: ["@typescript-eslint"],
|
||||
};
|
||||
1
public/ffmpeg_packages/util/.gitignore
vendored
Executable file
1
public/ffmpeg_packages/util/.gitignore
vendored
Executable file
@@ -0,0 +1 @@
|
||||
dist/
|
||||
57
public/ffmpeg_packages/util/package.json
Executable file
57
public/ffmpeg_packages/util/package.json
Executable file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "@ffmpeg/util",
|
||||
"version": "0.12.2",
|
||||
"description": "browser utils for @ffmpeg/*",
|
||||
"main": "./dist/cjs/index.js",
|
||||
"type": "module",
|
||||
"types": "./dist/cjs/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/cjs/index.d.ts",
|
||||
"import": "./dist/esm/index.js",
|
||||
"require": "./dist/cjs/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "tsc -p tsconfig-esm.json --watch",
|
||||
"lint": "eslint src",
|
||||
"clean": "rimraf dist",
|
||||
"build:esm": "tsc -p tsconfig.esm.json",
|
||||
"build:umd": "tsc -p tsconfig.cjs.json && webpack",
|
||||
"build": "npm run clean && npm run build:esm && npm run build:umd",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/ffmpegwasm/ffmpeg.wasm.git"
|
||||
},
|
||||
"keywords": [
|
||||
"ffmpeg",
|
||||
"video",
|
||||
"audio",
|
||||
"transcode"
|
||||
],
|
||||
"author": "Jerome Wu <jeromewus@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/ffmpegwasm/ffmpeg.wasm/issues"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.x"
|
||||
},
|
||||
"homepage": "https://github.com/ffmpegwasm/ffmpeg.wasm#readme",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||
"@typescript-eslint/parser": "^6.1.0",
|
||||
"eslint": "^8.45.0",
|
||||
"rimraf": "^5.0.1",
|
||||
"typescript": "^5.1.6",
|
||||
"webpack-cli": "^5.1.4"
|
||||
}
|
||||
}
|
||||
1
public/ffmpeg_packages/util/src/const.ts
Executable file
1
public/ffmpeg_packages/util/src/const.ts
Executable file
@@ -0,0 +1 @@
|
||||
export const HeaderContentLength = "Content-Length";
|
||||
6
public/ffmpeg_packages/util/src/errors.ts
Executable file
6
public/ffmpeg_packages/util/src/errors.ts
Executable file
@@ -0,0 +1,6 @@
|
||||
export const ERROR_RESPONSE_BODY_READER = new Error(
|
||||
"failed to get response body reader"
|
||||
);
|
||||
export const ERROR_INCOMPLETED_DOWNLOAD = new Error(
|
||||
"failed to complete download"
|
||||
);
|
||||
180
public/ffmpeg_packages/util/src/index.ts
Executable file
180
public/ffmpeg_packages/util/src/index.ts
Executable file
@@ -0,0 +1,180 @@
|
||||
import {
|
||||
ERROR_RESPONSE_BODY_READER,
|
||||
ERROR_INCOMPLETED_DOWNLOAD,
|
||||
} from "./errors.js";
|
||||
import { HeaderContentLength } from "./const.js";
|
||||
import { ProgressCallback } from "./types.js";
|
||||
|
||||
const readFromBlobOrFile = (blob: Blob | File): Promise<Uint8Array> =>
|
||||
new Promise((resolve, reject) => {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = () => {
|
||||
const { result } = fileReader;
|
||||
if (result instanceof ArrayBuffer) {
|
||||
resolve(new Uint8Array(result));
|
||||
} else {
|
||||
resolve(new Uint8Array());
|
||||
}
|
||||
};
|
||||
fileReader.onerror = (event) => {
|
||||
reject(
|
||||
Error(
|
||||
`File could not be read! Code=${event?.target?.error?.code || -1}`
|
||||
)
|
||||
);
|
||||
};
|
||||
fileReader.readAsArrayBuffer(blob);
|
||||
});
|
||||
|
||||
/**
|
||||
* An util function to fetch data from url string, base64, URL, File or Blob format.
|
||||
*
|
||||
* Examples:
|
||||
* ```ts
|
||||
* // URL
|
||||
* await fetchFile("http://localhost:3000/video.mp4");
|
||||
* // base64
|
||||
* await fetchFile("data:<type>;base64,wL2dvYWwgbW9yZ...");
|
||||
* // URL
|
||||
* await fetchFile(new URL("video.mp4", import.meta.url));
|
||||
* // File
|
||||
* fileInput.addEventListener('change', (e) => {
|
||||
* await fetchFile(e.target.files[0]);
|
||||
* });
|
||||
* // Blob
|
||||
* const blob = new Blob(...);
|
||||
* await fetchFile(blob);
|
||||
* ```
|
||||
*/
|
||||
export const fetchFile = async (
|
||||
file?: string | File | Blob
|
||||
): Promise<Uint8Array> => {
|
||||
let data: ArrayBuffer | number[];
|
||||
|
||||
if (typeof file === "string") {
|
||||
/* From base64 format */
|
||||
if (/data:_data\/([a-zA-Z]*);base64,([^"]*)/.test(file)) {
|
||||
data = atob(file.split(",")[1])
|
||||
.split("")
|
||||
.map((c) => c.charCodeAt(0));
|
||||
/* From remote server/URL */
|
||||
} else {
|
||||
data = await (await fetch(file)).arrayBuffer();
|
||||
}
|
||||
} else if (file instanceof URL) {
|
||||
data = await (await fetch(file)).arrayBuffer();
|
||||
} else if (file instanceof File || file instanceof Blob) {
|
||||
data = await readFromBlobOrFile(file);
|
||||
} else {
|
||||
return new Uint8Array();
|
||||
}
|
||||
|
||||
return new Uint8Array(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* importScript dynamically import a script, useful when you
|
||||
* want to use different versions of ffmpeg.wasm based on environment.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```ts
|
||||
* await importScript("http://localhost:3000/ffmpeg.js");
|
||||
* ```
|
||||
*/
|
||||
export const importScript = async (url: string): Promise<void> =>
|
||||
new Promise((resolve) => {
|
||||
const script = document.createElement("script");
|
||||
const eventHandler = () => {
|
||||
script.removeEventListener("load", eventHandler);
|
||||
resolve();
|
||||
};
|
||||
script.src = url;
|
||||
script.type = "text/javascript";
|
||||
script.addEventListener("load", eventHandler);
|
||||
document.getElementsByTagName("head")[0].appendChild(script);
|
||||
});
|
||||
|
||||
/**
|
||||
* Download content of a URL with progress.
|
||||
*
|
||||
* Progress only works when Content-Length is provided by the server.
|
||||
*
|
||||
*/
|
||||
export const downloadWithProgress = async (
|
||||
url: string | URL,
|
||||
cb?: ProgressCallback
|
||||
): Promise<ArrayBuffer> => {
|
||||
const resp = await fetch(url);
|
||||
let buf;
|
||||
|
||||
try {
|
||||
// Set total to -1 to indicate that there is not Content-Type Header.
|
||||
const total = parseInt(resp.headers.get(HeaderContentLength) || "-1");
|
||||
|
||||
const reader = resp.body?.getReader();
|
||||
if (!reader) throw ERROR_RESPONSE_BODY_READER;
|
||||
|
||||
const chunks = [];
|
||||
let received = 0;
|
||||
for (;;) {
|
||||
const { done, value } = await reader.read();
|
||||
const delta = value ? value.length : 0;
|
||||
|
||||
if (done) {
|
||||
if (total != -1 && total !== received) throw ERROR_INCOMPLETED_DOWNLOAD;
|
||||
cb && cb({ url, total, received, delta, done });
|
||||
break;
|
||||
}
|
||||
|
||||
chunks.push(value);
|
||||
received += delta;
|
||||
cb && cb({ url, total, received, delta, done });
|
||||
}
|
||||
|
||||
const data = new Uint8Array(received);
|
||||
let position = 0;
|
||||
for (const chunk of chunks) {
|
||||
data.set(chunk, position);
|
||||
position += chunk.length;
|
||||
}
|
||||
|
||||
buf = data.buffer;
|
||||
} catch (e) {
|
||||
console.log(`failed to send download progress event: `, e);
|
||||
// Fetch arrayBuffer directly when it is not possible to get progress.
|
||||
buf = await resp.arrayBuffer();
|
||||
cb &&
|
||||
cb({
|
||||
url,
|
||||
total: buf.byteLength,
|
||||
received: buf.byteLength,
|
||||
delta: 0,
|
||||
done: true,
|
||||
});
|
||||
}
|
||||
|
||||
return buf;
|
||||
};
|
||||
|
||||
/**
|
||||
* toBlobURL fetches data from an URL and return a blob URL.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```ts
|
||||
* await toBlobURL("http://localhost:3000/ffmpeg.js", "text/javascript");
|
||||
* ```
|
||||
*/
|
||||
export const toBlobURL = async (
|
||||
url: string,
|
||||
mimeType: string,
|
||||
progress = false,
|
||||
cb?: ProgressCallback
|
||||
): Promise<string> => {
|
||||
const buf = progress
|
||||
? await downloadWithProgress(url, cb)
|
||||
: await (await fetch(url)).arrayBuffer();
|
||||
const blob = new Blob([buf], { type: mimeType });
|
||||
return URL.createObjectURL(blob);
|
||||
};
|
||||
9
public/ffmpeg_packages/util/src/types.ts
Executable file
9
public/ffmpeg_packages/util/src/types.ts
Executable file
@@ -0,0 +1,9 @@
|
||||
export interface DownloadProgressEvent {
|
||||
url: string | URL;
|
||||
total: number;
|
||||
received: number;
|
||||
delta: number;
|
||||
done: boolean;
|
||||
}
|
||||
|
||||
export type ProgressCallback = (event: DownloadProgressEvent) => void;
|
||||
6
public/ffmpeg_packages/util/tests/.eslintrc
Executable file
6
public/ffmpeg_packages/util/tests/.eslintrc
Executable file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-undef": 0,
|
||||
"camelcase": 0
|
||||
}
|
||||
}
|
||||
19
public/ffmpeg_packages/util/tests/constants.js
Executable file
19
public/ffmpeg_packages/util/tests/constants.js
Executable file
@@ -0,0 +1,19 @@
|
||||
const TIMEOUT = 60000;
|
||||
const IS_BROWSER = typeof window !== 'undefined' && typeof window.document !== 'undefined';
|
||||
const OPTIONS = {
|
||||
corePath: IS_BROWSER ? 'http://localhost:3000/node_modules/@ffmpeg/core/dist/ffmpeg-core.js' : '@ffmpeg/core',
|
||||
};
|
||||
const FLAME_MP4_LENGTH = 100374;
|
||||
const META_FLAME_MP4_LENGTH = 100408;
|
||||
const META_FLAME_MP4_LENGTH_NO_SPACE = 100404;
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = {
|
||||
TIMEOUT,
|
||||
IS_BROWSER,
|
||||
OPTIONS,
|
||||
FLAME_MP4_LENGTH,
|
||||
META_FLAME_MP4_LENGTH,
|
||||
META_FLAME_MP4_LENGTH_NO_SPACE,
|
||||
};
|
||||
}
|
||||
21
public/ffmpeg_packages/util/tests/ffmpeg.test.html
Executable file
21
public/ffmpeg_packages/util/tests/ffmpeg.test.html
Executable file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>FFmpeg Unit Test</title>
|
||||
<link rel="stylesheet" href="../node_modules/mocha/mocha.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
<script src="../node_modules/mocha/mocha.js"></script>
|
||||
<script src="../node_modules/chai/chai.js"></script>
|
||||
<script src="../dist/ffmpeg.dev.js"></script>
|
||||
<script src="./constants.js"></script>
|
||||
<script>mocha.setup('bdd');</script>
|
||||
<script src="./ffmpeg.test.js"></script>
|
||||
<script>
|
||||
window.expect = chai.expect;
|
||||
mocha.run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
69
public/ffmpeg_packages/util/tests/ffmpeg.test.js
Executable file
69
public/ffmpeg_packages/util/tests/ffmpeg.test.js
Executable file
@@ -0,0 +1,69 @@
|
||||
const { createFFmpeg } = FFmpeg;
|
||||
|
||||
describe('FS()', () => {
|
||||
const ffmpeg = createFFmpeg(OPTIONS);
|
||||
before(async function cb() {
|
||||
this.timeout(0);
|
||||
await ffmpeg.load();
|
||||
});
|
||||
|
||||
it('should throw error when readdir for invalid path ', () => {
|
||||
expect(() => ffmpeg.FS('readdir', '/invalid')).to.throw(/readdir/);
|
||||
});
|
||||
it('should throw error when readFile for invalid path ', () => {
|
||||
expect(() => ffmpeg.FS('readFile', '/invalid')).to.throw(/readFile/);
|
||||
});
|
||||
it('should throw an default error ', () => {
|
||||
expect(() => ffmpeg.FS('unlink', '/invalid')).to.throw(/Oops/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('load()', () => {
|
||||
it('should throw error when corePath is not a string', async () => {
|
||||
const ffmpeg = createFFmpeg({ ...OPTIONS, corePath: null });
|
||||
|
||||
try {
|
||||
await ffmpeg.load();
|
||||
} catch (e) {
|
||||
expect(e).to.be.an('Error');
|
||||
}
|
||||
});
|
||||
it('should throw error when not called before FS() and run()', () => {
|
||||
const ffmpeg = createFFmpeg(OPTIONS);
|
||||
expect(() => ffmpeg.FS('readdir', 'dummy')).to.throw();
|
||||
expect(() => ffmpeg.run('-h')).to.throw();
|
||||
});
|
||||
|
||||
it('should throw error when running load() more than once', async () => {
|
||||
const ffmpeg = createFFmpeg(OPTIONS);
|
||||
await ffmpeg.load();
|
||||
try {
|
||||
await ffmpeg.load();
|
||||
} catch (e) {
|
||||
expect(e).to.be.an('Error');
|
||||
}
|
||||
}).timeout(TIMEOUT);
|
||||
});
|
||||
|
||||
describe('isLoaded()', () => {
|
||||
it('should return true when loaded', async () => {
|
||||
const ffmpeg = createFFmpeg(OPTIONS);
|
||||
await ffmpeg.load();
|
||||
expect(ffmpeg.isLoaded()).to.equal(true);
|
||||
}).timeout(TIMEOUT);
|
||||
});
|
||||
|
||||
describe('run()', () => {
|
||||
it('should not allow to run two command at the same time', async () => {
|
||||
const ffmpeg = createFFmpeg(OPTIONS);
|
||||
await ffmpeg.load();
|
||||
ffmpeg.run('-h');
|
||||
setTimeout(() => {
|
||||
try {
|
||||
ffmpeg.run('-h');
|
||||
} catch (e) {
|
||||
expect(e).to.be.an(Error);
|
||||
}
|
||||
}, 500);
|
||||
}).timeout(TIMEOUT);
|
||||
});
|
||||
8
public/ffmpeg_packages/util/tsconfig.cjs.json
Executable file
8
public/ffmpeg_packages/util/tsconfig.cjs.json
Executable file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"outDir": "dist/cjs",
|
||||
"target": "es2015"
|
||||
}
|
||||
}
|
||||
9
public/ffmpeg_packages/util/tsconfig.esm.json
Executable file
9
public/ffmpeg_packages/util/tsconfig.esm.json
Executable file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"outDir": "dist/esm",
|
||||
"target": "esnext",
|
||||
"moduleResolution": "nodenext"
|
||||
}
|
||||
}
|
||||
6
public/ffmpeg_packages/util/tsconfig.json
Executable file
6
public/ffmpeg_packages/util/tsconfig.json
Executable file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"declaration": true
|
||||
}
|
||||
}
|
||||
12
public/ffmpeg_packages/util/webpack.config.cjs
Executable file
12
public/ffmpeg_packages/util/webpack.config.cjs
Executable file
@@ -0,0 +1,12 @@
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
mode: "production",
|
||||
entry: "./dist/cjs/index.js",
|
||||
output: {
|
||||
library: "FFmpegUtil",
|
||||
libraryTarget: "umd",
|
||||
filename: "index.js",
|
||||
path: path.resolve(__dirname, "dist", "umd"),
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user