Skip to content

Commit

Permalink
added on progress callback function
Browse files Browse the repository at this point in the history
  • Loading branch information
k9p5 committed Jul 4, 2023
1 parent 2629576 commit 1ee27cc
Show file tree
Hide file tree
Showing 29 changed files with 222 additions and 27 deletions.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,31 @@ server: {
...
```

### △Next.js

Here is an example `next.config.js` that supports the SharedArrayBuffer:
```
module.exports = {
async headers() {
return [
{
source: '/',
headers: [
{
key: 'Cross-Origin-Embedder-Policy',
value: 'require-corp',
},
{
key: 'Cross-Origin-Opener-Policy',
value: 'same-origin',
},
],
},
];
},
};
```

## 💻 Usage

Somewhere in your project you need to initiate a ffmpeg instance.
Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/tests/main.ts"></script>
<script type="module" src="/tests/main.ts"></script>
</body>
</html>
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@diffusion-studio/ffmpeg-js",
"private": false,
"version": "0.0.2",
"version": "0.1.0",
"description": "FFmpeg.js - Use FFmpeg in the browser powered by WebAssembly",
"type": "module",
"files": [
Expand Down Expand Up @@ -57,7 +57,7 @@
"build": "rm -r -f ./dist && tsc && vite build",
"preview": "vite preview",
"test": "npx playwright test --project=chromium",
"prettier": "npx prettier --write ./src",
"prettier": "npx prettier --write ./src ./tests",
"tarball": "npm view @diffusion-studio/ffmpeg-js dist.tarball"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export default defineConfig({
command: 'npm run dev',
url: 'http://localhost:5173/',
},
testDir: './src/tests',
testDir: './tests',
/* Run tests in files in parallel */
fullyParallel: false,
/* Fail the build on CI if you accidentally left test.only in the source code. */
Expand Down
Binary file added public/samples/video.avi
Binary file not shown.
Binary file added public/samples/video.mov
Binary file not shown.
Binary file added public/samples/video.ogg
Binary file not shown.
Binary file added public/samples/video.webm
Binary file not shown.
Binary file added public/samples/video.wmv
Binary file not shown.
Binary file added public/samples/video_xl.mp4
Binary file not shown.
26 changes: 24 additions & 2 deletions src/ffmpeg-base.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { noop, toBlobURL, toUint8Array } from './utils';
import * as types from './types';
import * as utils from './utils';

export class FFmpegBase {
private _module: any;
Expand All @@ -12,7 +13,9 @@ export class FFmpegBase {

private _whenReady: Array<types.EventCallback> = [];
private _whenExecutionDone: Array<types.EventCallback> = [];

private _onMessage: Array<types.MessageCallback> = [];
private _onProgress: Array<types.ProgressCallback> = [];

private _memory: string[] = [];

Expand All @@ -33,9 +36,12 @@ export class FFmpegBase {
*/
private handleMessage(msg: string) {
this._logger(msg);
if (typeof msg == 'string' && msg.match(/(FFMPEG_END|error)/i)) {
if (msg.match(/(FFMPEG_END|error)/i)) {
this._whenExecutionDone.forEach((cb) => cb());
}
if (msg.match(/^frame=/)) {
this._onProgress.forEach((cb) => cb(utils.parseProgress(msg)));
}
this._onMessage.forEach((cb) => cb(msg));
}

Expand Down Expand Up @@ -117,12 +123,28 @@ export class FFmpegBase {

/**
* Remove the callback function from the
* message listeners
* message callbacks
*/
public removeOnMessage(cb: types.MessageCallback) {
this._onMessage = this._onMessage.filter((item) => item != cb);
}

/**
* Gets called when a number of frames
* has been rendered
*/
public onProgress(cb: types.ProgressCallback) {
this._onProgress.push(cb);
}

/**
* Remove the callback function from the
* progress callbacks
*/
public removeOnProgress(cb: types.ProgressCallback) {
this._onProgress = this._onProgress.filter((item) => item != cb);
}

/**
* Use this message to execute ffmpeg commands
*/
Expand Down
2 changes: 1 addition & 1 deletion src/ffmpeg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class FFmpeg<

public constructor(settings: types.FFmpegSettings = {}) {
let logger = console.log;
let source = configs[settings?.config ?? "lgpl-base"];
let source = configs[settings?.config ?? 'lgpl-base'];

if (settings?.log == false) {
logger = noop;
Expand Down
3 changes: 0 additions & 3 deletions src/tests/main.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/types/cmd-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type AudioOutputOptions<AudioCodecExtension> = {
* codecs with `ffmpeg.codecs()`. Use `"copy"` to use the input codec.
* @example "aac"
*/
codec: 'copy' | AudioCodecExtension;
codec?: 'copy' | AudioCodecExtension;
/**
* An integer representing the number of frame
* samples per second (Hz).
Expand Down Expand Up @@ -49,7 +49,7 @@ export type VideoOutputOptions<VideoCodecExtension> = {
* codecs with `ffmpeg.codecs()`. Use `"copy"` to use the input codec.
* @example "h263"
*/
codec: 'copy' | VideoCodecExtension;
codec?: 'copy' | VideoCodecExtension;
/**
* A number repersenting the framerate
* @example 30 // fps
Expand Down
7 changes: 7 additions & 0 deletions src/types/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ export type EventCallback = () => void;
*/
export type MessageCallback = (msg: string) => void;

/**
* Defines a callback function that
* gets called during rendering when a number
* of frames has been rendered
*/
export type ProgressCallback = (progress: number) => void;

/**
* Defines encoder and decoder records that
* contain the supported formats
Expand Down
2 changes: 2 additions & 0 deletions src/types/gpl-extended.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export type ExtensionGPLExtended =
| 'mp4'
| 'mpg'
| 'mpeg'
| 'mov'
| 'ts'
| 'm2t'
| 'm2ts'
Expand Down Expand Up @@ -262,6 +263,7 @@ export type VideoEncoderGPLExtended =
| 'webp'
| 'wmv1'
| 'wmv2'
| 'webm'
| 'wrapped_avframe'
| 'xbm'
| 'xface'
Expand Down
2 changes: 2 additions & 0 deletions src/types/lgpl-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export type ExtensionBase =
| 'mp4'
| 'mpg'
| 'mpeg'
| 'mov'
| 'ts'
| 'm2t'
| 'm2ts'
Expand Down Expand Up @@ -105,6 +106,7 @@ export type ExtensionBase =
| 'voc'
| 'w64'
| 'wav'
| 'webm'
| 'xml'
| 'vtt'
| 'wtv'
Expand Down
14 changes: 14 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,17 @@ export const noop = (msg?: any, ...params: any[]) => {
msg;
params;
};

/**
* Parse a ffmpeg progress event output
*/
export const parseProgress = (msg: string): number => {
// strip non required information
const match = msg
.match(/(^frame=)(\W)*([0-9]{1,})(\W)/)
?.at(0) // get first match
?.replace(/frame=/, '') // replace prefix
?.trim(); // remove spaces surrounding the number

return parseInt(match ?? '0');
};
50 changes: 38 additions & 12 deletions src/tests/base.spec.ts → tests/base.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ test.describe('FFmpegBase functionalities', async () => {
test('test intercepting logs', async () => {
const messages = await page.evaluate(async () => {
const _messages: string[] = [];
ffmpeg.onMessage(((msg) => {
ffmpeg.onMessage((msg) => {
_messages.push(msg);
}));
await ffmpeg.exec(["-help"]);
});
await ffmpeg.exec(['-help']);
return _messages;
});
expect(messages.length).toBeGreaterThan(0);
Expand All @@ -44,13 +44,13 @@ test.describe('FFmpegBase functionalities', async () => {
const _messages0: string[] = [];
const _messages1: string[] = [];

const cb0 = (msg: string) => _messages0.push(msg)
const cb1 = (msg: string) => _messages1.push(msg)
const cb0 = (msg: string) => _messages0.push(msg);
const cb1 = (msg: string) => _messages1.push(msg);

ffmpeg.onMessage(cb0);
ffmpeg.onMessage(cb1);

await ffmpeg.exec(["-help"]);
await ffmpeg.exec(['-help']);

ffmpeg.removeOnMessage(cb1);

Expand All @@ -59,17 +59,19 @@ test.describe('FFmpegBase functionalities', async () => {

expect(messages[0].length).toBeGreaterThan(0);
expect(messages[1].length).toBeGreaterThan(0);
// callback function 1 should have recieved
// callback function 1 should have recieved
// half as many messages than callback funtion 0
expect(messages[1].length).toBeGreaterThan(messages[0].length / 2);
});


test('test clearing memory works', async () => {
const result = await page.evaluate(async () => {
const inputName = 'input.ogg';
const outputName = 'output.wav';
await ffmpeg.writeFile(inputName, 'http://localhost:5173/samples/audio.ogg');
await ffmpeg.writeFile(
inputName,
'http://localhost:5173/samples/audio.ogg'
);
await ffmpeg.exec(['-i', inputName, outputName]);
const render = ffmpeg.readFile(outputName).length;

Expand All @@ -81,16 +83,40 @@ test.describe('FFmpegBase functionalities', async () => {
// input and output should stay null
try {
input = ffmpeg.readFile(inputName);
} catch (e) { }
} catch (e) {}
try {
output = ffmpeg.readFile(outputName);
} catch (e) { }
} catch (e) {}

return { render, input, output }
return { render, input, output };
});

expect(result.render).toBeGreaterThan(0);
expect(result.output).toBe(null);
expect(result.input).toBe(null);
});

test('test progress event gets called during export', async () => {
const result = await page.evaluate(async () => {
const progress: number[] = [];

ffmpeg.onProgress((frames: number) => {
progress.push(frames);
});

await ffmpeg.writeFile(
'input',
'http://localhost:5173/samples/video.mp4'
);
await ffmpeg.exec(['-i', 'input', 'output.gif']);

return progress;
});

expect(result.length).toBeGreaterThan(0);
for (const progress of result) {
expect(typeof progress).toBe('number');
expect(progress).toBeGreaterThan(0);
}
});
});
File renamed without changes.
File renamed without changes.
62 changes: 62 additions & 0 deletions tests/convert.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { test, expect, Page } from '@playwright/test';
import { VIDEO_EXTENSIONS, SUPPORTED_VIDEO_CONVERSIONS } from './fixtures';

// Annotate entire file as serial.
test.describe.configure({ mode: 'serial' });

let page: Page;

test.describe('FFmpeg basic file extension conversions', async () => {
/**
* Get index page and wait until ffmpeg is ready
*/
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
await page.goto('http://localhost:5173/');

const ready = await page.evaluate(async () => {
if (!ffmpeg.isReady) {
await new Promise<void>((resolve) => {
ffmpeg.whenReady(resolve);
});
}

return ffmpeg.isReady;
});
expect(ready).toBe(true);
});

for (const format of VIDEO_EXTENSIONS) {
test(`test converting ${format} into gif`, async () => {
const length = await page.evaluate(async (ext) => {
const result = await ffmpeg
.input({ source: `http://localhost:5173/samples/video.${ext}` })
.ouput({
format: 'gif',
video: { size: { width: 240, height: 135 }, framerate: 5 },
})
.export();

return result?.length;
}, format);
expect(length).toBeGreaterThan(0);
});
}

for (const formats of SUPPORTED_VIDEO_CONVERSIONS) {
test(`test converting video from ${formats[0]} into ${formats[1]} with trim`, async () => {
const length = await page.evaluate(async (ext) => {
const result = await ffmpeg
.input({
source: `http://localhost:5173/samples/video.${ext[0]}`,
seek: 2,
})
.ouput({ format: ext[1], duration: 3 })
.export();

return result?.length;
}, formats);
expect(length).toBeGreaterThan(0);
});
}
});
File renamed without changes.
Loading

0 comments on commit 1ee27cc

Please sign in to comment.