Merge main into release for tag v1.13.3

This commit is contained in:
github-actions[bot]
2025-09-04 09:14:52 +00:00
9 changed files with 76 additions and 34 deletions
+1
View File
@@ -3,6 +3,7 @@
[![](https://img.shields.io/npm/v/mediabunny)](https://www.npmjs.com/package/mediabunny)
[![](https://img.shields.io/bundlephobia/minzip/mediabunny)](https://bundlephobia.com/package/mediabunny)
[![](https://img.shields.io/npm/dm/mediabunny)](https://www.npmjs.com/package/mediabunny)
[![](https://img.shields.io/discord/1390044844285497344?logo=discord&label=Discord)](https://discord.gg/hmpkyYuS4U)
<div align="center">
<img src="./docs/public/mediabunny-logo.svg" width="180" height="180">
+6 -7
View File
@@ -18,15 +18,13 @@
const file = fileInput.files[0];
const source = new Mediabunny.BlobSource(file);
const target = new Mediabunny.NullTarget() ?? new Mediabunny.BufferTarget() ?? new Mediabunny.StreamTarget(new WritableStream({
const target = new Mediabunny.BufferTarget() ?? new Mediabunny.StreamTarget(new WritableStream({
write: console.log
}), {
chunked: true,
chunkSize: 2**20
});
const outputFormat = new Mediabunny.Mp4OutputFormat({
onMoov: console.log
});
const outputFormat = new Mediabunny.Mp4OutputFormat({});
const button = document.createElement('button');
button.textContent = 'Cancel';
@@ -76,7 +74,8 @@
},
*/
video: () => ({
//codec: 'avc',
forceTranscode: true,
codec: 'avc',
//fit: 'contain',
//frameRate: 27.123,
//width: 320,
@@ -98,8 +97,8 @@
//height: 100,
}),
trim: {
//start: 0,
//end: 20
start: 0,
end: 5
},
});
console.log(conversion);
+1 -1
View File
@@ -7,7 +7,7 @@
</div>
</div>
While Mediabunny is proudly human-coded, we want to encourage any and all usage of Mediabunny, even when the vibes are high.
While Mediabunny is proudly human-generated, we want to encourage any and all usage of Mediabunny, even when the vibes are high.
Mediabunny is still new and is unlikely to be in the training data of modern LLMs, but we can still make the AI perform extremely well by just giving it a little more context.
+6 -6
View File
@@ -1,12 +1,12 @@
{
"name": "mediabunny",
"version": "1.13.2",
"version": "1.13.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mediabunny",
"version": "1.13.2",
"version": "1.13.3",
"license": "MPL-2.0",
"workspaces": [
"packages/*"
@@ -7749,9 +7749,9 @@
}
},
"node_modules/mediabunny": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.13.1.tgz",
"integrity": "sha512-Fht/ujfoS0jt8s9W4zDEilnWVsafkBaiIQ5BX0x1tdvM4PClexgYIWDZSf6kC82/c9Bo3ehZPIIfS3aJ9younA==",
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.13.2.tgz",
"integrity": "sha512-quUwxiyA76+iAh2REQAFifHNthgVqzET37ys6v+GC8ljJpOXuP/g/XNsITc+/6r18gGuYQyF/9C4T/CeiTf7gA==",
"license": "MPL-2.0",
"peer": true,
"workspaces": [
@@ -12242,7 +12242,7 @@
},
"packages/mp3-encoder": {
"name": "@mediabunny/mp3-encoder",
"version": "1.13.2",
"version": "1.13.3",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "mediabunny",
"author": "Vanilagy",
"version": "1.13.2",
"version": "1.13.3",
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
"type": "module",
"workspaces": [
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/mp3-encoder",
"author": "Vanilagy",
"version": "1.13.2",
"version": "1.13.3",
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
+42 -8
View File
@@ -46,7 +46,9 @@ import {
Rotation,
} from './misc';
import { Output, TrackType } from './output';
import { Mp4OutputFormat } from './output-format';
import { AudioSample, VideoSample } from './sample';
import { NullTarget } from './target';
/**
* The options for media file conversion.
@@ -582,7 +584,7 @@ export class Conversion {
|| this._startTimestamp > 0
|| firstTimestamp < 0
|| !!trackOptions.frameRate;
const needsRerender = width !== originalWidth
let needsRerender = width !== originalWidth
|| height !== originalHeight
|| (totalRotation !== 0 && !outputSupportsRotation);
@@ -662,6 +664,43 @@ export class Conversion {
const source = new VideoSampleSource(encodingConfig);
videoSource = source;
if (!needsRerender) {
// If we're directly passing decoded samples back to the encoder, sometimes the encoder may error due
// to lack of support of certain video frame formats, like when HDR is at play. To check for this, we
// first try to pass a single frame to the encoder to see how it behaves. If it throws, we then fall
// back to the rerender path.
//
// Creating a new temporary Output is sort of hacky, but due to a lack of an isolated encoder API right
// now, this is the simplest way. Will refactor in the future!
const tempOutput = new Output({
format: new Mp4OutputFormat(), // Supports all video codecs
target: new NullTarget(),
});
const tempSource = new VideoSampleSource(encodingConfig);
tempOutput.addVideoTrack(tempSource);
await tempOutput.start();
const sink = new VideoSampleSink(track);
const firstSample = await sink.getSample(this._startTimestamp);
if (firstSample) {
try {
await tempSource.add(firstSample);
firstSample.close();
await tempOutput.finalize();
} catch (error) {
console.info('Error when probing encoder support. Falling back to rerender path.', error);
needsRerender = true;
void tempOutput.cancel();
}
} else {
await tempOutput.cancel();
}
}
if (needsRerender) {
this._trackPromises.push((async () => {
await this._started;
@@ -1084,13 +1123,8 @@ export class Conversion {
this._maxTimestamps.set(trackId, Math.max(endTimestamp, this._maxTimestamps.get(trackId) ?? -Infinity));
let totalTimestamps = 0;
for (const [, timestamp] of this._maxTimestamps) {
totalTimestamps += timestamp;
}
const averageTimestamp = totalTimestamps / this._totalTrackCount;
const newProgress = clamp(averageTimestamp / this._totalDuration, 0, 1);
const minTimestamp = Math.min(...this._maxTimestamps.values());
const newProgress = clamp(minTimestamp / this._totalDuration, 0, 1);
if (newProgress !== this._lastProgress) {
this._lastProgress = newProgress;
+14 -6
View File
@@ -353,6 +353,7 @@ class VideoEncoderWrapper {
return;
}
const encoderError = new Error();
return this.ensureEncoderPromise = (async () => {
const encoderConfig = buildVideoEncoderConfig({
width: videoSample.codedWidth,
@@ -411,7 +412,7 @@ class VideoEncoderWrapper {
void this.muxer!.addEncodedVideoPacket(this.source._connectedTrack!, packet, meta);
},
error: (error) => {
error.stack = new Error().stack; // Provide a more useful stack trace
error.stack = encoderError.stack; // Provide a more useful stack trace
this.encoderError ??= error;
},
});
@@ -426,7 +427,7 @@ class VideoEncoderWrapper {
}
async flushAndClose(forceClose: boolean) {
this.checkForEncoderError();
if (!forceClose) this.checkForEncoderError();
if (this.customEncoder) {
if (!forceClose) {
@@ -439,10 +440,12 @@ class VideoEncoderWrapper {
await this.encoder.flush();
}
if (this.encoder.state !== 'closed') {
this.encoder.close();
}
}
this.checkForEncoderError();
if (!forceClose) this.checkForEncoderError();
}
getQueueSize() {
@@ -455,6 +458,7 @@ class VideoEncoderWrapper {
checkForEncoderError() {
if (this.encoderError) {
this.encoderError.stack = new Error().stack; // Provide an even more useful stack trace
throw this.encoderError;
}
}
@@ -990,6 +994,7 @@ class AudioEncoderWrapper {
return;
}
const encoderError = new Error();
return this.ensureEncoderPromise = (async () => {
const { numberOfChannels, sampleRate } = audioSample;
@@ -1050,7 +1055,7 @@ class AudioEncoderWrapper {
void this.muxer!.addEncodedAudioPacket(this.source._connectedTrack!, packet, meta);
},
error: (error) => {
error.stack = new Error().stack; // Provide a more useful stack trace
error.stack = encoderError.stack; // Provide a more useful stack trace
this.encoderError ??= error;
},
});
@@ -1158,7 +1163,7 @@ class AudioEncoderWrapper {
}
async flushAndClose(forceClose: boolean) {
this.checkForEncoderError();
if (!forceClose) this.checkForEncoderError();
if (this.customEncoder) {
if (!forceClose) {
@@ -1171,10 +1176,12 @@ class AudioEncoderWrapper {
await this.encoder.flush();
}
if (this.encoder.state !== 'closed') {
this.encoder.close();
}
}
this.checkForEncoderError();
if (!forceClose) this.checkForEncoderError();
}
getQueueSize() {
@@ -1189,6 +1196,7 @@ class AudioEncoderWrapper {
checkForEncoderError() {
if (this.encoderError) {
this.encoderError.stack = new Error().stack; // Provide an even more useful stack trace
throw this.encoderError;
}
}
+2 -2
View File
@@ -114,11 +114,11 @@ export class VideoSample {
* in `init`.
*/
constructor(
data: BufferSource,
data: AllowSharedBufferSource,
init: SetRequired<VideoSampleInit, 'format' | 'codedWidth' | 'codedHeight' | 'timestamp'>
);
constructor(
data: VideoFrame | CanvasImageSource | BufferSource,
data: VideoFrame | CanvasImageSource | AllowSharedBufferSource,
init?: VideoSampleInit,
) {
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {