Add license headers, add @mediabunny/server to build process, add doc blocks, small fixes

This commit is contained in:
Vanilagy
2026-05-12 15:04:32 +02:00
parent 633d1bc235
commit af43f67835
15 changed files with 197 additions and 27 deletions
+5
View File
@@ -93,6 +93,11 @@ jobs:
packages/flac-encoder/dist/bundles/mediabunny-flac-encoder.mjs packages/flac-encoder/dist/bundles/mediabunny-flac-encoder.mjs
packages/flac-encoder/dist/bundles/mediabunny-flac-encoder.min.mjs packages/flac-encoder/dist/bundles/mediabunny-flac-encoder.min.mjs
packages/flac-encoder/dist/mediabunny-flac-encoder.d.ts packages/flac-encoder/dist/mediabunny-flac-encoder.d.ts
packages/server/dist/bundles/mediabunny-server.cjs
packages/server/dist/bundles/mediabunny-server.min.cjs
packages/server/dist/bundles/mediabunny-server.mjs
packages/server/dist/bundles/mediabunny-server.min.mjs
packages/server/dist/mediabunny-server.d.ts
- name: Publish workspace packages to npm - name: Publish workspace packages to npm
run: npm publish --access public --workspaces ${{ github.event.release.prerelease && '--tag beta' || '' }} run: npm publish --access public --workspaces ${{ github.event.release.prerelease && '--tag beta' || '' }}
+2 -1
View File
@@ -24,5 +24,6 @@
"@mediabunny/mp3-encoder": "Adds MP3 encoder support to Mediabunny.", "@mediabunny/mp3-encoder": "Adds MP3 encoder support to Mediabunny.",
"@mediabunny/ac3": "Adds AC-3/E-AC-3 decoder and encoder support to Mediabunny.", "@mediabunny/ac3": "Adds AC-3/E-AC-3 decoder and encoder support to Mediabunny.",
"@mediabunny/aac-encoder": "Polyfills AAC encoder support to Mediabunny.", "@mediabunny/aac-encoder": "Polyfills AAC encoder support to Mediabunny.",
"@mediabunny/flac-encoder": "Adds FLAC encoder support to Mediabunny." "@mediabunny/flac-encoder": "Adds FLAC encoder support to Mediabunny.",
"@mediabunny/server": "Adds full video/audio decoder and encoder support to Mediabunny running in server-side environments such as Node, Bun, or Deno."
} }
+1 -1
View File
@@ -51,7 +51,7 @@
"docs:dev": "vitepress dev docs", "docs:dev": "vitepress dev docs",
"docs:build": "npm run build && npm run docs:generate && vitepress build docs && npm run examples:build && cp dist/mediabunny.d.ts dist-docs/", "docs:build": "npm run build && npm run docs:generate && vitepress build docs && npm run examples:build && cp dist/mediabunny.d.ts dist-docs/",
"docs:preview": "vitepress preview docs", "docs:preview": "vitepress preview docs",
"docs:generate": "tsx scripts/generate-api-docs.ts src/index.ts packages/mp3-encoder/src/index.ts packages/ac3/src/index.ts packages/aac-encoder/src/index.ts packages/flac-encoder/src/index.ts docs/api-config.json", "docs:generate": "tsx scripts/generate-api-docs.ts src/index.ts packages/mp3-encoder/src/index.ts packages/ac3/src/index.ts packages/aac-encoder/src/index.ts packages/flac-encoder/src/index.ts packages/server/src/index.ts docs/api-config.json",
"dev": "vite", "dev": "vite",
"examples:build": "vite build", "examples:build": "vite build",
"fix-build-import-paths": "tsx scripts/add-import-extensions.ts", "fix-build-import-paths": "tsx scripts/add-import-extensions.ts",
+4 -2
View File
@@ -2,7 +2,9 @@
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"mainEntryPointFilePath": "dist/modules/src/index.d.ts", "mainEntryPointFilePath": "dist/modules/src/index.d.ts",
"bundledPackages": [], "bundledPackages": [],
"compiler": {}, "compiler": {
"tsconfigFilePath": "<projectFolder>/tsconfig.api-extractor.json"
},
"apiReport": { "apiReport": {
"enabled": false "enabled": false
}, },
@@ -11,7 +13,7 @@
}, },
"dtsRollup": { "dtsRollup": {
"enabled": true, "enabled": true,
"untrimmedFilePath": "dist/mediabunny-flac-encoder.d.ts" "untrimmedFilePath": "dist/mediabunny-server.d.ts"
}, },
"tsdocMetadata": { "tsdocMetadata": {
"enabled": false "enabled": false
+16 -3
View File
@@ -1,4 +1,12 @@
import { AudioCodec, AudioSample, CustomAudioDecoder, EncodedPacket, MaybePromise } from 'mediabunny'; /*!
* Copyright (c) 2026-present, Vanilagy and contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { AudioCodec, AudioSample, CustomAudioDecoder, EncodedPacket, type MaybePromise } from 'mediabunny';
import * as NodeAv from 'node-av'; import * as NodeAv from 'node-av';
import { CODEC_TO_CODEC_ID, getChannelLayout } from './misc'; import { CODEC_TO_CODEC_ID, getChannelLayout } from './misc';
import { assert, toUint8Array } from '../../../src/misc'; import { assert, toUint8Array } from '../../../src/misc';
@@ -76,8 +84,13 @@ export class NodeAvAudioDecoder extends CustomAudioDecoder {
receiveFrame(ret: number) { receiveFrame(ret: number) {
NodeAv.FFmpegError.throwIfError(ret, 'Receive frame'); NodeAv.FFmpegError.throwIfError(ret, 'Receive frame');
const timestamp = Number(this.frame.pts) / this.config.sampleRate; const clone = this.frame.clone();
this.onSample(new AudioSample(new NodeAvFrameAudioSampleResource(this.frame, timestamp))); if (!clone) {
throw new Error('Allocation failure during frame clone.');
}
clone.timeBase = new NodeAv.Rational(1, this.config.sampleRate);
this.onSample(new AudioSample(new NodeAvFrameAudioSampleResource(clone)));
} }
async flush(): Promise<void> { async flush(): Promise<void> {
+16 -1
View File
@@ -1,4 +1,19 @@
import { AudioCodec, AudioSample, CustomAudioEncoder, MaybePromise, QUALITY_MEDIUM, EncodedPacket } from 'mediabunny'; /*!
* Copyright (c) 2026-present, Vanilagy and contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import {
AudioCodec,
AudioSample,
CustomAudioEncoder,
type MaybePromise,
QUALITY_MEDIUM,
EncodedPacket,
} from 'mediabunny';
import * as NodeAv from 'node-av'; import * as NodeAv from 'node-av';
import { CODEC_TO_CODEC_ID, fromAudioSampleFormat, getChannelLayout } from './misc'; import { CODEC_TO_CODEC_ID, fromAudioSampleFormat, getChannelLayout } from './misc';
import { assert, toUint8Array } from '../../../src/misc'; import { assert, toUint8Array } from '../../../src/misc';
+39 -11
View File
@@ -1,22 +1,49 @@
/*!
* Copyright (c) 2026-present, Vanilagy and contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { AudioSampleResource } from 'mediabunny'; import { AudioSampleResource } from 'mediabunny';
import * as NodeAv from 'node-av'; import * as NodeAv from 'node-av';
import { toAudioSampleFormat } from './misc'; import { toAudioSampleFormat } from './misc';
import { assert, toUint8Array } from '../../../src/misc'; import { assert, toUint8Array } from '../../../src/misc';
/**
* A custom `AudioSampleResource` backed by NodeAV's
* [`Frame`](https://seydx.github.io/node-av/api/lib/classes/Frame.html), which in turn is backed by FFmpeg's
* [`AVFrame`](https://ffmpeg.org/doxygen/2.7/structAVFrame.html). You can use this resource to create `AudioSample`
* instances that are directly backed by FFmpeg's `AVFrame` without data having to be copied.
*
* @group \@mediabunny/server
* @public
*/
export class NodeAvFrameAudioSampleResource extends AudioSampleResource { export class NodeAvFrameAudioSampleResource extends AudioSampleResource {
frame: NodeAv.Frame; /** @internal */
timestamp: number; _frame: NodeAv.Frame | null;
constructor(frame: NodeAv.Frame, timestamp: number) { /**
super(); * The NodeAV [`Frame`](https://seydx.github.io/node-av/api/lib/classes/Frame.html) instance backing this resource.
* Access throws if the resource has already been closed.
const clone = frame.clone(); */
if (!clone) { get frame() {
throw new Error('Allocation failure during frame clone.'); if (!this._frame) {
throw new Error('NodeAvFrameAudioSampleResource has been closed.');
} }
this.frame = clone; return this._frame;
this.timestamp = timestamp; }
constructor(frame: NodeAv.Frame) {
super();
if (frame.getMediaType() !== NodeAv.AVMEDIA_TYPE_AUDIO) {
throw new Error('NodeAvFrameAudioSampleResource must be initialized with an audio frame.');
}
this._frame = frame;
} }
getFormat(): AudioSampleFormat { getFormat(): AudioSampleFormat {
@@ -41,11 +68,12 @@ export class NodeAvFrameAudioSampleResource extends AudioSampleResource {
} }
getTimestamp(): number { getTimestamp(): number {
return this.timestamp; return Number(this.frame.pts) / this.frame.timeBase.den;
} }
close(): void { close(): void {
this.frame.free(); this.frame.free();
this._frame = null;
} }
getDataPlane(planeIndex: number): Uint8Array { getDataPlane(planeIndex: number): Uint8Array {
+25
View File
@@ -1,3 +1,11 @@
/*!
* Copyright (c) 2026-present, Vanilagy and contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { registerDecoder, registerEncoder, registerVideoSampleTransformer } from 'mediabunny'; import { registerDecoder, registerEncoder, registerVideoSampleTransformer } from 'mediabunny';
import * as NodeAv from 'node-av'; import * as NodeAv from 'node-av';
import { NodeAvVideoDecoder } from './video-decoder'; import { NodeAvVideoDecoder } from './video-decoder';
@@ -18,6 +26,20 @@ if ((globalThis as Record<symbol, unknown>)[SERVER_LOADED_SYMBOL]) {
(globalThis as Record<symbol, unknown>)[SERVER_LOADED_SYMBOL] = true; (globalThis as Record<symbol, unknown>)[SERVER_LOADED_SYMBOL] = true;
let registered = false; let registered = false;
/**
* Registers video and audio decoders and encoders for all codecs, using FFmpeg's libavcodec under the hood.
* Additionally, a custom `VideoSample` transformer based on libavfilter is registered to enable resizing, rotation and
* cropping of video frames.
*
* Make sure to call this function before interacting with Mediabunny.
*
* The decoders and encoders will automatically detect hardware acceleration support for each codec and platform and
* make use of it if applicable.
*
* @group \@mediabunny/server
* @public
*/
export const registerMediabunnyServer = () => { export const registerMediabunnyServer = () => {
if (registered) { if (registered) {
return; return;
@@ -36,3 +58,6 @@ export const registerMediabunnyServer = () => {
registerVideoSampleTransformer(transformVideoSample); registerVideoSampleTransformer(transformVideoSample);
}; };
export { NodeAvFrameVideoSampleResource } from './video-sample';
export { NodeAvFrameAudioSampleResource } from './audio-sample';
+8
View File
@@ -1,3 +1,11 @@
/*!
* Copyright (c) 2026-present, Vanilagy and contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { VideoSamplePixelFormat, MediaCodec } from 'mediabunny'; import { VideoSamplePixelFormat, MediaCodec } from 'mediabunny';
import * as NodeAv from 'node-av'; import * as NodeAv from 'node-av';
+9 -1
View File
@@ -1,4 +1,12 @@
import { CustomVideoDecoder, VideoCodec, EncodedPacket, VideoSample, MaybePromise, Rational } from 'mediabunny'; /*!
* Copyright (c) 2026-present, Vanilagy and contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { CustomVideoDecoder, VideoCodec, EncodedPacket, VideoSample, type MaybePromise, Rational } from 'mediabunny';
import * as NodeAv from 'node-av'; import * as NodeAv from 'node-av';
import { CODEC_TO_CODEC_ID, getHardwareDecoderCodec, LIBVPX_VP9 } from './misc'; import { CODEC_TO_CODEC_ID, getHardwareDecoderCodec, LIBVPX_VP9 } from './misc';
import { assert, binarySearchLessOrEqual, simplifyRational, toUint8Array } from '../../../src/misc'; import { assert, binarySearchLessOrEqual, simplifyRational, toUint8Array } from '../../../src/misc';
+9 -1
View File
@@ -1,6 +1,14 @@
/*!
* Copyright (c) 2026-present, Vanilagy and contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { import {
CustomVideoEncoder, CustomVideoEncoder,
MaybePromise, type MaybePromise,
QUALITY_MEDIUM, QUALITY_MEDIUM,
VideoCodec, VideoCodec,
VideoSample, VideoSample,
+43 -3
View File
@@ -1,4 +1,13 @@
/*!
* Copyright (c) 2026-present, Vanilagy and contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { import {
type MaybePromise,
VideoSamplePixelFormat, VideoSamplePixelFormat,
VideoSampleResource, VideoSampleResource,
VideoSampleColorSpace, VideoSampleColorSpace,
@@ -9,7 +18,7 @@ import {
VideoSampleTransformationDescription, VideoSampleTransformationDescription,
} from 'mediabunny'; } from 'mediabunny';
import * as NodeAv from 'node-av'; import * as NodeAv from 'node-av';
import { assert, MaybePromise, toUint8Array } from '../../../src/misc'; import { assert, toUint8Array } from '../../../src/misc';
import { import {
toPixelFormat, toPixelFormat,
unmapColorPrimaries, unmapColorPrimaries,
@@ -29,13 +38,43 @@ const JPEG_RANGE_PIX_FORMATS = new Set([
NodeAv.AV_PIX_FMT_YUVJ444P, NodeAv.AV_PIX_FMT_YUVJ444P,
]); ]);
/**
* A custom `VideoSampleResource` backed by NodeAV's
* [`Frame`](https://seydx.github.io/node-av/api/lib/classes/Frame.html), which in turn is backed by FFmpeg's
* [`AVFrame`](https://ffmpeg.org/doxygen/2.7/structAVFrame.html). You can use this resource to create `VideoSample`
* instances that are directly backed by FFmpeg's `AVFrame` without data having to be copied. Since `AVFrame`s can
* themselves be backed by data on the GPU, this enables zero-copy hardware-accelerated decode and encode paths.
*
* When using Electron, you can directly create `Frame` instances without the data having to leave the GPU. For more,
* see [NodeAV's docs](https://seydx.github.io/node-av/api/lib/classes/Frame.html).
*
* @group \@mediabunny/server
* @public
*/
export class NodeAvFrameVideoSampleResource extends VideoSampleResource { export class NodeAvFrameVideoSampleResource extends VideoSampleResource {
frame: NodeAv.Frame; /** @internal */
_frame: NodeAv.Frame | null;
/**
* The NodeAV [`Frame`](https://seydx.github.io/node-av/api/lib/classes/Frame.html) instance backing this resource.
* Access throws if the resource has already been closed.
*/
get frame() {
if (!this._frame) {
throw new Error('NodeAvFrameVideoSampleResource has been closed.');
}
return this._frame;
}
constructor(frame: NodeAv.Frame) { constructor(frame: NodeAv.Frame) {
super(); super();
this.frame = frame; if (frame.getMediaType() !== NodeAv.AVMEDIA_TYPE_VIDEO) {
throw new Error('NodeAvFrameVideoSampleResource must be initialized with a video frame.');
}
this._frame = frame;
} }
getFormat(): VideoSamplePixelFormat | null { getFormat(): VideoSamplePixelFormat | null {
@@ -82,6 +121,7 @@ export class NodeAvFrameVideoSampleResource extends VideoSampleResource {
close(): void { close(): void {
this.frame.free(); this.frame.free();
this._frame = null;
} }
getDataPlanes(): MaybePromise<VideoDataPlane[]> { getDataPlanes(): MaybePromise<VideoDataPlane[]> {
@@ -0,0 +1,6 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"paths": {} // Fails if we don't override it due to the Mediabunny imports
}
}
+10 -2
View File
@@ -9,16 +9,22 @@ rm -rf packages/mp3-encoder/dist
rm -rf packages/ac3/dist rm -rf packages/ac3/dist
rm -rf packages/aac-encoder/dist rm -rf packages/aac-encoder/dist
rm -rf packages/flac-encoder/dist rm -rf packages/flac-encoder/dist
rm -rf packages/server/dist
# Ensure license headers on all source files # Ensure license headers on all source files
tsx scripts/ensure-license-headers.ts tsx scripts/ensure-license-headers.ts
# Type check & generate .js and .d.ts files # Type check & generate .js and .d.ts files
tsc -p src tsc -p src --stripInternal false # Don't strip internals since the packages may use them
tsc -p packages/mp3-encoder tsc -p packages/mp3-encoder
tsc -p packages/ac3 tsc -p packages/ac3
tsc -p packages/aac-encoder tsc -p packages/aac-encoder
tsc -p packages/flac-encoder tsc -p packages/flac-encoder
tsc -p packages/server
# Generate the root again, now with internals properly stripped
rm -rf dist
tsc -p src
# So that the resulting files use valid ESM imports with file extension. This only runs for the core Mediabunny as only # So that the resulting files use valid ESM imports with file extension. This only runs for the core Mediabunny as only
# it ships the individual files to npm (for tree shaking, because it's large) # it ships the individual files to npm (for tree shaking, because it's large)
@@ -33,6 +39,7 @@ api-extractor run -c packages/mp3-encoder/api-extractor.json
api-extractor run -c packages/ac3/api-extractor.json api-extractor run -c packages/ac3/api-extractor.json
api-extractor run -c packages/aac-encoder/api-extractor.json api-extractor run -c packages/aac-encoder/api-extractor.json
api-extractor run -c packages/flac-encoder/api-extractor.json api-extractor run -c packages/flac-encoder/api-extractor.json
api-extractor run -c packages/server/api-extractor.json
# Checks that all symbols are documented # Checks that all symbols are documented
tsx scripts/check-docblocks.ts dist/mediabunny.d.ts tsx scripts/check-docblocks.ts dist/mediabunny.d.ts
@@ -40,6 +47,7 @@ tsx scripts/check-docblocks.ts packages/mp3-encoder/dist/mediabunny-mp3-encoder.
tsx scripts/check-docblocks.ts packages/ac3/dist/mediabunny-ac3.d.ts tsx scripts/check-docblocks.ts packages/ac3/dist/mediabunny-ac3.d.ts
tsx scripts/check-docblocks.ts packages/aac-encoder/dist/mediabunny-aac-encoder.d.ts tsx scripts/check-docblocks.ts packages/aac-encoder/dist/mediabunny-aac-encoder.d.ts
tsx scripts/check-docblocks.ts packages/flac-encoder/dist/mediabunny-flac-encoder.d.ts tsx scripts/check-docblocks.ts packages/flac-encoder/dist/mediabunny-flac-encoder.d.ts
tsx scripts/check-docblocks.ts packages/server/dist/mediabunny-server.d.ts
# Checks that API docs are generatable # Checks that API docs are generatable
npm run docs:generate -- --dry npm run docs:generate -- --dry
@@ -50,4 +58,4 @@ echo 'export as namespace MediabunnyMp3Encoder;' >> packages/mp3-encoder/dist/me
echo 'export as namespace MediabunnyAc3;' >> packages/ac3/dist/mediabunny-ac3.d.ts echo 'export as namespace MediabunnyAc3;' >> packages/ac3/dist/mediabunny-ac3.d.ts
echo 'export as namespace MediabunnyAacEncoder;' >> packages/aac-encoder/dist/mediabunny-aac-encoder.d.ts echo 'export as namespace MediabunnyAacEncoder;' >> packages/aac-encoder/dist/mediabunny-aac-encoder.d.ts
echo 'export as namespace MediabunnyFlacEncoder;' >> packages/flac-encoder/dist/mediabunny-flac-encoder.d.ts echo 'export as namespace MediabunnyFlacEncoder;' >> packages/flac-encoder/dist/mediabunny-flac-encoder.d.ts
echo 'export as namespace MediabunnyServer;' >> packages/server/dist/mediabunny-server.d.ts
+4 -1
View File
@@ -35,9 +35,12 @@ const checkDirectory = (dirPath: string) => {
}; };
checkDirectory(path.join(__dirname, '..', 'src')); checkDirectory(path.join(__dirname, '..', 'src'));
checkDirectory(path.join(__dirname, '..', 'shared'));
checkDirectory(path.join(__dirname, '..', 'packages', 'mp3-encoder', 'src')); checkDirectory(path.join(__dirname, '..', 'packages', 'mp3-encoder', 'src'));
checkDirectory(path.join(__dirname, '..', 'packages', 'ac3', 'src')); checkDirectory(path.join(__dirname, '..', 'packages', 'ac3', 'src'));
checkDirectory(path.join(__dirname, '..', 'shared')); checkDirectory(path.join(__dirname, '..', 'packages', 'flac-encoder', 'src'));
checkDirectory(path.join(__dirname, '..', 'packages', 'aac-encoder', 'src'));
checkDirectory(path.join(__dirname, '..', 'packages', 'server', 'src'));
if (missingFiles.length > 0) { if (missingFiles.length > 0) {
console.error('Files missing license header:'); console.error('Files missing license header:');