Skip to content

Commit 1edef18

Browse files
committed
added documentation
1 parent fa962d8 commit 1edef18

File tree

5 files changed

+174
-36
lines changed

5 files changed

+174
-36
lines changed

README.md

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,138 @@
1-
# ffmpeg-js
1+
# 🎥 FFmpeg.js: A WebAssembly-powered FFmpeg Interface for Browsers
2+
3+
Welcome to FFmpeg.js, an innovative library that offers a WebAssembly-powered interface for utilizing FFmpeg in the browser. 🌐💡
4+
5+
## ❓ Why FFmpeg.js?
6+
7+
This project has been inspired by the awesome work of [ffmpeg.wasm](https://github.com/ffmpegwasm/ffmpeg.wasm), but we noted a few drawbacks that might limit its applicability for broader use:
8+
9+
1. The project employs a GPL3 build of FFmpeg, limiting its use for commercial projects. 🚫💼
10+
2. It's developed in JavaScript and hence offers limited typing, restricting the potential for more rigorous static type checks. ❗⌨️
11+
3. The lack of an object-oriented approach for writing FFmpeg commands. 🔄📝
12+
13+
## ✔️ FFmpeg.js to the Rescue!
14+
15+
Addressing the issues above, FFmpeg.js:
16+
17+
- Provides an LGPL build of FFmpeg, making it commercially more viable, checkout https://ffmpeg.org/legal.html for more detail. 🟢💼
18+
- Is written in TypeScript, introducing static type checking to enhance code reliability. 👌🔍
19+
- Offers an object-oriented interface for writing FFmpeg commands, inspired by `fluent-ffmpeg`
20+
, making it more programmer-friendly. 🎯🔄
21+
22+
However, it's important to note that as of now, FFmpeg.js runs only in Chrome, Firefox, and Edge browsers. It doesn't support Safari or Node. 🚧🔍
23+
24+
## ⚙️ Setup
25+
26+
Setting up FFmpeg.js is a breeze!
27+
28+
```bash
29+
npm i @diffusion-studio/ffmpeg-js
30+
```
31+
32+
This should install the library. Now because ffmpeg.js requires the use of the [SharedArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer) you need to enable `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp` on the server side.
33+
34+
### ⚡️Vite
35+
36+
In a vite environment you can simply add these policies by putting the following into your `vite-config.js`:
37+
38+
```js
39+
...
40+
server: {
41+
...
42+
headers: {
43+
'Cross-Origin-Embedder-Policy': 'require-corp',
44+
'Cross-Origin-Opener-Policy': 'same-origin',
45+
},
46+
},
47+
...
48+
```
49+
50+
## 💻 Usage
51+
52+
Somewhere in your project you need to initiate a ffmpeg instance.
53+
54+
```typescript
55+
import { FFmpeg } from '@diffusion-studio/ffmpeg-js';
56+
57+
const ffmpeg = new FFmpeg();
58+
```
59+
60+
By default this will pull a [LGPLv2.1 compliant build](https://github.com/diffusion-studio/ffmpeg-wasm-lgpl-build) of FFmpeg from the [UNPKG delivery network](https://www.unpkg.com).<br> Consequently if you immidiately intent to run commands you need to wait until the binaries have been fetched successfully, like this:
61+
62+
```typescript
63+
ffmpeg.whenReady(async () => {
64+
await ffmpeg.exec(['-help']);
65+
});
66+
```
67+
68+
This will output the ffmpeg help as fast as possible.
69+
70+
> HINT: Even though this library intends to provide a object oriented interface for ffmpeg, you can still run commands manually using the `exec` method.
71+
72+
When working with files you need to save them to the in-memory file system first:
73+
74+
```typescript
75+
const source = 'https://<path to file>/<filename>.mp4';
76+
77+
// write to file system
78+
await ffmpeg.writeFile('input.mp4', source);
79+
80+
// convert mp4 to avi
81+
await ffmpeg.exec(['-i', 'input.mp4', 'output.avi']);
82+
83+
// read from file system
84+
const result: Uint8Array = ffmpeg.readFile('output.avi');
85+
86+
// free memory
87+
ffmpeg.deleteFile('input.mp4');
88+
ffmpeg.deleteFile('output.avi');
89+
```
90+
91+
Let's see how you would get the same result using the **object oriented** way.
92+
93+
```typescript
94+
const source = 'https://<path to file>/<filename>.mp4';
95+
96+
const result: Uint8Array = ffmpeg
97+
.input({ source })
98+
.ouput({ format: 'avi' })
99+
.export();
100+
```
101+
102+
> If you were wondering, yes the memory is being managed for you.
103+
104+
## 📖 Examples
105+
106+
Take a look at these tests for more examples:
107+
108+
- https://github.com/diffusion-studio/ffmpeg-js/blob/main/src/tests/export.spec.ts
109+
- https://github.com/diffusion-studio/ffmpeg-js/blob/main/src/tests/commands.spec.ts
110+
111+
## 🛑 Limitations
112+
113+
- Webassembly is limited to 2GB
114+
- Difficult to handle in unit tests, it's probably best if you mock the FFmpeg class and leave the testing to us (which is also good practice).
115+
- The LGPLv2.1 build of FFmpeg without external libaries doesn't support any mainstream video delivery codecs such as h264/hevc/vp9. But it's very useful for audio processing, run the following commands for more information
116+
117+
```typescript
118+
console.log(await ffmpeg.codecs());
119+
console.log(await ffmpeg.formats());
120+
```
121+
122+
**BUT WAIT THERE IS MORE!** FFmpeg js is compatible with the binaries of `@ffmpeg/core`, which supports all major codecs like those mentioned before. Here is how you can configure it:
123+
124+
```typescript
125+
import {
126+
FFmpeg,
127+
FFmpegConfigurationGPLExtended,
128+
} from '@diffusion-studio/ffmpeg-js';
129+
130+
// FFmpegConfigurationGPLExtended will add the type extensions
131+
const ffmpeg = new FFmpeg<FFmpegConfigurationGPLExtended>({
132+
lib: 'gpl-extended',
133+
});
134+
```
135+
Thats it!
136+
> More FFmpeg LGPLv2.1 builds with external libraries such as VP9 and LAME will be added soon!
137+
138+
We believe that FFmpeg.js will significantly streamline your interaction with FFmpeg in the browser, providing a more effective and efficient coding experience. Happy coding! 🚀🌟

src/ffmpeg-base.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ export class FFmpegBase {
9696
* to receive commands
9797
*/
9898
public whenReady(cb: types.EventCallback) {
99-
this._whenReady.push(cb);
99+
if (this.isReady) cb();
100+
else this._whenReady.push(cb);
100101
}
101102

102103
/**

src/tests/capabilities.spec.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,18 @@ test.describe('FFmpeg get capabilities', async () => {
1717
const ready = await page.evaluate(async () => {
1818
if (!ffmpeg.isReady) {
1919
await new Promise<void>((resolve) => {
20-
globalThis.ffmpeg.whenReady(resolve);
20+
ffmpeg.whenReady(resolve);
2121
});
2222
}
2323

24-
return globalThis.ffmpeg.isReady;
24+
return ffmpeg.isReady;
2525
});
2626
expect(ready).toBe(true);
2727
});
2828

2929
test('test that popular audio decoders are available', async () => {
3030
const codecs = await page.evaluate(async () => {
31-
return await globalThis.ffmpeg.codecs();
31+
return await ffmpeg.codecs();
3232
});
3333

3434
expect(codecs).toBeTruthy();
@@ -43,7 +43,7 @@ test.describe('FFmpeg get capabilities', async () => {
4343

4444
test('test that popular audio encoders are available', async () => {
4545
const codecs = await page.evaluate(async () => {
46-
return await globalThis.ffmpeg.codecs();
46+
return await ffmpeg.codecs();
4747
});
4848

4949
expect(codecs).toBeTruthy();
@@ -56,7 +56,7 @@ test.describe('FFmpeg get capabilities', async () => {
5656

5757
test('test that popular video decoders are available', async () => {
5858
const codecs = await page.evaluate(async () => {
59-
return await globalThis.ffmpeg.codecs();
59+
return await ffmpeg.codecs();
6060
});
6161

6262
expect(codecs).toBeTruthy();
@@ -68,7 +68,7 @@ test.describe('FFmpeg get capabilities', async () => {
6868

6969
test('test that popular video encoders are available', async () => {
7070
const codecs = await page.evaluate(async () => {
71-
return await globalThis.ffmpeg.codecs();
71+
return await ffmpeg.codecs();
7272
});
7373

7474
expect(codecs).toBeTruthy();
@@ -81,7 +81,7 @@ test.describe('FFmpeg get capabilities', async () => {
8181

8282
test("test that audio/video encoders/decoders don't intersect", async () => {
8383
const codecs = await page.evaluate(async () => {
84-
return await globalThis.ffmpeg.codecs();
84+
return await ffmpeg.codecs();
8585
});
8686

8787
expect(codecs).toBeTruthy();
@@ -117,7 +117,7 @@ test.describe('FFmpeg get capabilities', async () => {
117117

118118
test('test that .formats returns popular demuxers', async () => {
119119
const formats = await page.evaluate(async () => {
120-
return await globalThis.ffmpeg.formats();
120+
return await ffmpeg.formats();
121121
});
122122

123123
expect(formats).toBeTruthy();
@@ -130,7 +130,7 @@ test.describe('FFmpeg get capabilities', async () => {
130130

131131
test('test that .formats returns popular muxers', async () => {
132132
const formats = await page.evaluate(async () => {
133-
return await globalThis.ffmpeg.formats();
133+
return await ffmpeg.formats();
134134
});
135135

136136
expect(formats).toBeTruthy();

src/tests/commands.spec.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,18 @@ test.describe('FFmpeg create command', async () => {
1616
const ready = await page.evaluate(async () => {
1717
if (!ffmpeg.isReady) {
1818
await new Promise<void>((resolve) => {
19-
globalThis.ffmpeg.whenReady(resolve);
19+
ffmpeg.whenReady(resolve);
2020
});
2121
}
2222

23-
return globalThis.ffmpeg.isReady;
23+
return ffmpeg.isReady;
2424
});
2525
expect(ready).toBe(true);
2626
});
2727

2828
test('test that basic command can be created', async () => {
2929
const command = await page.evaluate(async () => {
30-
return await globalThis.ffmpeg
30+
return await ffmpeg
3131
.input({ source: 'abc.mp4' })
3232
.ouput({ format: 'mp3' })
3333
.command();
@@ -41,11 +41,11 @@ test.describe('FFmpeg create command', async () => {
4141

4242
test('test that new input resets exisiting ones', async () => {
4343
const command = await page.evaluate(async () => {
44-
globalThis.ffmpeg
44+
ffmpeg
4545
.input({ source: 'abc.mp4' })
4646
.ouput({ format: 'mp3', duration: 20 });
4747

48-
return await globalThis.ffmpeg
48+
return await ffmpeg
4949
.input({ source: 'rrr.mp4' })
5050
.ouput({ format: 'm4v', duration: 10 })
5151
.command();
@@ -61,7 +61,7 @@ test.describe('FFmpeg create command', async () => {
6161

6262
test('test that input seeking works', async () => {
6363
const command = await page.evaluate(async () => {
64-
return await globalThis.ffmpeg
64+
return await ffmpeg
6565
.input({ source: 'cba.mp3', seek: 40 })
6666
.ouput({ format: 'wav' })
6767
.command();
@@ -77,7 +77,7 @@ test.describe('FFmpeg create command', async () => {
7777

7878
test('test that using an image sequence as input works', async () => {
7979
const command = await page.evaluate(async () => {
80-
return await globalThis.ffmpeg
80+
return await ffmpeg
8181
.input({
8282
sequence: ['my-image-0001.png', 'my-image-0002.png'],
8383
framerate: 30,
@@ -97,7 +97,7 @@ test.describe('FFmpeg create command', async () => {
9797

9898
test('test that adding filters works', async () => {
9999
const command = await page.evaluate(async () => {
100-
return await globalThis.ffmpeg
100+
return await ffmpeg
101101
.input({ source: 'ccc.mp4' })
102102
.videoFilter('rotate=90')
103103
.audioFilter('silencedetect=noise=0.0001')
@@ -118,7 +118,7 @@ test.describe('FFmpeg create command', async () => {
118118
test('test that adding a filter to multiple inputs throws error', async () => {
119119
const result = await page.evaluate(async () => {
120120
try {
121-
await globalThis.ffmpeg
121+
await ffmpeg
122122
.input({ source: 'abc.mov' })
123123
.input({ source: 'xyz.mov' })
124124
.videoFilter('scale=640:360')
@@ -135,7 +135,7 @@ test.describe('FFmpeg create command', async () => {
135135

136136
test('test that adding complex filters works', async () => {
137137
const command = await page.evaluate(async () => {
138-
return await globalThis.ffmpeg
138+
return await ffmpeg
139139
.input({ source: 'abc.webm' })
140140
.input({ source: 'xyz.png' })
141141
.complexFilter('overlay')
@@ -154,7 +154,7 @@ test.describe('FFmpeg create command', async () => {
154154

155155
test('test that adding video output options works', async () => {
156156
const command = await page.evaluate(async () => {
157-
return await globalThis.ffmpeg
157+
return await ffmpeg
158158
.input({ source: 'bbb.webm' })
159159
.ouput({
160160
format: 'm4v',
@@ -192,7 +192,7 @@ test.describe('FFmpeg create command', async () => {
192192

193193
test('test that video output can be disabled', async () => {
194194
const command = await page.evaluate(async () => {
195-
return await globalThis.ffmpeg
195+
return await ffmpeg
196196
.input({ source: 'bbb.webm' })
197197
.ouput({
198198
format: 'm4v',
@@ -206,7 +206,7 @@ test.describe('FFmpeg create command', async () => {
206206

207207
test('test that adding audio output options works', async () => {
208208
const command = await page.evaluate(async () => {
209-
return await globalThis.ffmpeg
209+
return await ffmpeg
210210
.input({ source: 'bbb.webm' })
211211
.ouput({
212212
format: 'm4v',
@@ -244,7 +244,7 @@ test.describe('FFmpeg create command', async () => {
244244

245245
test('test that audio output can be disabled', async () => {
246246
const command = await page.evaluate(async () => {
247-
return await globalThis.ffmpeg
247+
return await ffmpeg
248248
.input({ source: 'bbb.webm' })
249249
.ouput({
250250
format: 'm4v',

0 commit comments

Comments
 (0)