mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-09 16:53:47 +02:00
Merge main into release for tag v1.25.3
This commit is contained in:
@@ -433,4 +433,12 @@ await textSource.add('00:00:00.000 --> 00:00:02.000\nHello there!\n\n');
|
|||||||
await textSource.add('00:00:02.500 --> 00:00:04.000\nChunky chunks.\n\n');
|
await textSource.add('00:00:02.500 --> 00:00:04.000\nChunky chunks.\n\n');
|
||||||
```
|
```
|
||||||
|
|
||||||
The chunks have certain constraints: A cue must be fully contained within a chunk and cannot be split across multiple smaller chunks (although a chunk can contain multiple cues). Also, the WebVTT preamble must be added first and all at once.
|
The chunks have certain constraints: A cue must be fully contained within a chunk and cannot be split across multiple smaller chunks (although a chunk can contain multiple cues). Also, the WebVTT preamble must be added first and all at once.
|
||||||
|
|
||||||
|
::: info
|
||||||
|
For QuickTime to display WebVTT subtitles, it typically expects alignment information to be specified:
|
||||||
|
```
|
||||||
|
00:00:00.000 --> 00:00:02.000 align:center
|
||||||
|
This is your last chance.
|
||||||
|
```
|
||||||
|
:::
|
||||||
@@ -21,7 +21,7 @@ const context = canvas.getContext('2d', { alpha: false, desynchronized: true })!
|
|||||||
|
|
||||||
const frameRate = 30;
|
const frameRate = 30;
|
||||||
|
|
||||||
const chunks: Uint8Array[] = [];
|
const chunks: Uint8Array<ArrayBuffer>[] = [];
|
||||||
let recording = false;
|
let recording = false;
|
||||||
let output: Output;
|
let output: Output;
|
||||||
let videoSource: CanvasSource;
|
let videoSource: CanvasSource;
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ const extractMetadata = (resource: File | string) => {
|
|||||||
'Lyrics': tags.lyrics,
|
'Lyrics': tags.lyrics,
|
||||||
'Comment': tags.comment,
|
'Comment': tags.comment,
|
||||||
'Images': tags.images?.map((image) => {
|
'Images': tags.images?.map((image) => {
|
||||||
const blob = new Blob([image.data], { type: image.mimeType });
|
const blob = new Blob([image.data.slice()], { type: image.mimeType });
|
||||||
const element = new Image();
|
const element = new Image();
|
||||||
element.src = URL.createObjectURL(blob);
|
element.src = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
|||||||
Generated
+285
-462
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "mediabunny",
|
"name": "mediabunny",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.25.2",
|
"version": "1.25.3",
|
||||||
"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": [
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
"@eslint/js": "^9.22.0",
|
"@eslint/js": "^9.22.0",
|
||||||
"@fontsource-variable/rubik": "^5.2.6",
|
"@fontsource-variable/rubik": "^5.2.6",
|
||||||
"@fontsource/dm-mono": "^5.2.6",
|
"@fontsource/dm-mono": "^5.2.6",
|
||||||
"@microsoft/api-extractor": "^7.52.1",
|
"@microsoft/api-extractor": "^7.55.1",
|
||||||
"@stylistic/eslint-plugin": "^4.2.0",
|
"@stylistic/eslint-plugin": "^4.2.0",
|
||||||
"@tailwindcss/vite": "^4.1.7",
|
"@tailwindcss/vite": "^4.1.7",
|
||||||
"@types/markdown-it-footnote": "^3.0.4",
|
"@types/markdown-it-footnote": "^3.0.4",
|
||||||
@@ -84,8 +84,8 @@
|
|||||||
"mermaid": "^11.6.0",
|
"mermaid": "^11.6.0",
|
||||||
"tailwindcss": "^4.1.7",
|
"tailwindcss": "^4.1.7",
|
||||||
"tsx": "^4.19.4",
|
"tsx": "^4.19.4",
|
||||||
"typescript": "^5.8.2",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.26.1",
|
"typescript-eslint": "^8.48.0",
|
||||||
"vite": "^6.3.5",
|
"vite": "^6.3.5",
|
||||||
"vitepress": "^1.6.3",
|
"vitepress": "^1.6.3",
|
||||||
"vitepress-plugin-llms": "^1.5.1",
|
"vitepress-plugin-llms": "^1.5.1",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@mediabunny/mp3-encoder",
|
"name": "@mediabunny/mp3-encoder",
|
||||||
"author": "Vanilagy",
|
"author": "Vanilagy",
|
||||||
"version": "1.25.2",
|
"version": "1.25.3",
|
||||||
"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",
|
||||||
|
|||||||
@@ -850,7 +850,7 @@ export const dOps = (trackData: IsobmffAudioTrackData) => {
|
|||||||
let inputSampleRate = trackData.info.sampleRate;
|
let inputSampleRate = trackData.info.sampleRate;
|
||||||
let outputGain = 0;
|
let outputGain = 0;
|
||||||
let channelMappingFamily = 0;
|
let channelMappingFamily = 0;
|
||||||
let channelMappingTable = new Uint8Array(0);
|
let channelMappingTable: Uint8Array<ArrayBufferLike> = new Uint8Array(0);
|
||||||
|
|
||||||
// Read preskip and from codec private data from the encoder
|
// Read preskip and from codec private data from the encoder
|
||||||
// https://www.rfc-editor.org/rfc/rfc7845#section-5
|
// https://www.rfc-editor.org/rfc/rfc7845#section-5
|
||||||
|
|||||||
+7
-7
@@ -157,20 +157,20 @@ export const writeBits = (bytes: Uint8Array, start: number, end: number, value:
|
|||||||
export const toUint8Array = (source: AllowSharedBufferSource): Uint8Array => {
|
export const toUint8Array = (source: AllowSharedBufferSource): Uint8Array => {
|
||||||
if (source.constructor === Uint8Array) { // We want a true Uint8Array, not something that extends it like Buffer
|
if (source.constructor === Uint8Array) { // We want a true Uint8Array, not something that extends it like Buffer
|
||||||
return source;
|
return source;
|
||||||
} else if (source instanceof ArrayBuffer) {
|
} else if (ArrayBuffer.isView(source)) {
|
||||||
return new Uint8Array(source);
|
|
||||||
} else {
|
|
||||||
return new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
|
return new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
|
||||||
|
} else {
|
||||||
|
return new Uint8Array(source);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toDataView = (source: AllowSharedBufferSource) => {
|
export const toDataView = (source: AllowSharedBufferSource): DataView => {
|
||||||
if (source.constructor === DataView) {
|
if (source.constructor === DataView) {
|
||||||
return source;
|
return source;
|
||||||
} else if (source instanceof ArrayBuffer) {
|
} else if (ArrayBuffer.isView(source)) {
|
||||||
return new DataView(source);
|
|
||||||
} else {
|
|
||||||
return new DataView(source.buffer, source.byteOffset, source.byteLength);
|
return new DataView(source.buffer, source.byteOffset, source.byteLength);
|
||||||
|
} else {
|
||||||
|
return new DataView(source);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -1250,7 +1250,9 @@ export class AudioSample implements Disposable {
|
|||||||
numberOfFrames: this.numberOfFrames,
|
numberOfFrames: this.numberOfFrames,
|
||||||
numberOfChannels: this.numberOfChannels,
|
numberOfChannels: this.numberOfChannels,
|
||||||
timestamp: this.microsecondTimestamp,
|
timestamp: this.microsecondTimestamp,
|
||||||
data: this._data,
|
data: this._data.buffer instanceof ArrayBuffer
|
||||||
|
? this._data.buffer
|
||||||
|
: this._data.slice(), // In the case of SharedArrayBuffer, convert to ArrayBuffer
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-39
@@ -102,10 +102,17 @@ export class BufferSource extends Source {
|
|||||||
/** @internal */
|
/** @internal */
|
||||||
_onreadCalled = false;
|
_onreadCalled = false;
|
||||||
|
|
||||||
/** Creates a new {@link BufferSource} backed the specified `ArrayBuffer` or `ArrayBufferView`. */
|
/**
|
||||||
constructor(buffer: ArrayBuffer | ArrayBufferView) {
|
* Creates a new {@link BufferSource} backed by the specified `ArrayBuffer`, `SharedArrayBuffer`,
|
||||||
if (!(buffer instanceof ArrayBuffer) && !ArrayBuffer.isView(buffer)) {
|
* or `ArrayBufferView`.
|
||||||
throw new TypeError('buffer must be an ArrayBuffer or ArrayBufferView.');
|
*/
|
||||||
|
constructor(buffer: AllowSharedBufferSource) {
|
||||||
|
if (
|
||||||
|
!(buffer instanceof ArrayBuffer)
|
||||||
|
&& !(typeof SharedArrayBuffer !== 'undefined' && buffer instanceof SharedArrayBuffer)
|
||||||
|
&& !ArrayBuffer.isView(buffer)
|
||||||
|
) {
|
||||||
|
throw new TypeError('buffer must be an ArrayBuffer, SharedArrayBuffer, or ArrayBufferView.');
|
||||||
}
|
}
|
||||||
|
|
||||||
super();
|
super();
|
||||||
@@ -234,12 +241,7 @@ export class BlobSource extends Source {
|
|||||||
const { done, value } = await reader.read();
|
const { done, value } = await reader.read();
|
||||||
if (done) {
|
if (done) {
|
||||||
this._orchestrator.forgetWorker(worker);
|
this._orchestrator.forgetWorker(worker);
|
||||||
|
throw new Error('Blob reader stopped unexpectedly before all requested data was read.');
|
||||||
if (worker.currentPos < worker.targetPos) { // I think this `if` should always hit?
|
|
||||||
throw new Error('Blob reader stopped unexpectedly before all requested data was read.');
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (worker.aborted) {
|
if (worker.aborted) {
|
||||||
@@ -441,7 +443,7 @@ export class UrlSource extends Source {
|
|||||||
let fileSize: number;
|
let fileSize: number;
|
||||||
|
|
||||||
if (response.status === 206) {
|
if (response.status === 206) {
|
||||||
fileSize = this._getPartialLengthFromRangeResponse(response);
|
fileSize = this._getTotalLengthFromRangeResponse(response);
|
||||||
worker = this._orchestrator.createWorker(0, Math.min(fileSize, URL_SOURCE_MIN_LOAD_AMOUNT));
|
worker = this._orchestrator.createWorker(0, Math.min(fileSize, URL_SOURCE_MIN_LOAD_AMOUNT));
|
||||||
} else {
|
} else {
|
||||||
// Server probably returned a 200.
|
// Server probably returned a 200.
|
||||||
@@ -515,14 +517,6 @@ export class UrlSource extends Source {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const length = this._getPartialLengthFromRangeResponse(response);
|
|
||||||
const required = worker.targetPos - worker.currentPos;
|
|
||||||
if (length < required) {
|
|
||||||
throw new Error(
|
|
||||||
`HTTP response unexpectedly too short: Needed at least ${required} bytes, got only ${length}.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.body) {
|
if (!response.body) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Missing HTTP response body stream. The used fetch function must provide the response body as a'
|
'Missing HTTP response body stream. The used fetch function must provide the response body as a'
|
||||||
@@ -568,16 +562,17 @@ export class UrlSource extends Source {
|
|||||||
const { done, value } = readResult;
|
const { done, value } = readResult;
|
||||||
|
|
||||||
if (done) {
|
if (done) {
|
||||||
this._orchestrator.forgetWorker(worker);
|
if (worker.currentPos >= worker.targetPos) {
|
||||||
|
// All data was delivered, we're good
|
||||||
if (worker.currentPos < worker.targetPos) {
|
this._orchestrator.forgetWorker(worker);
|
||||||
throw new Error(
|
worker.running = false;
|
||||||
'Response stream reader stopped unexpectedly before all requested data was read.',
|
return;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.running = false;
|
// The response stopped early, before the target. This can happen if server decides to cap range
|
||||||
return;
|
// requests arbitrarily, even if the request had an uncapped end. In this case, let's fetch the rest
|
||||||
|
// of the data using a new request.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onread?.(worker.currentPos, worker.currentPos + value.length);
|
this.onread?.(worker.currentPos, worker.currentPos + value.length);
|
||||||
@@ -597,25 +592,23 @@ export class UrlSource extends Source {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
private _getPartialLengthFromRangeResponse(response: Response) {
|
private _getTotalLengthFromRangeResponse(response: Response) {
|
||||||
const contentRange = response.headers.get('Content-Range');
|
const contentRange = response.headers.get('Content-Range');
|
||||||
if (contentRange) {
|
if (contentRange) {
|
||||||
const match = /\/(\d+)/.exec(contentRange);
|
const match = /\/(\d+)/.exec(contentRange);
|
||||||
if (match) {
|
if (match) {
|
||||||
return Number(match[1]);
|
return Number(match[1]);
|
||||||
} else {
|
|
||||||
throw new Error(`Invalid Content-Range header: ${contentRange}`);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentLength = response.headers.get('Content-Length');
|
||||||
|
if (contentLength) {
|
||||||
|
return Number(contentLength);
|
||||||
} else {
|
} else {
|
||||||
const contentLength = response.headers.get('Content-Length');
|
throw new Error(
|
||||||
if (contentLength) {
|
'Partial HTTP response (status 206) must surface either Content-Range or'
|
||||||
return Number(contentLength);
|
+ ' Content-Length header.',
|
||||||
} else {
|
);
|
||||||
throw new Error(
|
|
||||||
'Partial HTTP response (status 206) must surface either Content-Range or'
|
|
||||||
+ ' Content-Length header.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -62,7 +62,7 @@ export type StreamTargetChunk = {
|
|||||||
/** The operation type. */
|
/** The operation type. */
|
||||||
type: 'write'; // This ensures automatic compatibility with FileSystemWritableFileStream
|
type: 'write'; // This ensures automatic compatibility with FileSystemWritableFileStream
|
||||||
/** The data to write. */
|
/** The data to write. */
|
||||||
data: Uint8Array;
|
data: Uint8Array<ArrayBuffer>;
|
||||||
/** The byte offset in the output file at which to write the data. */
|
/** The byte offset in the output file at which to write the data. */
|
||||||
position: number;
|
position: number;
|
||||||
};
|
};
|
||||||
|
|||||||
+2
-2
@@ -190,7 +190,7 @@ const MAX_CHUNKS_AT_ONCE = 2;
|
|||||||
interface Chunk {
|
interface Chunk {
|
||||||
start: number;
|
start: number;
|
||||||
written: ChunkSection[];
|
written: ChunkSection[];
|
||||||
data: Uint8Array;
|
data: Uint8Array<ArrayBuffer>;
|
||||||
shouldFlush: boolean;
|
shouldFlush: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,7 +281,7 @@ export class StreamTargetWriter extends Writer {
|
|||||||
const chunks: {
|
const chunks: {
|
||||||
start: number;
|
start: number;
|
||||||
size: number;
|
size: number;
|
||||||
data?: Uint8Array;
|
data?: Uint8Array<ArrayBuffer>;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
const sorted = [...this.sections].sort((a, b) => a.start - b.start);
|
const sorted = [...this.sections].sort((a, b) => a.start - b.start);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user