mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-10 09:13:47 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dceb67a065 | ||
|
|
dd39b8b2ca | ||
|
|
71963cbd92 | ||
|
|
816bb020fb | ||
|
|
e5aeff89e3 | ||
|
|
3620b04caf |
@@ -5,6 +5,7 @@ on:
|
|||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
id-token: write # Required for OIDC
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -14,7 +15,7 @@ jobs:
|
|||||||
permissions: write-all
|
permissions: write-all
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@@ -27,12 +28,15 @@ jobs:
|
|||||||
git push origin release
|
git push origin release
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 22
|
node-version: 22
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
registry-url: https://registry.npmjs.org
|
registry-url: https://registry.npmjs.org
|
||||||
|
|
||||||
|
- name: Update npm
|
||||||
|
run: npm install -g npm@latest
|
||||||
|
|
||||||
- name: Get package.json version
|
- name: Get package.json version
|
||||||
id: package-json-version
|
id: package-json-version
|
||||||
run: echo "version=v$(cat package.json | jq '.version' --raw-output)" >> $GITHUB_OUTPUT
|
run: echo "version=v$(cat package.json | jq '.version' --raw-output)" >> $GITHUB_OUTPUT
|
||||||
@@ -58,11 +62,7 @@ jobs:
|
|||||||
run: gh release upload ${{ github.event.release.tag_name }} dist/bundles/mediabunny.cjs dist/bundles/mediabunny.min.cjs dist/bundles/mediabunny.mjs dist/bundles/mediabunny.min.mjs dist/mediabunny.d.ts packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.js packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.min.js packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.mjs packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.min.mjs packages/mp3-encoder/dist/mediabunny-mp3-encoder.d.ts
|
run: gh release upload ${{ github.event.release.tag_name }} dist/bundles/mediabunny.cjs dist/bundles/mediabunny.min.cjs dist/bundles/mediabunny.mjs dist/bundles/mediabunny.min.mjs dist/mediabunny.d.ts packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.js packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.min.js packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.mjs packages/mp3-encoder/dist/bundles/mediabunny-mp3-encoder.min.mjs packages/mp3-encoder/dist/mediabunny-mp3-encoder.d.ts
|
||||||
|
|
||||||
- name: Publish Mediabunny to npm
|
- name: Publish Mediabunny to npm
|
||||||
run: npm publish --provenance --access public
|
run: npm publish --access public
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: Publish workspace packages to npm
|
- name: Publish workspace packages to npm
|
||||||
run: npm publish --provenance --access public --workspaces
|
run: npm publish --access public --workspaces
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|||||||
+12
-1
@@ -14,7 +14,18 @@
|
|||||||
source: new Mediabunny.BlobSource(file),
|
source: new Mediabunny.BlobSource(file),
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(await input.getTracks());
|
const audioTrack = await input.getPrimaryAudioTrack();
|
||||||
|
const decoderConfig = await audioTrack.getDecoderConfig();
|
||||||
|
console.log(decoderConfig);
|
||||||
|
|
||||||
|
/*
|
||||||
|
const videoTrack = await input.getPrimaryVideoTrack();
|
||||||
|
const sink = new Mediabunny.EncodedPacketSink(videoTrack);
|
||||||
|
|
||||||
|
for await (const packet of sink.packets()) {
|
||||||
|
console.log(packet);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
return;
|
return;
|
||||||
|
|||||||
Generated
+6
-6
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"version": "1.25.3",
|
"version": "1.25.5",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"version": "1.25.3",
|
"version": "1.25.5",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
@@ -7739,9 +7739,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/mediabunny": {
|
"node_modules/mediabunny": {
|
||||||
"version": "1.25.2",
|
"version": "1.25.4",
|
||||||
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.25.2.tgz",
|
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.25.4.tgz",
|
||||||
"integrity": "sha512-bS+lFdHsvvWUGK8ARDzhHnjmzLDPLmRfwlqKiXSCk+cdlyjKpiSHdAaKmrI6+2hD9Tmeqr39OQJk67nG/kAJAQ==",
|
"integrity": "sha512-oEMpnAj3fwP6mel84liF7vG58fl0RMC77jS9nmy0UH45nSfZgQqPj06TRQoje1n9hLpz6Dun4+UyCg6x+EKUvw==",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
@@ -12065,7 +12065,7 @@
|
|||||||
},
|
},
|
||||||
"packages/mp3-encoder": {
|
"packages/mp3-encoder": {
|
||||||
"name": "@mediabunny/mp3-encoder",
|
"name": "@mediabunny/mp3-encoder",
|
||||||
"version": "1.25.3",
|
"version": "1.25.5",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/emscripten": "^1.40.1"
|
"@types/emscripten": "^1.40.1"
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.25.3",
|
"version": "1.25.5",
|
||||||
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
|
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@mediabunny/mp3-encoder",
|
"name": "@mediabunny/mp3-encoder",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.25.3",
|
"version": "1.25.5",
|
||||||
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
|
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
|
||||||
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||||
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
|
||||||
|
|||||||
+64
-3
@@ -590,9 +590,7 @@ export const extractNalUnitTypeForHevc = (data: Uint8Array) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** Builds a HevcDecoderConfigurationRecord from an HEVC packet in Annex B format. */
|
/** Builds a HevcDecoderConfigurationRecord from an HEVC packet in Annex B format. */
|
||||||
export const extractHevcDecoderConfigurationRecord = (
|
export const extractHevcDecoderConfigurationRecord = (packetData: Uint8Array) => {
|
||||||
packetData: Uint8Array,
|
|
||||||
) => {
|
|
||||||
try {
|
try {
|
||||||
const nalUnits = findNalUnitsInAnnexB(packetData);
|
const nalUnits = findNalUnitsInAnnexB(packetData);
|
||||||
|
|
||||||
@@ -1456,6 +1454,69 @@ export const extractAv1CodecInfoFromPacket = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Frame size
|
||||||
|
const frameWidthBitsMinus1 = bitstream.readBits(4);
|
||||||
|
const frameHeightBitsMinus1 = bitstream.readBits(4);
|
||||||
|
const n1 = frameWidthBitsMinus1 + 1;
|
||||||
|
bitstream.skipBits(n1); // max_frame_width_minus_1
|
||||||
|
const n2 = frameHeightBitsMinus1 + 1;
|
||||||
|
bitstream.skipBits(n2); // max_frame_height_minus_1
|
||||||
|
|
||||||
|
// Frame IDs
|
||||||
|
let frameIdNumbersPresentFlag = 0;
|
||||||
|
if (reducedStillPictureHeader) {
|
||||||
|
frameIdNumbersPresentFlag = 0;
|
||||||
|
} else {
|
||||||
|
frameIdNumbersPresentFlag = bitstream.readBits(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frameIdNumbersPresentFlag) {
|
||||||
|
bitstream.skipBits(4); // delta_frame_id_length_minus_2
|
||||||
|
bitstream.skipBits(3); // additional_frame_id_length_minus_1
|
||||||
|
}
|
||||||
|
|
||||||
|
bitstream.skipBits(1); // use_128x128_superblock
|
||||||
|
bitstream.skipBits(1); // enable_filter_intra
|
||||||
|
bitstream.skipBits(1); // enable_intra_edge_filter
|
||||||
|
|
||||||
|
if (!reducedStillPictureHeader) {
|
||||||
|
bitstream.skipBits(1); // enable_interintra_compound
|
||||||
|
bitstream.skipBits(1); // enable_masked_compound
|
||||||
|
bitstream.skipBits(1); // enable_warped_motion
|
||||||
|
bitstream.skipBits(1); // enable_dual_filter
|
||||||
|
const enableOrderHint = bitstream.readBits(1);
|
||||||
|
|
||||||
|
if (enableOrderHint) {
|
||||||
|
bitstream.skipBits(1); // enable_jnt_comp
|
||||||
|
bitstream.skipBits(1); // enable_ref_frame_mvs
|
||||||
|
}
|
||||||
|
|
||||||
|
const seqChooseScreenContentTools = bitstream.readBits(1);
|
||||||
|
let seqForceScreenContentTools = 0;
|
||||||
|
|
||||||
|
if (seqChooseScreenContentTools) {
|
||||||
|
seqForceScreenContentTools = 2; // SELECT_SCREEN_CONTENT_TOOLS
|
||||||
|
} else {
|
||||||
|
seqForceScreenContentTools = bitstream.readBits(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seqForceScreenContentTools > 0) {
|
||||||
|
const seqChooseIntegerMv = bitstream.readBits(1);
|
||||||
|
if (!seqChooseIntegerMv) {
|
||||||
|
bitstream.skipBits(1); // seq_force_integer_mv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enableOrderHint) {
|
||||||
|
bitstream.skipBits(3); // order_hint_bits_minus_1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bitstream.skipBits(1); // enable_superres
|
||||||
|
bitstream.skipBits(1); // enable_cdef
|
||||||
|
bitstream.skipBits(1); // enable_restoration
|
||||||
|
|
||||||
|
// color_config()
|
||||||
const highBitdepth = bitstream.readBits(1);
|
const highBitdepth = bitstream.readBits(1);
|
||||||
|
|
||||||
let bitDepth = 8;
|
let bitDepth = 8;
|
||||||
|
|||||||
+15
-5
@@ -82,10 +82,16 @@ export type ConversionOptions = {
|
|||||||
|
|
||||||
/** Options to trim the input file. */
|
/** Options to trim the input file. */
|
||||||
trim?: {
|
trim?: {
|
||||||
/** The time in the input file in seconds at which the output file should start. Must be less than `end`. */
|
/**
|
||||||
start: number;
|
* The time in the input file in seconds at which the output file should start. Must be less than `end`.
|
||||||
/** The time in the input file in seconds at which the output file should end. Must be greater than `start`. */
|
* Defaults to 0 when omitted.
|
||||||
end: number;
|
*/
|
||||||
|
start?: number;
|
||||||
|
/**
|
||||||
|
* The time in the input file in seconds at which the output file should end. Must be greater than `start`.
|
||||||
|
* Defaults to the duration of the input when omitted.
|
||||||
|
*/
|
||||||
|
end?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1509,7 +1515,10 @@ export class Conversion {
|
|||||||
targetSampleRate,
|
targetSampleRate,
|
||||||
startTime: this._startTimestamp,
|
startTime: this._startTimestamp,
|
||||||
endTime: this._endTimestamp,
|
endTime: this._endTimestamp,
|
||||||
onSample: sample => this._registerAudioSample(track, trackOptions, source, sample),
|
onSample: async (sample) => {
|
||||||
|
await this._registerAudioSample(track, trackOptions, source, sample);
|
||||||
|
sample.close();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const sink = new AudioSampleSink(track);
|
const sink = new AudioSampleSink(track);
|
||||||
@@ -1521,6 +1530,7 @@ export class Conversion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await resampler.add(sample);
|
await resampler.add(sample);
|
||||||
|
sample.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
await resampler.finalize();
|
await resampler.finalize();
|
||||||
|
|||||||
+62
-3
@@ -21,6 +21,54 @@ import {
|
|||||||
|
|
||||||
polyfillSymbolDispose();
|
polyfillSymbolDispose();
|
||||||
|
|
||||||
|
type FinalizationRegistryValue = {
|
||||||
|
type: 'video';
|
||||||
|
data: VideoFrame | OffscreenCanvas | Uint8Array;
|
||||||
|
} | {
|
||||||
|
type: 'audio';
|
||||||
|
data: AudioData | Uint8Array;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Let's manually handle logging the garbage collection errors that are typically logged by the browser. This way, they
|
||||||
|
// also kick for audio samples (which is normally not the case), making sure any incorrect code is quickly caught.
|
||||||
|
let lastVideoGcErrorLog = -Infinity;
|
||||||
|
let lastAudioGcErrorLog = -Infinity;
|
||||||
|
let finalizationRegistry: FinalizationRegistry<FinalizationRegistryValue> | null = null;
|
||||||
|
if (typeof FinalizationRegistry !== 'undefined') {
|
||||||
|
finalizationRegistry = new FinalizationRegistry<FinalizationRegistryValue>((value) => {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
if (value.type === 'video') {
|
||||||
|
if (now - lastVideoGcErrorLog >= 1000) {
|
||||||
|
// This error is annoying but oh so important
|
||||||
|
console.error(
|
||||||
|
`A VideoSample was garbage collected without first being closed. For proper resource management,`
|
||||||
|
+ ` make sure to call close() on all your VideoSamples as soon as you're done using them.`,
|
||||||
|
);
|
||||||
|
|
||||||
|
lastVideoGcErrorLog = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof VideoFrame !== 'undefined' && value.data instanceof VideoFrame) {
|
||||||
|
value.data.close(); // Prevent the browser error since we're logging our own
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (now - lastAudioGcErrorLog >= 1000) {
|
||||||
|
console.error(
|
||||||
|
`An AudioSample was garbage collected without first being closed. For proper resource management,`
|
||||||
|
+ ` make sure to call close() on all your AudioSamples as soon as you're done using them.`,
|
||||||
|
);
|
||||||
|
|
||||||
|
lastAudioGcErrorLog = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof AudioData !== 'undefined' && value.data instanceof AudioData) {
|
||||||
|
value.data.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metadata used for VideoSample initialization.
|
* Metadata used for VideoSample initialization.
|
||||||
* @group Samples
|
* @group Samples
|
||||||
@@ -133,7 +181,11 @@ export class VideoSample implements Disposable {
|
|||||||
data: VideoFrame | CanvasImageSource | AllowSharedBufferSource,
|
data: VideoFrame | CanvasImageSource | AllowSharedBufferSource,
|
||||||
init?: VideoSampleInit,
|
init?: VideoSampleInit,
|
||||||
) {
|
) {
|
||||||
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
|
if (
|
||||||
|
data instanceof ArrayBuffer
|
||||||
|
|| (typeof SharedArrayBuffer !== 'undefined' && data instanceof SharedArrayBuffer)
|
||||||
|
|| ArrayBuffer.isView(data)
|
||||||
|
) {
|
||||||
if (!init || typeof init !== 'object') {
|
if (!init || typeof init !== 'object') {
|
||||||
throw new TypeError('init must be an object.');
|
throw new TypeError('init must be an object.');
|
||||||
}
|
}
|
||||||
@@ -265,6 +317,8 @@ export class VideoSample implements Disposable {
|
|||||||
} else {
|
} else {
|
||||||
throw new TypeError('Invalid data type: Must be a BufferSource or CanvasImageSource.');
|
throw new TypeError('Invalid data type: Must be a BufferSource or CanvasImageSource.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finalizationRegistry?.register(this, { type: 'video', data: this._data }, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Clones this video sample. */
|
/** Clones this video sample. */
|
||||||
@@ -313,6 +367,8 @@ export class VideoSample implements Disposable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finalizationRegistry?.unregister(this);
|
||||||
|
|
||||||
if (isVideoFrame(this._data)) {
|
if (isVideoFrame(this._data)) {
|
||||||
this._data.close();
|
this._data.close();
|
||||||
} else {
|
} else {
|
||||||
@@ -955,6 +1011,8 @@ export class AudioSample implements Disposable {
|
|||||||
|
|
||||||
this._data = dataBuffer;
|
this._data = dataBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finalizationRegistry?.register(this, { type: 'audio', data: this._data }, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the number of bytes required to hold the audio sample's data as specified by the given options. */
|
/** Returns the number of bytes required to hold the audio sample's data as specified by the given options. */
|
||||||
@@ -1116,9 +1174,8 @@ export class AudioSample implements Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Branch for Uint8Array data (non-AudioData)
|
|
||||||
const uint8Data = this._data;
|
const uint8Data = this._data;
|
||||||
const srcView = new DataView(uint8Data.buffer, uint8Data.byteOffset, uint8Data.byteLength);
|
const srcView = toDataView(uint8Data);
|
||||||
|
|
||||||
const srcFormat = this.format;
|
const srcFormat = this.format;
|
||||||
const readFn = getReadFunction(srcFormat);
|
const readFn = getReadFunction(srcFormat);
|
||||||
@@ -1188,6 +1245,8 @@ export class AudioSample implements Disposable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finalizationRegistry?.unregister(this);
|
||||||
|
|
||||||
if (isAudioData(this._data)) {
|
if (isAudioData(this._data)) {
|
||||||
this._data.close();
|
this._data.close();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user