mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-27 10:53:50 +02:00
Fix EBML parsers for large integers, make small API and docblock adjustments
This commit is contained in:
+8
-8
@@ -15,7 +15,7 @@
|
||||
const file = fileInput.files[0];
|
||||
|
||||
const source = new Metamuxer.BlobSource(file);
|
||||
const target = new Metamuxer.StreamTarget(new WritableStream({
|
||||
const target = new Metamuxer.BufferTarget() ?? new Metamuxer.StreamTarget(new WritableStream({
|
||||
write: console.log
|
||||
}), {
|
||||
chunked: true,
|
||||
@@ -41,8 +41,8 @@
|
||||
target
|
||||
}),
|
||||
audio: {
|
||||
//discard: true
|
||||
forceReencode: true,
|
||||
discard: true
|
||||
//forceReencode: true,
|
||||
},
|
||||
/*
|
||||
video: {
|
||||
@@ -73,16 +73,16 @@
|
||||
//rotate: 90
|
||||
//width: 720 ?? 2160,
|
||||
//height: 1280 ?? 3840,
|
||||
fit: 'contain',
|
||||
rotate: 90,
|
||||
width: 512,
|
||||
height: 512,
|
||||
//fit: 'contain',
|
||||
//rotate: 90,
|
||||
//width: 512,
|
||||
//height: 512,
|
||||
//width: 200,
|
||||
//height: 100,
|
||||
},
|
||||
trim: {
|
||||
start: 0,
|
||||
//end: 60
|
||||
end: 30
|
||||
},
|
||||
computeProgress: true
|
||||
});
|
||||
|
||||
+1
-1
@@ -50,7 +50,7 @@ if (!(await videoTrack?.canDecode())) {
|
||||
videoTrack = null;
|
||||
}
|
||||
if (!(await audioTrack?.canDecode())) {
|
||||
audioTrack = null;
|
||||
audioTrack = null;
|
||||
}
|
||||
|
||||
const canvas = document.querySelector('canvas');
|
||||
|
||||
+2
-2
@@ -1303,7 +1303,7 @@ export const validateVideoChunkMetadata = (metadata: EncodedVideoChunkMetadata |
|
||||
if (!VALID_VIDEO_CODEC_STRING_PREFIXES.some(prefix => metadata.decoderConfig!.codec.startsWith(prefix))) {
|
||||
throw new TypeError(
|
||||
'Video chunk metadata decoder configuration codec string must be a valid video codec string as specified in'
|
||||
+ ' the WebCodecs codec registry.',
|
||||
+ ' the WebCodecs Codec Registry.',
|
||||
);
|
||||
}
|
||||
if (!Number.isInteger(metadata.decoderConfig.codedWidth) || metadata.decoderConfig.codedWidth! <= 0) {
|
||||
@@ -1444,7 +1444,7 @@ export const validateAudioChunkMetadata = (metadata: EncodedAudioChunkMetadata |
|
||||
if (!VALID_AUDIO_CODEC_STRING_PREFIXES.some(prefix => metadata.decoderConfig!.codec.startsWith(prefix))) {
|
||||
throw new TypeError(
|
||||
'Audio chunk metadata decoder configuration codec string must be a valid audio codec string as specified in'
|
||||
+ ' the WebCodecs codec registry.',
|
||||
+ ' the WebCodecs Codec Registry.',
|
||||
);
|
||||
}
|
||||
if (!Number.isInteger(metadata.decoderConfig.sampleRate) || metadata.decoderConfig.sampleRate <= 0) {
|
||||
|
||||
+12
-6
@@ -52,13 +52,13 @@ export type ConversionOptions = {
|
||||
/** The desired bitrate of the output video. */
|
||||
bitrate?: VideoEncodingConfig['bitrate'];
|
||||
/**
|
||||
* The desired width of the output video, defaulting to the video's natural display width. If height is not set,
|
||||
* it will be deduced automatically based on aspect ratio.
|
||||
* The desired width of the output video in pixels, defaulting to the video's natural display width. If height
|
||||
* is not set, it will be deduced automatically based on aspect ratio.
|
||||
*/
|
||||
width?: number;
|
||||
/**
|
||||
* The desired height of the output video, defaulting to the video's natural display height. If width is not
|
||||
* set, it will be deduced automatically based on aspect ratio.
|
||||
* The desired height of the output video in pixels, defaulting to the video's natural display height. If width
|
||||
* is not set, it will be deduced automatically based on aspect ratio.
|
||||
*/
|
||||
height?: number;
|
||||
/**
|
||||
@@ -492,8 +492,11 @@ export class Conversion {
|
||||
const sink = new EncodedPacketSink(track);
|
||||
const decoderConfig = await track.getDecoderConfig();
|
||||
const meta: EncodedVideoChunkMetadata = { decoderConfig: decoderConfig ?? undefined };
|
||||
const endPacket = Number.isFinite(this._endTimestamp)
|
||||
? await sink.getPacket(this._endTimestamp, { metadataOnly: true }) ?? undefined
|
||||
: undefined;
|
||||
|
||||
for await (const packet of sink.packets(undefined, this._endTimestamp)) {
|
||||
for await (const packet of sink.packets(undefined, endPacket)) {
|
||||
if (this._synchronizer.shouldWait(track.id, packet.timestamp)) {
|
||||
await this._synchronizer.wait(packet.timestamp);
|
||||
}
|
||||
@@ -658,8 +661,11 @@ export class Conversion {
|
||||
const sink = new EncodedPacketSink(track);
|
||||
const decoderConfig = await track.getDecoderConfig();
|
||||
const meta: EncodedAudioChunkMetadata = { decoderConfig: decoderConfig ?? undefined };
|
||||
const endPacket = Number.isFinite(this._endTimestamp)
|
||||
? await sink.getPacket(this._endTimestamp, { metadataOnly: true }) ?? undefined
|
||||
: undefined;
|
||||
|
||||
for await (const packet of sink.packets(undefined, this._endTimestamp)) {
|
||||
for await (const packet of sink.packets(undefined, endPacket)) {
|
||||
if (this._synchronizer.shouldWait(track.id, packet.timestamp)) {
|
||||
await this._synchronizer.wait(packet.timestamp);
|
||||
}
|
||||
|
||||
+5
-5
@@ -158,12 +158,12 @@ export class InputVideoTrack extends InputTrack {
|
||||
return this._backing.getCodec();
|
||||
}
|
||||
|
||||
/** The width of the track's coded samples, before any transformations or rotations. */
|
||||
/** The width in pixels of the track's coded samples, before any transformations or rotations. */
|
||||
get codedWidth() {
|
||||
return this._backing.getCodedWidth();
|
||||
}
|
||||
|
||||
/** The height of the track's coded samples, before any transformations or rotations. */
|
||||
/** The height in pixels of the track's coded samples, before any transformations or rotations. */
|
||||
get codedHeight() {
|
||||
return this._backing.getCodedHeight();
|
||||
}
|
||||
@@ -173,13 +173,13 @@ export class InputVideoTrack extends InputTrack {
|
||||
return this._backing.getRotation();
|
||||
}
|
||||
|
||||
/** The width of the track's frames after rotation. */
|
||||
/** The width in pixels of the track's frames after rotation. */
|
||||
get displayWidth() {
|
||||
const rotation = this._backing.getRotation();
|
||||
return rotation % 180 === 0 ? this._backing.getCodedWidth() : this._backing.getCodedHeight();
|
||||
}
|
||||
|
||||
/** The height of the track's frames after rotation. */
|
||||
/** The height in pixels of the track's frames after rotation. */
|
||||
get displayHeight() {
|
||||
const rotation = this._backing.getRotation();
|
||||
return rotation % 180 === 0 ? this._backing.getCodedHeight() : this._backing.getCodedWidth();
|
||||
@@ -190,7 +190,7 @@ export class InputVideoTrack extends InputTrack {
|
||||
return this._backing.getColorSpace();
|
||||
}
|
||||
|
||||
/** Returns true iff the track's samples use a high dynamic range (HDR). */
|
||||
/** If this method returns true, the track's samples use a high dynamic range (HDR). */
|
||||
async hasHighDynamicRange() {
|
||||
const colorSpace = await this._backing.getColorSpace();
|
||||
|
||||
|
||||
@@ -393,15 +393,14 @@ export class EBMLReader {
|
||||
const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 1);
|
||||
const firstByte = view.getUint8(offset);
|
||||
|
||||
// Find the position of the first set bit, which determines the width
|
||||
// Find the position of VINT_MARKER, which determines the width
|
||||
let width = 1;
|
||||
let mask = 0x80;
|
||||
let mask = 1 << 7;
|
||||
while ((firstByte & mask) === 0 && width < MAX_VAR_INT_SIZE) {
|
||||
width++;
|
||||
mask >>= 1;
|
||||
}
|
||||
|
||||
// Read all bytes
|
||||
const { view: fullView, offset: fullOffset } = this.reader.getViewAndOffset(this.pos, this.pos + width);
|
||||
|
||||
// First byte's value needs the marker bit cleared
|
||||
@@ -409,7 +408,8 @@ export class EBMLReader {
|
||||
|
||||
// Read remaining bytes
|
||||
for (let i = 1; i < width; i++) {
|
||||
value = (value << 8) | fullView.getUint8(fullOffset + i);
|
||||
value *= 1 << 8;
|
||||
value += fullView.getUint8(fullOffset + i);
|
||||
}
|
||||
|
||||
this.pos += width;
|
||||
@@ -426,7 +426,8 @@ export class EBMLReader {
|
||||
|
||||
// Read bytes from most significant to least significant
|
||||
for (let i = 0; i < width; i++) {
|
||||
value = (value << 8) | view.getUint8(offset + i);
|
||||
value *= 1 << 8;
|
||||
value += view.getUint8(offset + i);
|
||||
}
|
||||
|
||||
this.pos += width;
|
||||
|
||||
+28
-17
@@ -129,13 +129,24 @@ export class EncodedPacketSink {
|
||||
* method will intelligently preload packets based on the speed of the consumer.
|
||||
*
|
||||
* @param startPacket - (optional) The packet from which iteration should begin. This packet will also be yielded.
|
||||
* @param endTimestamp - The timestamp in seconds at which to stop iteration. This timestamp is exclusive.
|
||||
* @param endTimestamp - (optional) The timestamp at which iteration should end. This packet will _not_ be yielded.
|
||||
*/
|
||||
packets(
|
||||
startPacket?: EncodedPacket,
|
||||
endTimestamp = Infinity,
|
||||
options?: PacketRetrievalOptions,
|
||||
endPacket?: EncodedPacket,
|
||||
options: PacketRetrievalOptions = {},
|
||||
): AsyncGenerator<EncodedPacket, void, unknown> {
|
||||
if (startPacket !== undefined && !(startPacket instanceof EncodedPacket)) {
|
||||
throw new TypeError('startPacket must be an EncodedPacket.');
|
||||
}
|
||||
if (startPacket !== undefined && startPacket.isMetadataOnly && !options?.metadataOnly) {
|
||||
throw new TypeError('startPacket can only be metadata-only if options.metadataOnly is enabled.');
|
||||
}
|
||||
if (endPacket !== undefined && !(endPacket instanceof EncodedPacket)) {
|
||||
throw new TypeError('endPacket must be an EncodedPacket.');
|
||||
}
|
||||
validatePacketRetrievalOptions(options);
|
||||
|
||||
const packetQueue: EncodedPacket[] = [];
|
||||
|
||||
let { promise: queueNotEmpty, resolve: onQueueNotEmpty } = promiseWithResolvers();
|
||||
@@ -157,7 +168,7 @@ export class EncodedPacketSink {
|
||||
let packet = startPacket ?? await this.getFirstPacket(options);
|
||||
|
||||
while (packet && !terminated) {
|
||||
if (packet.timestamp >= endTimestamp) {
|
||||
if (endPacket && packet.sequenceNumber >= endPacket?.sequenceNumber) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -332,25 +343,25 @@ export abstract class BaseMediaSampleSink<
|
||||
|
||||
let currentPacket: EncodedPacket | null = keyPacket;
|
||||
|
||||
let packetsEndTimestamp = Infinity;
|
||||
let endPacket: EncodedPacket | undefined = undefined;
|
||||
if (endTimestamp < Infinity) {
|
||||
// When an end timestamp is set, we cannot simply use that for the packet iterator due to out-of-order
|
||||
// frames (B-frames). Instead, we'll need to keep decoding packets until we get a frame that exceeds
|
||||
// this end time. However, we can still put a bound on it: Since key frames are by definition never
|
||||
// out of order, we can stop at the first key frame after the end timestamp.
|
||||
const endPacket = await packetSink.getPacket(endTimestamp);
|
||||
const endKeyPacket = !endPacket
|
||||
const packet = await packetSink.getPacket(endTimestamp);
|
||||
const keyPacket = !packet
|
||||
? null
|
||||
: endPacket.type === 'key' && endPacket.timestamp === endTimestamp
|
||||
? endPacket
|
||||
: await packetSink.getNextKeyPacket(endPacket);
|
||||
: packet.type === 'key' && packet.timestamp === endTimestamp
|
||||
? packet
|
||||
: await packetSink.getNextKeyPacket(packet);
|
||||
|
||||
if (endKeyPacket) {
|
||||
packetsEndTimestamp = endKeyPacket.timestamp;
|
||||
if (keyPacket) {
|
||||
endPacket = keyPacket;
|
||||
}
|
||||
}
|
||||
|
||||
const packets = packetSink.packets(keyPacket, packetsEndTimestamp);
|
||||
const packets = packetSink.packets(keyPacket, endPacket);
|
||||
await packets.next(); // Skip the start packet as we already have it
|
||||
|
||||
while (currentPacket && !ended) {
|
||||
@@ -847,13 +858,13 @@ export type WrappedCanvas = {
|
||||
*/
|
||||
export type CanvasSinkOptions = {
|
||||
/**
|
||||
* The width of the output canvas, defaulting to the display width of the video track. If height is not set, it
|
||||
* will be deduced automatically based on aspect ratio.
|
||||
* The width of the output canvas in pixels, defaulting to the display width of the video track. If height is not
|
||||
* set, it will be deduced automatically based on aspect ratio.
|
||||
*/
|
||||
width?: number;
|
||||
/**
|
||||
* The height of the output canvas, defaulting to the display height of the video track. If width is not set, it
|
||||
* will be deduced automatically based on aspect ratio.
|
||||
* The height of the output canvas in pixels, defaulting to the display height of the video track. If width is not
|
||||
* set, it will be deduced automatically based on aspect ratio.
|
||||
*/
|
||||
height?: number;
|
||||
/**
|
||||
|
||||
+2
-2
@@ -188,7 +188,7 @@ export type VideoEncodingConfig = {
|
||||
*/
|
||||
keyFrameInterval?: number;
|
||||
/**
|
||||
* The full codec string as specified in the WebCodecs API Codec Registry. This string must match the codec
|
||||
* The full codec string as specified in the WebCodecs Codec Registry. This string must match the codec
|
||||
* specified in `codec`. When not set, a fitting codec string will be constructed automatically by the library.
|
||||
*/
|
||||
fullCodecString?: string;
|
||||
@@ -653,7 +653,7 @@ export type AudioEncodingConfig = {
|
||||
*/
|
||||
bitrate?: number | Quality;
|
||||
/**
|
||||
* The full codec string as specified in the WebCodecs API Codec Registry. This string must match the codec
|
||||
* The full codec string as specified in the WebCodecs Codec Registry. This string must match the codec
|
||||
* specified in `codec`. When not set, a fitting codec string will be constructed automatically by the library.
|
||||
*/
|
||||
fullCodecString?: string;
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ export class Reader {
|
||||
}
|
||||
}
|
||||
|
||||
this.source.onread?.({ start, end });
|
||||
this.source.onread?.(start, end);
|
||||
const bytesPromise = this.source._read(start, end);
|
||||
const loadingSegment: LoadingSegment = { start, end, promise: bytesPromise };
|
||||
this.loadingSegments.push(loadingSegment);
|
||||
|
||||
+2
-2
@@ -348,8 +348,8 @@ export class VideoSample {
|
||||
*
|
||||
* @param dx - The x-coordinate in the destination canvas at which to place the top-left corner of the source image.
|
||||
* @param dy - The y-coordinate in the destination canvas at which to place the top-left corner of the source image.
|
||||
* @param dWidth - The width to draw the image in the destination canvas. This allows scaling of the drawn image.
|
||||
* @param dHeight - The height to draw the image in the destination canvas. This allows scaling of the drawn image.
|
||||
* @param dWidth - The width in pixels with which to draw the image in the destination canvas.
|
||||
* @param dHeight - The height in pixels with which to draw the image in the destination canvas.
|
||||
*/
|
||||
draw(
|
||||
context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
|
||||
|
||||
+1
-6
@@ -19,12 +19,7 @@ export abstract class Source {
|
||||
}
|
||||
|
||||
/** Called each time data is requested from the source. */
|
||||
onread: ((range: {
|
||||
/** The start byte offset (inclusive). */
|
||||
start: number;
|
||||
/** The end byte offset (exclusive). */
|
||||
end: number;
|
||||
}) => unknown) | null = null;
|
||||
onread: ((start: number, end: number) => unknown) | null = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user