Add util and rewrite examples (WIP)

This commit is contained in:
Jerome Wu
2022-09-23 17:11:48 +08:00
parent 6e9d91483e
commit f2b68cb34c
109 changed files with 1669 additions and 851 deletions

View File

@@ -2,13 +2,13 @@
"name": "@ffmpeg/ffmpeg-mt",
"version": "0.11.5",
"description": "FFmpeg WebAssembly version w/ multi-thread",
"main": "./dist/commonjs/ffmpeg.js",
"main": "./dist/cjs/ffmpeg.js",
"types": "./types/ffmpeg.d.ts",
"exports": {
".": {
"types": "./types/ffmpeg.d.ts",
"import": "./dist/esm/ffmpeg.js",
"require": "./dist/commonjs/ffmpeg.js"
"require": "./dist/cjs/ffmpeg.js"
}
},
"scripts": {

View File

@@ -9,7 +9,7 @@
<div id="mocha"></div>
<script src="../../../node_modules/mocha/mocha.js"></script>
<script src="../../../node_modules/chai/chai.js"></script>
<script src="../dist/commonjs/ffmpeg.js"></script>
<script src="../dist/cjs/ffmpeg.js"></script>
<script src="./constants.js"></script>
<script>mocha.setup('bdd');</script>
<script src="./ffmpeg.test.js"></script>

View File

@@ -2,13 +2,13 @@
"name": "@ffmpeg/ffmpeg",
"version": "0.11.5",
"description": "FFmpeg WebAssembly version",
"main": "./dist/commonjs/ffmpeg.js",
"main": "./dist/cjs/ffmpeg.js",
"types": "./types/ffmpeg.d.ts",
"exports": {
".": {
"types": "./types/ffmpeg.d.ts",
"import": "./dist/esm/ffmpeg.js",
"require": "./dist/commonjs/ffmpeg.js"
"require": "./dist/cjs/ffmpeg.js"
}
},
"scripts": {

View File

@@ -9,7 +9,7 @@
<div id="mocha"></div>
<script src="../../../node_modules/mocha/mocha.js"></script>
<script src="../../../node_modules/chai/chai.js"></script>
<script src="../dist/commonjs/ffmpeg.js"></script>
<script src="../dist/cjs/ffmpeg.js"></script>
<script src="./constants.js"></script>
<script>mocha.setup('bdd');</script>
<script src="./ffmpeg.test.js"></script>

1
packages/node-util/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
dist

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
packages/util/.gitignore vendored Normal file
View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -1,22 +0,0 @@
Browser Examples
==================
To run this example, execute:
```
$ npm start
```
Visit http://localhost:3000/examples/browser/transcode.html
Web Worker Examples
==================
To run the webworker example, execute:
```
$ npm run start:worker
```
Visit http://localhost:3000/examples/browser/transcode.worker.html

View File

@@ -1,55 +0,0 @@
<html>
<head>
<script src="/dist/ffmpeg.dev.js"></script>
<style>
html,
body {
margin: 0;
width: 100%;
height: 100%;
}
body {
display: flex;
flex-direction: column;
align-items: center;
}
</style>
</head>
<body>
<h3>Select multiple video files to Concatenate</h3>
<video id="output-video" controls></video><br />
<input type="file" id="uploader" multiple />
<p id="message"></p>
<script>
const { createFFmpeg, fetchFile } = FFmpeg;
const ffmpeg = createFFmpeg({ log: true });
const transcode = async ({ target: { files } }) => {
const message = document.getElementById("message");
message.innerHTML = "Loading ffmpeg-core.js";
await ffmpeg.load();
message.innerHTML = "Start Concating";
const inputPaths = [];
for (const file of files) {
const { name } = file;
ffmpeg.FS('writeFile', name, await fetchFile(file));
inputPaths.push(`file ${name}`);
}
ffmpeg.FS('writeFile', 'concat_list.txt', inputPaths.join('\n'));
await ffmpeg.run('-f', 'concat', '-safe', '0', '-i', 'concat_list.txt', 'output.mp4');
message.innerHTML = "Complete Concating";
const data = ffmpeg.FS('readFile', 'output.mp4');
const video = document.getElementById("output-video");
video.src = URL.createObjectURL(
new Blob([data.buffer], {
type: "video/mp4"
})
);
};
const elm = document.getElementById("uploader");
elm.addEventListener("change", transcode);
</script>
</body>
</html>

View File

@@ -1,53 +0,0 @@
<html>
<head>
<script src="/dist/ffmpeg.dev.js"></script>
<style>
html, body {
margin: 0;
width: 100%;
height: 100%
}
body {
display: flex;
flex-direction: column;
align-items: center;
}
</style>
</head>
<body>
<h3>Click start to transcode images to mp4 (x264) and play!</h3>
<video id="output-video" controls></video><br/>
<button id="start-btn">Start</button>
<p id="message"></p>
<a href="https://github.com/ffmpegjs/ffmpeg.js/tree/master/examples/assets/triangle">Data Set</a>
<script>
const { createFFmpeg, fetchFile } = FFmpeg;
const ffmpeg = createFFmpeg({ log: true });
const image2video = async () => {
const message = document.getElementById('message');
message.innerHTML = 'Loading ffmpeg-core.js';
await ffmpeg.load();
message.innerHTML = 'Loading data';
ffmpeg.FS('writeFile', 'audio.ogg', await fetchFile('../assets/triangle/audio.ogg'));
for (let i = 0; i < 60; i += 1) {
const num = `00${i}`.slice(-3);
ffmpeg.FS('writeFile', `tmp.${num}.png`, await fetchFile(`../assets/triangle/tmp.${num}.png`));
}
message.innerHTML = 'Start transcoding';
await ffmpeg.run('-framerate', '30', '-pattern_type', 'glob', '-i', '*.png', '-i', 'audio.ogg', '-c:a', 'copy', '-shortest', '-c:v', 'libx264', '-pix_fmt', 'yuv420p', 'out.mp4');
const data = ffmpeg.FS('readFile', 'out.mp4');
ffmpeg.FS('unlink', 'audio.ogg')
for (let i = 0; i < 60; i += 1) {
const num = `00${i}`.slice(-3);
ffmpeg.FS('unlink', `tmp.${num}.png`);
}
const video = document.getElementById('output-video');
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
}
const elm = document.getElementById('start-btn');
elm.addEventListener('click', image2video);
</script>
</body>
</html>

View File

@@ -1,57 +0,0 @@
<html>
<head>
<script src="/dist/ffmpeg.dev.js"></script>
<style>
html, body {
margin: 0;
width: 100%;
height: 100%
}
body {
display: flex;
flex-direction: column;
align-items: center;
}
</style>
</head>
<body>
<h3>Upload a video to transcode to mp4 (x264) and play!</h3>
<video id="output-video" controls></video><br/>
<input type="file" id="uploader">
<button onClick="cancel()">Cancel</button>
<p id="message"></p>
<script>
const { createFFmpeg, fetchFile } = FFmpeg;
let ffmpeg = null;
const transcode = async ({ target: { files } }) => {
if (ffmpeg === null) {
ffmpeg = createFFmpeg({ log: true });
}
const message = document.getElementById('message');
const { name } = files[0];
message.innerHTML = 'Loading ffmpeg-core.js';
if (!ffmpeg.isLoaded()) {
await ffmpeg.load();
}
ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
message.innerHTML = 'Start transcoding';
await ffmpeg.run('-i', name, 'output.mp4');
message.innerHTML = 'Complete transcoding';
const data = ffmpeg.FS('readFile', 'output.mp4');
const video = document.getElementById('output-video');
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
}
const elm = document.getElementById('uploader');
elm.addEventListener('change', transcode);
const cancel = () => {
try {
ffmpeg.exit();
} catch(e) {}
ffmpeg = null;
}
</script>
</body>
</html>

View File

@@ -1,48 +0,0 @@
<html>
<head>
<style>
html, body {
margin: 0;
width: 100%;
height: 100%
}
body {
display: flex;
flex-direction: column;
align-items: center;
}
</style>
</head>
<body>
<h3>Upload a video to transcode to mp4 (x264) and play!</h3>
<video id="output-video" controls></video><br/>
<input type="file" id="uploader">
<p id="message"></p>
<script type="module">
const worker = new Worker(new URL('./transcode.worker.js', import.meta.url).href);
worker.onmessage = (event) => {
const {data} = event;
message.innerHTML = 'Complete transcoding';
const video = document.getElementById('output-video');
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
}
worker.onerror = (error) => console.log(error);
const transcode = async ({ target: { files } }) => {
const message = document.getElementById('message');
const [file] = files;
let name = file.name.split('.');
const inType = name.pop();
name = name.join();
const buffer = await file.arrayBuffer();
const outType = 'mp4';
worker.postMessage({name, inType, outType, buffer}, [buffer]);
message.innerHTML = 'Start transcoding';
}
const elm = document.getElementById('uploader');
elm.addEventListener('change', transcode);
</script>
</body>
</html>

View File

@@ -1,24 +0,0 @@
importScripts('/dist/ffmpeg.dev.js');
const ffmpeg = self.FFmpeg.createFFmpeg({log: true});
onmessage = async (event) => {
try {
const {buffer, name, inType, outType} = event.data;
if (!ffmpeg.isLoaded()) {
await ffmpeg.load();
}
ffmpeg.FS('writeFile', `${name}.${inType}`, new Uint8Array(buffer));
await ffmpeg.run('-i', `${name}.${inType}`, `${name}.${outType}`);
const data = ffmpeg.FS('readFile', `${name}.${outType}`);
postMessage({buffer: data.buffer, type: "result"}, [data.buffer]);
// delete files from memory
ffmpeg.FS('unlink', `${name}.${inType}`);
ffmpeg.FS('unlink', `${name}.${outType}`);
} catch (e) {
postMessage({type: "error", error: e});
}
}

View File

@@ -1,44 +0,0 @@
<html>
<head>
<script src="/dist/ffmpeg.dev.js"></script>
<style>
html, body {
margin: 0;
width: 100%;
height: 100%
}
body {
display: flex;
flex-direction: column;
align-items: center;
}
</style>
</head>
<body>
<h3>Upload a mp4 (x264) video and trim its first 1 seconds and play!</h3>
<video id="output-video" controls></video><br/>
<input type="file" id="uploader">
<p id="message"></p>
<script>
const { createFFmpeg, fetchFile } = FFmpeg;
const ffmpeg = createFFmpeg({ log: true });
const trim = async ({ target: { files } }) => {
const message = document.getElementById('message');
const { name } = files[0];
message.innerHTML = 'Loading ffmpeg-core.js';
await ffmpeg.load();
message.innerHTML = 'Start trimming';
ffmpeg.FS('writeFile', name, await fetchFile(files[0]));
await ffmpeg.run('-i', name, '-ss', '0', '-to', '1', 'output.mp4');
message.innerHTML = 'Complete trimming';
const data = ffmpeg.FS('readFile', 'output.mp4');
const video = document.getElementById('output-video');
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
}
const elm = document.getElementById('uploader');
elm.addEventListener('change', trim);
</script>
</body>
</html>

View File

@@ -1,72 +0,0 @@
<html>
<head>
<script src="/dist/ffmpeg.dev.js"></script>
<style>
html, body {
margin: 0;
width: 100%;
height: 100%
}
body {
display: flex;
flex-direction: column;
align-items: center;
}
</style>
</head>
<body>
<h3>Record video from webcam and transcode to mp4 (x264) and play!</h3>
<div>
<video id="webcam" width="320px" height="180px"></video>
<video id="output-video" width="320px" height="180px" controls></video>
</div>
<button id="record" disabled>Start Recording</button>
<p id="message"></p>
<script>
const { createFFmpeg, fetchFile } = FFmpeg;
const ffmpeg = createFFmpeg({ log: true });
const webcam = document.getElementById('webcam');
const recordBtn = document.getElementById('record');
const startRecording = () => {
const rec = new MediaRecorder(webcam.srcObject);
const chunks = [];
recordBtn.textContent = 'Stop Recording';
recordBtn.onclick = () => {
rec.stop();
recordBtn.textContent = 'Start Recording';
recordBtn.onclick = startRecording;
}
rec.ondataavailable = e => chunks.push(e.data);
rec.onstop = async () => {
transcode(new Uint8Array(await (new Blob(chunks)).arrayBuffer()));
};
rec.start();
};
(async () => {
webcam.srcObject = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
await webcam.play();
recordBtn.disabled = false;
recordBtn.onclick = startRecording;
})();
const transcode = async (webcamData) => {
const message = document.getElementById('message');
const name = 'record.webm';
message.innerHTML = 'Loading ffmpeg-core.js';
await ffmpeg.load();
message.innerHTML = 'Start transcoding';
ffmpeg.FS('writeFile', name, await fetchFile(webcamData));
await ffmpeg.run('-i', name, 'output.mp4');
message.innerHTML = 'Complete transcoding';
const data = ffmpeg.FS('readFile', 'output.mp4');
const video = document.getElementById('output-video');
video.src = URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' }));
}
</script>
</body>
</html>

View File

@@ -1,13 +0,0 @@
const fs = require('fs');
const { createFFmpeg, fetchFile } = require('../../src');
const ffmpeg = createFFmpeg({ log: true });
(async () => {
await ffmpeg.load();
ffmpeg.FS('writeFile', 'flame.avi', await fetchFile('../assets/flame.avi'));
ffmpeg.FS('writeFile', 'concat_list.txt', 'file flame.avi\nfile flame.avi');
await ffmpeg.run('-f', 'concat', '-safe', '0', '-i', 'concat_list.txt', 'flame.mp4');
await fs.promises.writeFile('flame.mp4', ffmpeg.FS('readFile', 'flame.mp4'));
process.exit(0);
})();

View File

@@ -1,12 +0,0 @@
const fs = require('fs');
const { createFFmpeg, fetchFile } = require('../../src');
const ffmpeg = createFFmpeg({ log: true });
(async () => {
await ffmpeg.load();
ffmpeg.FS('writeFile', 'flame.avi', await fetchFile('../assets/flame.avi'));
await ffmpeg.run('-i', 'flame.avi', '-i', 'flame.avi', '-filter_complex', 'hstack', 'flame.mp4');
await fs.promises.writeFile('flame.mp4', ffmpeg.FS('readFile', 'flame.mp4'));
process.exit(0);
})();

View File

@@ -1,22 +0,0 @@
const fs = require('fs');
const { createFFmpeg, fetchFile } = require('../../src');
const ffmpeg = createFFmpeg({ log: true });
(async () => {
await ffmpeg.load();
ffmpeg.FS('writeFile', 'audio.ogg', await fetchFile('../assets/triangle/audio.ogg'));
for (let i = 0; i < 60; i += 1) {
const num = `00${i}`.slice(-3);
ffmpeg.FS('writeFile', `tmp.${num}.png`, await fetchFile(`../assets/triangle/tmp.${num}.png`));
}
console.log(ffmpeg.FS('readdir', '/'));
await ffmpeg.run('-framerate', '30', '-pattern_type', 'glob', '-i', '*.png', '-i', 'audio.ogg', '-c:a', 'copy', '-shortest', '-c:v', 'libx264', '-pix_fmt', 'yuv420p', 'out.mp4');
await ffmpeg.FS('unlink', 'audio.ogg');
for (let i = 0; i < 60; i += 1) {
const num = `00${i}`.slice(-3);
await ffmpeg.FS('unlink', `tmp.${num}.png`);
}
await fs.promises.writeFile('out.mp4', ffmpeg.FS('readFile', 'out.mp4'));
process.exit(0);
})();

View File

@@ -1,14 +0,0 @@
const fs = require('fs');
const { createFFmpeg, fetchFile } = require('../../src');
const ffmpeg = createFFmpeg({ log: true });
(async () => {
await ffmpeg.load();
ffmpeg.FS('writeFile', 'flame.avi', await fetchFile('../assets/flame.avi'));
await ffmpeg.run('-i', 'flame.avi', '-map', '0:v', '-r', '25', 'out_%06d.bmp');
ffmpeg.FS('readdir', '/').filter((p) => p.endsWith('.bmp')).forEach(async (p) => {
fs.writeFileSync(p, ffmpeg.FS('readFile', p));
});
process.exit(0);
})();

View File

@@ -1,14 +0,0 @@
const fs = require('fs');
const { createFFmpeg, fetchFile } = require('../../src');
const ffmpeg = createFFmpeg({
log: true,
});
(async () => {
await ffmpeg.load();
ffmpeg.FS('writeFile', 'flame.avi', await fetchFile('../assets/flame.avi'));
await ffmpeg.run('-i', 'flame.avi', 'flame.mp4');
await fs.promises.writeFile('flame.mp4', ffmpeg.FS('readFile', 'flame.mp4'));
process.exit(0);
})();

View File

@@ -1,12 +0,0 @@
const fs = require('fs');
const { createFFmpeg, fetchFile } = require('../../src');
const ffmpeg = createFFmpeg({ log: true });
(async () => {
await ffmpeg.load();
ffmpeg.FS('writeFile', 'flame.avi', await fetchFile('../assets/flame.avi'));
await ffmpeg.run('-i', 'flame.avi', '-ss', '0', '-to', '1', 'flame_trim.avi');
await fs.promises.writeFile('flame_trim.avi', ffmpeg.FS('readFile', 'flame_trim.avi'));
process.exit(0);
})();

View File

@@ -0,0 +1,54 @@
{
"name": "@ffmpeg/util",
"version": "0.11.5",
"description": "browser utils for @ffmpeg/*",
"main": "./dist/cjs/index.js",
"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",
"build": "rimraf dist && tsc -p tsconfig-esm.json && tsc -p tsconfig-cjs.json",
"lint": "eslint src"
},
"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": ">=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",
"rimraf": "^3.0.2",
"ts-loader": "^9.4.1",
"typescript": "^4.8.3",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0"
}
}

View File

@@ -1,10 +0,0 @@
import pkg from '../../package.json';
/*
* Default options for browser environment
*/
const corePath = typeof process !== 'undefined' && process.env.NODE_ENV === 'development'
? new URL('/node_modules/@ffmpeg/core/dist/ffmpeg-core.js', import.meta.url).href
: `https://unpkg.com/@ffmpeg/core@${pkg.devDependencies['@ffmpeg/core'].substring(1)}/dist/ffmpeg-core.js`;
export default { corePath };

View File

@@ -1,38 +0,0 @@
const readFromBlobOrFile = (blob) => (
new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = () => {
resolve(fileReader.result);
};
fileReader.onerror = ({ target: { error: { code } } }) => {
reject(Error(`File could not be read! Code=${code}`));
};
fileReader.readAsArrayBuffer(blob);
})
);
// eslint-disable-next-line
export const fetchFile = async (_data) => {
let data = _data;
if (typeof _data === 'undefined') {
return new Uint8Array();
}
if (typeof _data === 'string') {
/* From base64 format */
if (/data:_data\/([a-zA-Z]*);base64,([^"]*)/.test(_data)) {
data = atob(_data.split(',')[1])
.split('')
.map((c) => c.charCodeAt(0));
/* From remote server/URL */
} else {
const res = await fetch(new URL(_data, import.meta.url).href);
data = await res.arrayBuffer();
}
/* From Blob or File */
} else if (_data instanceof File || _data instanceof Blob) {
data = await readFromBlobOrFile(_data);
}
return new Uint8Array(data);
};

View File

@@ -1,114 +0,0 @@
/* eslint-disable no-undef */
import { log } from '../utils/log';
import {
CREATE_FFMPEG_CORE_IS_NOT_DEFINED,
} from '../utils/errors';
/*
* Fetch data from remote URL and convert to blob URL
* to avoid CORS issue
*/
const toBlobURL = async (url, mimeType) => {
log('info', `fetch ${url}`);
const buf = await (await fetch(url)).arrayBuffer();
log('info', `${url} file size = ${buf.byteLength} bytes`);
const blob = new Blob([buf], { type: mimeType });
const blobURL = URL.createObjectURL(blob);
log('info', `${url} blob URL = ${blobURL}`);
return blobURL;
};
// eslint-disable-next-line
export const getCreateFFmpegCore = async ({
corePath: _corePath,
workerPath: _workerPath,
wasmPath: _wasmPath,
}) => {
// in Web Worker context
// eslint-disable-next-line
if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
if (typeof _corePath !== 'string') {
throw Error('corePath should be a string!');
}
const coreRemotePath = new URL(_corePath, import.meta.url).href;
const corePath = await toBlobURL(
coreRemotePath,
'application/javascript',
);
const wasmPath = await toBlobURL(
_wasmPath !== undefined ? _wasmPath : coreRemotePath.replace('ffmpeg-core.js', 'ffmpeg-core.wasm'),
'application/wasm',
);
const workerPath = await toBlobURL(
_workerPath !== undefined ? _workerPath : coreRemotePath.replace('ffmpeg-core.js', 'ffmpeg-core.worker.js'),
'application/javascript',
);
if (typeof createFFmpegCore === 'undefined') {
return new Promise((resolve) => {
globalThis.importScripts(corePath);
if (typeof createFFmpegCore === 'undefined') {
throw Error(CREATE_FFMPEG_CORE_IS_NOT_DEFINED(coreRemotePath));
}
log('info', 'ffmpeg-core.js script loaded');
resolve({
createFFmpegCore,
corePath,
wasmPath,
workerPath,
});
});
}
log('info', 'ffmpeg-core.js script is loaded already');
return Promise.resolve({
createFFmpegCore,
corePath,
wasmPath,
workerPath,
});
}
if (typeof _corePath !== 'string') {
throw Error('corePath should be a string!');
}
const coreRemotePath = new URL(_corePath, import.meta.url).href;
const corePath = await toBlobURL(
coreRemotePath,
'application/javascript',
);
const wasmPath = await toBlobURL(
coreRemotePath.replace('ffmpeg-core.js', 'ffmpeg-core.wasm'),
'application/wasm',
);
const workerPath = await toBlobURL(
coreRemotePath.replace('ffmpeg-core.js', 'ffmpeg-core.worker.js'),
'application/javascript',
);
if (typeof createFFmpegCore === 'undefined') {
return new Promise((resolve) => {
const script = document.createElement('script');
const eventHandler = () => {
script.removeEventListener('load', eventHandler);
if (typeof createFFmpegCore === 'undefined') {
throw Error(CREATE_FFMPEG_CORE_IS_NOT_DEFINED(coreRemotePath));
}
log('info', 'ffmpeg-core.js script loaded');
resolve({
createFFmpegCore,
corePath,
wasmPath,
workerPath,
});
};
script.src = corePath;
script.type = 'text/javascript';
script.addEventListener('load', eventHandler);
document.getElementsByTagName('head')[0].appendChild(script);
});
}
log('info', 'ffmpeg-core.js script is loaded already');
return Promise.resolve({
createFFmpegCore,
corePath,
wasmPath,
workerPath,
});
};

View File

@@ -1,5 +0,0 @@
import defaultOptions from './defaultOptions';
import { getCreateFFmpegCore } from './getCreateFFmpegCore';
import { fetchFile } from './fetchFile';
export { defaultOptions, getCreateFFmpegCore, fetchFile };

View File

@@ -1,50 +0,0 @@
module.exports = {
defaultArgs: [
/* args[0] is always the binary path */
'./ffmpeg',
/* Disable interaction mode */
'-nostdin',
/* Force to override output file */
'-y',
],
baseOptions: {
/* Flag to turn on/off log messages in console */
log: false,
/*
* Custom logger to get ffmpeg.wasm output messages.
* a sample logger looks like this:
*
* ```
* logger = ({ type, message }) => {
* console.log(type, message);
* }
* ```
*
* type can be one of following:
*
* info: internal workflow debug messages
* fferr: ffmpeg native stderr output
* ffout: ffmpeg native stdout output
*/
logger: () => {},
/*
* Progress handler to get current progress of ffmpeg command.
* a sample progress handler looks like this:
*
* ```
* progress = ({ ratio }) => {
* console.log(ratio);
* }
* ```
*
* ratio is a float number between 0 to 1.
*/
progress: () => {},
/*
* Path to find/download ffmpeg.wasm-core,
* this value should be overwriten by `defaultOptions` in
* each environment.
*/
corePath: '',
},
};

View File

@@ -1,279 +0,0 @@
const { defaultArgs, baseOptions } = require('./config');
const parseArgs = require('./utils/parseArgs');
const { defaultOptions, getCreateFFmpegCore } = require('./node');
const { version } = require('../package.json');
const NO_LOAD = Error('ffmpeg.wasm is not ready, make sure you have completed load().');
module.exports = (_options = {}) => {
const {
log: optLog,
logger,
progress: optProgress,
...options
} = {
...baseOptions,
...defaultOptions,
..._options,
};
let Core = null;
let ffmpeg = null;
let runResolve = null;
let runReject = null;
let running = false;
let customLogger = () => {};
let logging = optLog;
let progress = optProgress;
let duration = 0;
let frames = 0;
let readFrames = false;
let ratio = 0;
const detectCompletion = (message) => {
if (message === 'FFMPEG_END' && runResolve !== null) {
runResolve();
runResolve = null;
runReject = null;
running = false;
}
};
const log = (type, message) => {
customLogger({ type, message });
if (logging) {
console.log(`[${type}] ${message}`);
}
};
const ts2sec = (ts) => {
const [h, m, s] = ts.split(':');
return (parseFloat(h) * 60 * 60) + (parseFloat(m) * 60) + parseFloat(s);
};
const parseProgress = (message, prog) => {
if (typeof message === 'string') {
if (message.startsWith(' Duration')) {
const ts = message.split(', ')[0].split(': ')[1];
const d = ts2sec(ts);
prog({ duration: d, ratio });
if (duration === 0 || duration > d) {
duration = d;
readFrames = true;
}
} else if (readFrames && message.startsWith(' Stream')) {
const match = message.match(/([\d.]+) fps/);
if (match) {
const fps = parseFloat(match[1]);
frames = duration * fps;
} else {
frames = 0;
}
readFrames = false;
} else if (message.startsWith('frame') || message.startsWith('size')) {
const ts = message.split('time=')[1].split(' ')[0];
const t = ts2sec(ts);
const match = message.match(/frame=\s*(\d+)/);
if (frames && match) {
const f = parseFloat(match[1]);
ratio = Math.min(f / frames, 1);
} else {
ratio = t / duration;
}
prog({ ratio, time: t });
} else if (message.startsWith('video:')) {
prog({ ratio: 1 });
duration = 0;
}
}
};
const parseMessage = ({ type, message }) => {
log(type, message);
parseProgress(message, progress);
detectCompletion(message);
};
/*
* Load ffmpeg.wasm-core script.
* In browser environment, the ffmpeg.wasm-core script is fetch from
* CDN and can be assign to a local path by assigning `corePath`.
* In node environment, we use dynamic require and the default `corePath`
* is `$ffmpeg/core`.
*
* Typically the load() func might take few seconds to minutes to complete,
* better to do it as early as possible.
*
*/
const load = async () => {
log('info', 'load ffmpeg-core');
if (Core === null) {
log('info', 'loading ffmpeg-core');
/*
* In node environment, all paths are undefined as there
* is no need to set them.
*/
const {
createFFmpegCore,
corePath,
workerPath,
wasmPath,
} = await getCreateFFmpegCore(options);
Core = await createFFmpegCore({
/*
* Assign mainScriptUrlOrBlob fixes chrome extension web worker issue
* as there is no document.currentScript in the context of content_scripts
*/
mainScriptUrlOrBlob: corePath,
printErr: (message) => parseMessage({ type: 'fferr', message }),
print: (message) => parseMessage({ type: 'ffout', message }),
/*
* locateFile overrides paths of files that is loaded by main script (ffmpeg-core.js).
* It is critical for browser environment and we override both wasm and worker paths
* as we are using blob URL instead of original URL to avoid cross origin issues.
*/
locateFile: (path, prefix) => {
if (typeof window !== 'undefined' || typeof WorkerGlobalScope !== 'undefined') {
if (typeof wasmPath !== 'undefined'
&& path.endsWith('ffmpeg-core.wasm')) {
return wasmPath;
}
if (typeof workerPath !== 'undefined'
&& path.endsWith('ffmpeg-core.worker.js')) {
return workerPath;
}
}
return prefix + path;
},
});
ffmpeg = Core.cwrap(options.mainName || 'proxy_main', 'number', ['number', 'number']);
log('info', 'ffmpeg-core loaded');
} else {
throw Error('ffmpeg.wasm was loaded, you should not load it again, use ffmpeg.isLoaded() to check next time.');
}
};
/*
* Determine whether the Core is loaded.
*/
const isLoaded = () => Core !== null;
/*
* Run ffmpeg command.
* This is the major function in ffmpeg.wasm, you can just imagine it
* as ffmpeg native cli and what you need to pass is the same.
*
* For example, you can convert native command below:
*
* ```
* $ ffmpeg -i video.avi -c:v libx264 video.mp4
* ```
*
* To
*
* ```
* await ffmpeg.run('-i', 'video.avi', '-c:v', 'libx264', 'video.mp4');
* ```
*
*/
const run = (..._args) => {
log('info', `run ffmpeg command: ${_args.join(' ')}`);
if (Core === null) {
throw NO_LOAD;
} else if (running) {
throw Error('ffmpeg.wasm can only run one command at a time');
} else {
running = true;
return new Promise((resolve, reject) => {
const args = [...defaultArgs, ..._args].filter((s) => s.length !== 0);
runResolve = resolve;
runReject = reject;
ffmpeg(...parseArgs(Core, args));
});
}
};
/*
* Run FS operations.
* For input/output file of ffmpeg.wasm, it is required to save them to MEMFS
* first so that ffmpeg.wasm is able to consume them. Here we rely on the FS
* methods provided by Emscripten.
*
* Common methods to use are:
* ffmpeg.FS('writeFile', 'video.avi', new Uint8Array(...)): writeFile writes
* data to MEMFS. You need to use Uint8Array for binary data.
* ffmpeg.FS('readFile', 'video.mp4'): readFile from MEMFS.
* ffmpeg.FS('unlink', 'video.map'): delete file from MEMFS.
*
* For more info, check https://emscripten.org/docs/api_reference/Filesystem-API.html
*
*/
const FS = (method, ...args) => {
log('info', `run FS.${method} ${args.map((arg) => (typeof arg === 'string' ? arg : `<${arg.length} bytes binary file>`)).join(' ')}`);
if (Core === null) {
throw NO_LOAD;
} else {
let ret = null;
try {
ret = Core.FS[method](...args);
} catch (e) {
if (method === 'readdir') {
throw Error(`ffmpeg.FS('readdir', '${args[0]}') error. Check if the path exists, ex: ffmpeg.FS('readdir', '/')`);
} else if (method === 'readFile') {
throw Error(`ffmpeg.FS('readFile', '${args[0]}') error. Check if the path exists`);
} else {
throw Error('Oops, something went wrong in FS operation.');
}
}
return ret;
}
};
/**
* forcibly terminate the ffmpeg program.
*/
const exit = () => {
if (Core === null) {
throw NO_LOAD;
} else {
// if there's any pending runs, reject them
if (runReject) {
runReject('ffmpeg has exited');
}
running = false;
try {
Core.exit(1);
} catch (err) {
log(err.message);
if (runReject) {
runReject(err);
}
} finally {
Core = null;
ffmpeg = null;
runResolve = null;
runReject = null;
}
}
};
const setProgress = (_progress) => {
progress = _progress;
};
const setLogger = (_logger) => {
customLogger = _logger;
};
const setLogging = (_logging) => {
logging = _logging;
};
log('info', `use ffmpeg.wasm v${version}`);
return {
setProgress,
setLogger,
setLogging,
load,
isLoaded,
run,
exit,
FS,
};
};

View File

@@ -1,120 +0,0 @@
export const FS: {
writeFile: (fileName: string, binaryData: Uint8Array | string) => void,
readFile: (fileName: string) => Uint8Array,
readdir: (pathName: string) => string[],
unlink: (fileName: string) => void,
mkdir: (fileName: string) => void,
}
type FSMethodNames = { [K in keyof typeof FS]: (typeof FS)[K] extends (...args: any[]) => any ? K : never }[keyof typeof FS];
type FSMethodArgs = { [K in FSMethodNames]: Parameters<(typeof FS)[K]> };
type FSMethodReturn = { [K in FSMethodNames]: ReturnType<(typeof FS)[K]> };
type LogCallback = (logParams: { type: string; message: string }) => any;
type ProgressCallback = (progressParams: { ratio: number }) => any;
export interface CreateFFmpegOptions {
/** path for ffmpeg-core.js script */
corePath?: string;
/** path for ffmpeg-worker.js script */
workerPath?: string;
/** path for ffmpeg-core.wasm script */
wasmPath?: string;
/** a boolean to turn on all logs, default is false */
log?: boolean;
/** a function to get log messages, a quick example is ({ message }) => console.log(message) */
logger?: LogCallback;
/** a function to trace the progress, a quick example is p => console.log(p) */
progress?: ProgressCallback;
/** name of the main function of the ffmpeg-core.js script */
mainName?: string;
}
export interface FFmpeg {
/*
* Load ffmpeg.wasm-core script.
* In browser environment, the ffmpeg.wasm-core script is fetch from
* CDN and can be assign to a local path by assigning `corePath`.
* In node environment, we use dynamic require and the default `corePath`
* is `$ffmpeg/core`.
*
* Typically the load() func might take few seconds to minutes to complete,
* better to do it as early as possible.
*
*/
load(): Promise<void>;
/*
* Determine whether the Core is loaded.
*/
isLoaded(): boolean;
/*
* Run ffmpeg command.
* This is the major function in ffmpeg.wasm, you can just imagine it
* as ffmpeg native cli and what you need to pass is the same.
*
* For example, you can convert native command below:
*
* ```
* $ ffmpeg -i video.avi -c:v libx264 video.mp4
* ```
*
* To
*
* ```
* await ffmpeg.run('-i', 'video.avi', '-c:v', 'libx264', 'video.mp4');
* ```
*
*/
run(...args: string[]): Promise<void>;
/*
* Run FS operations.
* For input/output file of ffmpeg.wasm, it is required to save them to MEMFS
* first so that ffmpeg.wasm is able to consume them. Here we rely on the FS
* methods provided by Emscripten.
*
* Common methods to use are:
* ffmpeg.FS('writeFile', 'video.avi', new Uint8Array(...)): writeFile writes
* data to MEMFS. You need to use Uint8Array for binary data.
* ffmpeg.FS('readFile', 'video.mp4'): readFile from MEMFS.
* ffmpeg.FS('unlink', 'video.map'): delete file from MEMFS.
*
* For more info, check https://emscripten.org/docs/api_reference/Filesystem-API.html
*
*/
FS<Method extends FSMethodNames>(method: Method, ...args: FSMethodArgs[Method]): FSMethodReturn[Method];
setProgress(progress: ProgressCallback): void;
setLogger(log: LogCallback): void;
setLogging(logging: boolean): void;
exit(): void;
}
/*
* Create ffmpeg instance.
* Each ffmpeg instance owns an isolated MEMFS and works
* independently.
*
* For example:
*
* ```
* const ffmpeg = createFFmpeg({
* log: true,
* logger: () => {},
* progress: () => {},
* corePath: '',
* })
* ```
*
* For the usage of these four arguments, check config.js
*
*/
export function createFFmpeg(options?: CreateFFmpegOptions): FFmpeg;
/*
* Helper function for fetching files from various resource.
* Sometimes the video/audio file you want to process may located
* in a remote URL and somewhere in your local file system.
*
* This helper function helps you to fetch to file and return an
* Uint8Array variable for ffmpeg.wasm to consume.
*
*/
export function fetchFile(data: string | Buffer | Blob | File): Promise<Uint8Array>;

View File

@@ -1,36 +0,0 @@
require('regenerator-runtime/runtime');
const createFFmpeg = require('./createFFmpeg');
const { fetchFile } = require('./node');
module.exports = {
/*
* Create ffmpeg instance.
* Each ffmpeg instance owns an isolated MEMFS and works
* independently.
*
* For example:
*
* ```
* const ffmpeg = createFFmpeg({
* log: true,
* logger: () => {},
* progress: () => {},
* corePath: '',
* })
* ```
*
* For the usage of these four arguments, check config.js
*
*/
createFFmpeg,
/*
* Helper function for fetching files from various resource.
* Sometimes the video/audio file you want to process may located
* in a remote URL and somewhere in your local file system.
*
* This helper function helps you to fetch to file and return an
* Uint8Array variable for ffmpeg.wasm to consume.
*
*/
fetchFile,
};

107
packages/util/src/index.ts Normal file
View File

@@ -0,0 +1,107 @@
export 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);
});
/**
* 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
): Promise<string> => {
const buf = await (await fetch(url)).arrayBuffer();
const blob = new Blob([buf], { type: mimeType });
return URL.createObjectURL(blob);
};

View File

@@ -1,6 +0,0 @@
/*
* Default options for node environment
*/
module.exports = {
corePath: '@ffmpeg/core',
};

View File

@@ -1,33 +0,0 @@
const util = require('util');
const fs = require('fs');
const fetch = require('node-fetch');
const isURL = require('is-url');
module.exports = async (_data) => {
let data = _data;
if (typeof _data === 'undefined') {
return new Uint8Array();
}
if (typeof _data === 'string') {
/* From remote URL/server */
if (isURL(_data)
|| _data.startsWith('moz-extension://')
|| _data.startsWith('chrome-extension://')
|| _data.startsWith('file://')) {
const res = await fetch(_data);
data = await res.arrayBuffer();
/* From base64 format */
} else if (/data:_data\/([a-zA-Z]*);base64,([^"]*)/.test(_data)) {
data = Buffer.from(_data.split(',')[1], 'base64');
/* From local file path */
} else {
data = await util.promisify(fs.readFile)(_data);
}
/* From Buffer */
} else if (Buffer.isBuffer(_data)) {
data = _data;
}
return new Uint8Array(data);
};

View File

@@ -1,7 +0,0 @@
const { log } = require('../utils/log');
module.exports = ({ corePath }) => new Promise((resolve) => {
log('info', `fetch ffmpeg.wasm-core script from ${corePath}`);
// eslint-disable-next-line import/no-dynamic-require
resolve({ createFFmpegCore: require(corePath) });
});

View File

@@ -1,9 +0,0 @@
const defaultOptions = require('./defaultOptions');
const getCreateFFmpegCore = require('./getCreateFFmpegCore');
const fetchFile = require('./fetchFile');
module.exports = {
defaultOptions,
getCreateFFmpegCore,
fetchFile,
};

View File

@@ -1,11 +0,0 @@
const CREATE_FFMPEG_CORE_IS_NOT_DEFINED = (corePath) => (`
createFFmpegCore is not defined. ffmpeg.wasm is unable to find createFFmpegCore after loading ffmpeg-core.js from ${corePath}. Use another URL when calling createFFmpeg():
const ffmpeg = createFFmpeg({
corePath: 'http://localhost:3000/ffmpeg-core.js',
});
`);
module.exports = {
CREATE_FFMPEG_CORE_IS_NOT_DEFINED,
};

View File

@@ -1,24 +0,0 @@
let logging = false;
let customLogger = () => {};
const setLogging = (_logging) => {
logging = _logging;
};
const setCustomLogger = (logger) => {
customLogger = logger;
};
const log = (type, message) => {
customLogger({ type, message });
if (logging) {
console.log(`[${type}] ${message}`);
}
};
module.exports = {
logging,
setLogging,
setCustomLogger,
log,
};

Some files were not shown because too many files have changed in this diff Show More