This commit is contained in:
ct
2025-06-21 20:21:47 +08:00
parent 6a77a8b8c2
commit 8bd90c85dc
37 changed files with 1752 additions and 1 deletions

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"],
},
plugins: ["@typescript-eslint"],
};

1
public/ffmpeg_packages/util/.gitignore vendored Executable file
View File

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

View 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"
}
}

View File

@@ -0,0 +1 @@
export const HeaderContentLength = "Content-Length";

View 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"
);

View 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);
};

View 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;

View File

@@ -0,0 +1,6 @@
{
"rules": {
"no-undef": 0,
"camelcase": 0
}
}

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

View 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>

View 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);
});

View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "dist/cjs",
"target": "es2015"
}
}

View File

@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "esnext",
"outDir": "dist/esm",
"target": "esnext",
"moduleResolution": "nodenext"
}
}

View File

@@ -0,0 +1,6 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"declaration": true
}
}

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