diff --git a/dev/convert.html b/dev/convert.html
index ea8de68..4d6702e 100644
--- a/dev/convert.html
+++ b/dev/convert.html
@@ -54,7 +54,8 @@
source
}),
output,
- audio: {
+ audio: (_, n) => ({
+ discard: n > 1,
//codec: 'pcm-s16',
//sampleRate: 16000,
//numberOfChannels: 1,
@@ -65,7 +66,7 @@
//sampleRate: 4000
//discard: true
//forceTranscode: true,
- },
+ }),
/*
video: {
discard: true,
@@ -91,6 +92,7 @@
},
*/
video: () => ({
+ discard: true,
//discard: true,
//crop: {
// left: 0,
@@ -124,7 +126,7 @@
//width: 200,
//height: 100,
}),
- tags: {
+ tags: {} ?? {
title: 'Bigggy',
artist: 'Buck Bunny',
images: [{
@@ -142,8 +144,8 @@
}
},
trim: {
- start: 10,
- end: 20
+ //start: 10,
+ //end: 20
},
});
console.log(conversion);
diff --git a/package-lock.json b/package-lock.json
index 79780ca..3e1c1e0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "mediabunny",
- "version": "1.16.0",
+ "version": "1.16.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mediabunny",
- "version": "1.16.0",
+ "version": "1.16.1",
"license": "MPL-2.0",
"workspaces": [
"packages/*"
@@ -7749,9 +7749,9 @@
}
},
"node_modules/mediabunny": {
- "version": "1.15.2",
- "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.15.2.tgz",
- "integrity": "sha512-OvIuSK10wNdz+7Dd4bmeq/IWkYG0bkm33FQb29vmyDztpr47ru8XSxYansa3EHLy6lLEm4rUADUENOcmrnN0jg==",
+ "version": "1.16.0",
+ "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.16.0.tgz",
+ "integrity": "sha512-JXNR8MVo4bMHN4WMLSBjCUMBAxPjEJKtfAqBJI7gJayMOMeLp00K3WE5CH5uLSyhk0yCr26bGNjIEBF0/pQjZA==",
"license": "MPL-2.0",
"peer": true,
"workspaces": [
@@ -12242,7 +12242,7 @@
},
"packages/mp3-encoder": {
"name": "@mediabunny/mp3-encoder",
- "version": "1.16.0",
+ "version": "1.16.1",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
diff --git a/package.json b/package.json
index c5f1bfb..f4d99a4 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "mediabunny",
"author": "Vanilagy",
- "version": "1.16.0",
+ "version": "1.16.1",
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
"type": "module",
"workspaces": [
diff --git a/packages/mp3-encoder/package.json b/packages/mp3-encoder/package.json
index 7151341..96598bf 100644
--- a/packages/mp3-encoder/package.json
+++ b/packages/mp3-encoder/package.json
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/mp3-encoder",
"author": "Vanilagy",
- "version": "1.16.0",
+ "version": "1.16.1",
"description": "MP3 encoder extension for Mediabunny, based on LAME.",
"main": "./dist/bundles/mediabunny-mp3-encoder.mjs",
"module": "./dist/bundles/mediabunny-mp3-encoder.mjs",
diff --git a/src/codec-data.ts b/src/codec-data.ts
index 895569f..9ebf360 100644
--- a/src/codec-data.ts
+++ b/src/codec-data.ts
@@ -12,6 +12,7 @@ import {
assert,
assertNever,
Bitstream,
+ getUint24,
last,
readExpGolomb,
readSignedExpGolomb,
@@ -116,7 +117,7 @@ const findNalUnitsInLengthPrefixed = (packetData: Uint8Array, lengthSize: 1 | 2
} else if (lengthSize === 2) {
nalUnitLength = dataView.getUint16(offset, false);
} else if (lengthSize === 3) {
- nalUnitLength = (dataView.getUint16(offset, false) << 8) + dataView.getUint8(offset + 2);
+ nalUnitLength = getUint24(dataView, offset, false);
} else if (lengthSize === 4) {
nalUnitLength = dataView.getUint32(offset, false);
} else {
diff --git a/src/matroska/matroska-demuxer.ts b/src/matroska/matroska-demuxer.ts
index 89617ce..8277b73 100644
--- a/src/matroska/matroska-demuxer.ts
+++ b/src/matroska/matroska-demuxer.ts
@@ -116,7 +116,7 @@ type Cluster = {
};
type ClusterTrackData = {
- track: InternalTrack | null;
+ track: InternalTrack;
startTimestamp: number;
endTimestamp: number;
firstKeyFrameTimestamp: number | null;
@@ -141,6 +141,7 @@ type ClusterBlock = {
referencedTimestamps: number[];
data: Uint8Array;
lacing: BlockLacing;
+ decoded: boolean;
};
type CuePoint = {
@@ -667,7 +668,7 @@ export class MatroskaDemuxer extends Demuxer {
const nextEntry = trackData.presentationTimestamps[i + 1]!;
currentBlock.duration = nextEntry.timestamp - currentBlock.timestamp;
} else if (currentBlock.duration === 0) {
- if (track?.defaultDuration != null) {
+ if (track.defaultDuration != null) {
if (currentBlock.lacing === BlockLacing.None) {
currentBlock.duration = track.defaultDuration;
} else {
@@ -695,13 +696,11 @@ export class MatroskaDemuxer extends Demuxer {
trackData.startTimestamp = firstBlock.timestamp;
trackData.endTimestamp = lastBlock.timestamp + lastBlock.duration;
- if (track) {
- insertSorted(track.clusters, cluster, x => x.elementStartPos);
+ insertSorted(track.clusters, cluster, x => x.elementStartPos);
- const hasKeyFrame = trackData.firstKeyFrameTimestamp !== null;
- if (hasKeyFrame) {
- insertSorted(track.clustersWithKeyFrame, cluster, x => x.elementStartPos);
- }
+ const hasKeyFrame = trackData.firstKeyFrameTimestamp !== null;
+ if (hasKeyFrame) {
+ insertSorted(track.clustersWithKeyFrame, cluster, x => x.elementStartPos);
}
}
@@ -714,8 +713,13 @@ export class MatroskaDemuxer extends Demuxer {
getTrackDataInCluster(cluster: Cluster, trackNumber: number) {
let trackData = cluster.trackData.get(trackNumber);
if (!trackData) {
+ const track = cluster.segment.tracks.find(x => x.id === trackNumber);
+ if (!track) {
+ return null;
+ }
+
trackData = {
- track: cluster.segment.tracks.find(x => x.id === trackNumber) ?? null,
+ track,
startTimestamp: 0,
endTimestamp: 0,
firstKeyFrameTimestamp: null,
@@ -728,7 +732,7 @@ export class MatroskaDemuxer extends Demuxer {
return trackData;
}
- expandLacedBlocks(blocks: ClusterBlock[], track: InternalTrack | null) {
+ expandLacedBlocks(blocks: ClusterBlock[], track: InternalTrack) {
// https://www.matroska.org/technical/notes.html#block-lacing
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
@@ -737,6 +741,12 @@ export class MatroskaDemuxer extends Demuxer {
continue;
}
+ // Decode the block data if it hasn't been decoded yet (needed for lacing expansion)
+ if (!originalBlock.decoded) {
+ originalBlock.data = this.decodeBlockData(track, originalBlock.data);
+ originalBlock.decoded = true;
+ }
+
const slice = FileSlice.tempFromBytes(originalBlock.data);
const frameSizes: number[] = [];
@@ -819,7 +829,7 @@ export class MatroskaDemuxer extends Demuxer {
const frameSize = frameSizes[i]!;
const frameData = readBytes(slice, frameSize);
- const blockDuration = originalBlock.duration || (frameCount * (track?.defaultDuration ?? 0));
+ const blockDuration = originalBlock.duration || (frameCount * (track.defaultDuration ?? 0));
// Distribute timestamps evenly across the block duration
const frameTimestamp = originalBlock.timestamp + (blockDuration * i / frameCount);
@@ -832,6 +842,7 @@ export class MatroskaDemuxer extends Demuxer {
referencedTimestamps: originalBlock.referencedTimestamps,
data: frameData,
lacing: BlockLacing.None,
+ decoded: true,
});
}
@@ -1331,18 +1342,17 @@ export class MatroskaDemuxer extends Demuxer {
const trackNumber = readVarInt(slice);
if (trackNumber === null) break;
+ const trackData = this.getTrackDataInCluster(this.currentCluster, trackNumber);
+ if (!trackData) break; // Not a track we care about
+
const relativeTimestamp = readI16Be(slice);
const flags = readU8(slice);
const isKeyFrame = !!(flags & 0x80);
const lacing = (flags >> 1) & 0x3 as BlockLacing; // If the block is laced, we'll expand it later
- const trackData = this.getTrackDataInCluster(this.currentCluster, trackNumber);
- let blockData = readBytes(slice, size - (slice.filePos - dataStartPos));
-
- if (trackData.track) {
- blockData = this.decodeBlockData(trackData.track, blockData);
- }
+ const blockData = readBytes(slice, size - (slice.filePos - dataStartPos));
+ const hasDecodingInstructions = trackData.track.decodingInstructions.length > 0;
trackData.blocks.push({
timestamp: relativeTimestamp, // We'll add the cluster's timestamp to this later
@@ -1351,6 +1361,7 @@ export class MatroskaDemuxer extends Demuxer {
referencedTimestamps: [],
data: blockData,
lacing,
+ decoded: !hasDecodingInstructions,
});
}; break;
@@ -1374,17 +1385,16 @@ export class MatroskaDemuxer extends Demuxer {
const trackNumber = readVarInt(slice);
if (trackNumber === null) break;
+ const trackData = this.getTrackDataInCluster(this.currentCluster, trackNumber);
+ if (!trackData) break;
+
const relativeTimestamp = readI16Be(slice);
const flags = readU8(slice);
const lacing = (flags >> 1) & 0x3 as BlockLacing; // If the block is laced, we'll expand it later
- const trackData = this.getTrackDataInCluster(this.currentCluster, trackNumber);
- let blockData = readBytes(slice, size - (slice.filePos - dataStartPos));
-
- if (trackData.track) {
- blockData = this.decodeBlockData(trackData.track, blockData);
- }
+ const blockData = readBytes(slice, size - (slice.filePos - dataStartPos));
+ const hasDecodingInstructions = trackData.track.decodingInstructions.length > 0;
this.currentBlock = {
timestamp: relativeTimestamp, // We'll add the cluster's timestamp to this later
@@ -1393,6 +1403,7 @@ export class MatroskaDemuxer extends Demuxer {
referencedTimestamps: [],
data: blockData,
lacing,
+ decoded: !hasDecodingInstructions,
};
trackData.blocks.push(this.currentBlock);
}; break;
@@ -1600,24 +1611,26 @@ export class MatroskaDemuxer extends Demuxer {
}
decodeBlockData(track: InternalTrack, rawData: Uint8Array) {
+ assert(track.decodingInstructions.length > 0); // This method shouldn't be called otherwise
+
let currentData = rawData;
- // In the vast number of cases there are exactly zero decoding instructions
- for (let i = 0; i < track.decodingInstructions.length; i++) {
- const instruction = track.decodingInstructions[i]!;
+ for (const instruction of track.decodingInstructions) {
assert(instruction.data);
switch (instruction.data.type) {
case 'decompress': {
switch (instruction.data.algorithm) {
case ContentCompAlgo.HeaderStripping: {
- const prefix = instruction.data.settings ?? new Uint8Array(0);
- const newData = new Uint8Array(prefix.length + currentData.length);
+ if (instruction.data.settings && instruction.data.settings.length > 0) {
+ const prefix = instruction.data.settings;
+ const newData = new Uint8Array(prefix.length + currentData.length);
- newData.set(prefix, 0);
- newData.set(currentData, prefix.length);
+ newData.set(prefix, 0);
+ newData.set(currentData, prefix.length);
- currentData = newData;
+ currentData = newData;
+ }
}; break;
default: {
@@ -1959,6 +1972,12 @@ abstract class MatroskaTrackBacking implements InputTrackBacking {
const block = trackData.blocks[blockIndex];
assert(block);
+ // Perform lazy decoding if needed
+ if (!block.decoded) {
+ block.data = this.internalTrack.demuxer.decodeBlockData(this.internalTrack, block.data);
+ block.decoded = true;
+ }
+
const data = options.metadataOnly ? PLACEHOLDER_DATA : block.data;
const timestamp = block.timestamp / this.internalTrack.segment.timestampFactor;
const duration = block.duration / this.internalTrack.segment.timestampFactor;
diff --git a/src/reader.ts b/src/reader.ts
index b0104f0..687e9bc 100644
--- a/src/reader.ts
+++ b/src/reader.ts
@@ -6,7 +6,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
-import { assert, clamp, MaybePromise, toDataView } from './misc';
+import { assert, clamp, getUint24, MaybePromise, toDataView } from './misc';
import { Source } from './source';
export class Reader {
@@ -157,9 +157,10 @@ export const readU16Be = (slice: FileSlice) => {
};
export const readU24Be = (slice: FileSlice) => {
- const high = readU16Be(slice);
- const low = readU8(slice);
- return high * 0x100 + low;
+ const value = getUint24(slice.view, slice.bufferPos, false);
+ slice.bufferPos += 3;
+
+ return value;
};
export const readI16Be = (slice: FileSlice) => {