diff --git a/dev/convert.html b/dev/convert.html
index 658b6b4..d204380 100644
--- a/dev/convert.html
+++ b/dev/convert.html
@@ -104,6 +104,8 @@
},
*/
video: () => ({
+ //width: 640,
+ //rotate: 90,
//forceTranscode: true,
//forceTranscode: true,
//width: 1280,
@@ -189,7 +191,7 @@
},
trim: {
//start: 0,
- end: 10
+ end: 2
},
});
console.log(conversion);
diff --git a/docs/guide/output-formats.md b/docs/guide/output-formats.md
index e4831be..32980a1 100644
--- a/docs/guide/output-formats.md
+++ b/docs/guide/output-formats.md
@@ -303,9 +303,16 @@ const output = new Output({
The following options are available:
```ts
type FlacOutputFormatOptions = {
+ appendOnly?: boolean;
+
onFrame?: (data: Uint8Array, position: number) => unknown;
};
```
+- `appendOnly`\
+ Configures the output to only append new data at the end, useful for live-streaming the file as it's being created. When enabled, the STREAMINFO block will not be finalized with accurate min/max block sizes, frame sizes, or total sample count, so don't use this option when you want to write out a clean file for later use.
+ ::: info
+ This option ensures [append-only writing](#append-only-writing).
+ :::
- `onFrame`\
Will be called for each FLAC frame that is written.
diff --git a/package-lock.json b/package-lock.json
index f003e96..6a990f7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "mediabunny",
- "version": "1.39.2",
+ "version": "1.40.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "mediabunny",
- "version": "1.39.2",
+ "version": "1.40.0",
"license": "MPL-2.0",
"workspaces": [
"packages/*"
@@ -7751,9 +7751,9 @@
}
},
"node_modules/mediabunny": {
- "version": "1.39.1",
- "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.39.1.tgz",
- "integrity": "sha512-gaQKDWtfLhgSN9tRj37lgznHf9MOvey7sbT6GdQA7mWl1kaE5R+P1lfl9CDsgtzongx12IG9X/y2vgZmTHzGDg==",
+ "version": "1.39.2",
+ "resolved": "https://registry.npmjs.org/mediabunny/-/mediabunny-1.39.2.tgz",
+ "integrity": "sha512-VcrisGRt+OI7tTPrziucJoCIPYIS/DEWY37TqzQVLWSUUHiyvsiRizEypQ3FOlhfIZ4ytAG/Mw4zxfetCTyKUg==",
"license": "MPL-2.0",
"peer": true,
"workspaces": [
@@ -12077,7 +12077,7 @@
},
"packages/aac-encoder": {
"name": "@mediabunny/aac-encoder",
- "version": "1.39.2",
+ "version": "1.40.0",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
@@ -12092,7 +12092,7 @@
},
"packages/ac3": {
"name": "@mediabunny/ac3",
- "version": "1.39.2",
+ "version": "1.40.0",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
@@ -12107,7 +12107,7 @@
},
"packages/flac-encoder": {
"name": "@mediabunny/flac-encoder",
- "version": "1.39.2",
+ "version": "1.40.0",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
@@ -12122,7 +12122,7 @@
},
"packages/mp3-encoder": {
"name": "@mediabunny/mp3-encoder",
- "version": "1.39.2",
+ "version": "1.40.0",
"license": "MPL-2.0",
"devDependencies": {
"@types/emscripten": "^1.40.1"
diff --git a/package.json b/package.json
index 4fb193f..267d8b4 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "mediabunny",
"author": "Vanilagy",
- "version": "1.39.2",
+ "version": "1.40.0",
"description": "Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.",
"type": "module",
"workspaces": [
diff --git a/packages/aac-encoder/package.json b/packages/aac-encoder/package.json
index faa9753..c271c15 100644
--- a/packages/aac-encoder/package.json
+++ b/packages/aac-encoder/package.json
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/aac-encoder",
"author": "Vanilagy",
- "version": "1.39.2",
+ "version": "1.40.0",
"description": "AAC encoder extension for Mediabunny, based on FFmpeg.",
"main": "./dist/bundles/mediabunny-aac-encoder.mjs",
"module": "./dist/bundles/mediabunny-aac-encoder.mjs",
diff --git a/packages/ac3/package.json b/packages/ac3/package.json
index bd5b58c..ca70acf 100644
--- a/packages/ac3/package.json
+++ b/packages/ac3/package.json
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/ac3",
"author": "Vanilagy",
- "version": "1.39.2",
+ "version": "1.40.0",
"description": "AC-3 and E-AC-3 (Dolby Digital) decoder and encoder extension for Mediabunny, based on FFmpeg.",
"main": "./dist/bundles/mediabunny-ac3.mjs",
"module": "./dist/bundles/mediabunny-ac3.mjs",
diff --git a/packages/flac-encoder/package.json b/packages/flac-encoder/package.json
index fe3aac3..e51171c 100644
--- a/packages/flac-encoder/package.json
+++ b/packages/flac-encoder/package.json
@@ -1,7 +1,7 @@
{
"name": "@mediabunny/flac-encoder",
"author": "Vanilagy",
- "version": "1.39.2",
+ "version": "1.40.0",
"description": "FLAC encoder extension for Mediabunny, based on libFLAC.",
"main": "./dist/bundles/mediabunny-flac-encoder.mjs",
"module": "./dist/bundles/mediabunny-flac-encoder.mjs",
diff --git a/packages/mp3-encoder/package.json b/packages/mp3-encoder/package.json
index 522f6a3..0848ec3 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.39.2",
+ "version": "1.40.0",
"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/conversion.ts b/src/conversion.ts
index a6dccfe..f93ed10 100644
--- a/src/conversion.ts
+++ b/src/conversion.ts
@@ -1086,6 +1086,8 @@ export class Conversion {
}
if (needsRerender) {
+ outputTrackRotation = 0; // Since the rotation is baked into the output
+
this._trackPromises.push((async () => {
await this._started;
@@ -1101,8 +1103,6 @@ export class Conversion {
const iterator = sink.canvases(this._startTimestamp, this._endTimestamp);
const frameRate = trackOptions.frameRate;
- outputTrackRotation = 0; // Since the rotation is baked into the output
-
let lastCanvas: HTMLCanvasElement | OffscreenCanvas | null = null;
let lastCanvasTimestamp: number | null = null;
let lastCanvasEndTimestamp: number | null = null;
diff --git a/src/flac/flac-demuxer.ts b/src/flac/flac-demuxer.ts
index adffadf..c0c0a20 100644
--- a/src/flac/flac-demuxer.ts
+++ b/src/flac/flac-demuxer.ts
@@ -283,13 +283,38 @@ export class FlacDemuxer extends Demuxer {
// --> 6 bytes
const minimumHeaderLength = 6;
// If we read everything in readFlacFrameHeader, we read 16 bytes
- const maximumHeaderSize = 16;
+ const maximumHeaderLength = 16;
+
+ // The shortest valid FLAC frame per RFC 9639:
+ // 6 bytes header (see minimumHeaderLength above)
+ // 2 bytes subframe (constant subframe with minimum bit depth,
+ // padded to byte boundary)
+ // 2 bytes footer (CRC-16)
+ // --> 10 bytes
+ const minimumFrameLength = 10;
+
+ // The longest valid FLAC frame per RFC 9639:
+ // https://www.rfc-editor.org/rfc/rfc9639.html#name-prediction
+ // https://www.rfc-editor.org/rfc/rfc9639.html#name-frame-structure
+ // maximumBlockSize * numberOfChannels * 4 bytes (max 32 bps verbatim)
+ // + 16 bytes header (see maximumHeaderSize above)
+ // + 2 bytes footer (CRC-16)
+ const maximumFrameLength = this.audioInfo.maximumBlockSize
+ * this.audioInfo.numberOfChannels
+ * 4
+ + maximumHeaderLength
+ + 2;
+
+ // Per RFC 9639, a value of 0 means "unknown" for frame sizes.
+ const effectiveMinFrameSize = this.audioInfo.minimumFrameSize || minimumFrameLength;
+ const effectiveMaxFrameSize = this.audioInfo.maximumFrameSize || maximumFrameLength;
+
const maximumSliceLength
- = this.audioInfo.maximumFrameSize + maximumHeaderSize;
+ = effectiveMaxFrameSize + maximumHeaderLength;
const slice = await this.reader.requestSliceRange(
startPos,
- this.audioInfo.minimumFrameSize,
+ maximumHeaderLength,
maximumSliceLength,
);
@@ -312,7 +337,7 @@ export class FlacDemuxer extends Demuxer {
// The next sync word is expected at earliest when `minimumFrameSize` is reached,
// we can skip over anything before that
- slice.filePos = startPos + this.audioInfo.minimumFrameSize;
+ slice.filePos = startPos + effectiveMinFrameSize;
while (true) {
// Reached end of the file, packet is over
diff --git a/src/flac/flac-muxer.ts b/src/flac/flac-muxer.ts
index 83cdfe9..b52c582 100644
--- a/src/flac/flac-muxer.ts
+++ b/src/flac/flac-muxer.ts
@@ -50,6 +50,10 @@ export class FlacMuxer extends Muxer {
this.writer = output._writer;
this.format = format;
+
+ if (this.format._options.appendOnly) {
+ this.writer.ensureMonotonicity = true;
+ }
}
async start() {
@@ -165,7 +169,9 @@ export class FlacMuxer extends Muxer {
}
writeVorbisCommentAndPictureBlock() {
- this.writer.seek(STREAMINFO_SIZE + FLAC_HEADER.byteLength);
+ if (!this.format._options.appendOnly) {
+ this.writer.seek(STREAMINFO_SIZE + FLAC_HEADER.byteLength);
+ }
if (metadataTagsAreEmpty(this.output._metadataTags)) {
this.metadataWritten = true;
return;
@@ -233,6 +239,30 @@ export class FlacMuxer extends Muxer {
descriptionBitstream.skipBits(103 + 64);
const bitsPerSample = descriptionBitstream.readBits(5) + 1;
this.bitsPerSample = bitsPerSample;
+
+ if (this.format._options.appendOnly) {
+ // Write STREAMINFO immediately since we can't seek back later.
+ this.writeHeader({
+ // https://www.rfc-editor.org/rfc/rfc9639.html#name-streaminfo
+ // Per RFC 9639, min/max block sizes can be looser than
+ // actual values, so we use the full valid range (16–65535).
+ // "The actual max block size MAY be smaller than what's
+ // listed, and the actual min (excluding last block) MAY be
+ // larger. This is because the encoder has to write these
+ // fields before receiving any input audio data and cannot
+ // know beforehand what block sizes it will use."
+ minimumBlockSize: 16,
+ maximumBlockSize: 65535,
+ // https://www.rfc-editor.org/rfc/rfc9639.html#name-streaminfo
+ // "A value of 0 signifies that the value is not known."
+ minimumFrameSize: 0,
+ maximumFrameSize: 0,
+ sampleRate: this.sampleRate,
+ channels: this.channels,
+ bitsPerSample: this.bitsPerSample,
+ totalSamples: 0,
+ });
+ }
}
if (!this.metadataWritten) {
@@ -251,8 +281,10 @@ export class FlacMuxer extends Muxer {
readCodedNumber(slice); // num
const blockSize = readBlockSize(slice, blockSizeOrUncommon);
- this.blockSizes.push(blockSize);
- this.frameSizes.push(packet.data.length);
+ if (!this.format._options.appendOnly) {
+ this.blockSizes.push(blockSize);
+ this.frameSizes.push(packet.data.length);
+ }
const startPos = this.writer.getPos();
this.writer.write(packet.data);
@@ -274,43 +306,45 @@ export class FlacMuxer extends Muxer {
async finalize(): Promise {
const release = await this.mutex.acquire();
- let minimumBlockSize = Infinity;
- let maximumBlockSize = 0;
- let minimumFrameSize = Infinity;
- let maximumFrameSize = 0;
- let totalSamples = 0;
- for (let i = 0; i < this.blockSizes.length; i++) {
- minimumFrameSize = Math.min(minimumFrameSize, this.frameSizes[i]!);
- maximumFrameSize = Math.max(maximumFrameSize, this.frameSizes[i]!);
- maximumBlockSize = Math.max(maximumBlockSize, this.blockSizes[i]!);
- totalSamples += this.blockSizes[i]!;
+ if (!this.format._options.appendOnly) {
+ let minimumBlockSize = Infinity;
+ let maximumBlockSize = 0;
+ let minimumFrameSize = Infinity;
+ let maximumFrameSize = 0;
+ let totalSamples = 0;
+ for (let i = 0; i < this.blockSizes.length; i++) {
+ minimumFrameSize = Math.min(minimumFrameSize, this.frameSizes[i]!);
+ maximumFrameSize = Math.max(maximumFrameSize, this.frameSizes[i]!);
+ maximumBlockSize = Math.max(maximumBlockSize, this.blockSizes[i]!);
+ totalSamples += this.blockSizes[i]!;
- // Excluding the last frame from block size calculation
- // https://www.rfc-editor.org/rfc/rfc9639.html#name-streaminfo
- // "The minimum block size (in samples) used in the stream, excluding the last block."
- const isLastFrame = i === this.blockSizes.length - 1;
- if (isLastFrame) {
- continue;
+ // Excluding the last frame from block size calculation
+ // https://www.rfc-editor.org/rfc/rfc9639.html#name-streaminfo
+ // "The minimum block size (in samples) used in the stream, excluding the last block."
+ const isLastFrame = i === this.blockSizes.length - 1;
+ if (isLastFrame) {
+ continue;
+ }
+ minimumBlockSize = Math.min(minimumBlockSize, this.blockSizes[i]!);
}
- minimumBlockSize = Math.min(minimumBlockSize, this.blockSizes[i]!);
+
+ assert(this.sampleRate !== null);
+ assert(this.channels !== null);
+ assert(this.bitsPerSample !== null);
+
+ this.writer.seek(4);
+ this.writeHeader({
+ minimumBlockSize,
+ maximumBlockSize,
+ minimumFrameSize,
+ maximumFrameSize,
+ sampleRate: this.sampleRate,
+ channels: this.channels,
+ bitsPerSample: this.bitsPerSample,
+ totalSamples,
+ });
}
- assert(this.sampleRate !== null);
- assert(this.channels !== null);
- assert(this.bitsPerSample !== null);
-
- this.writer.seek(4);
- this.writeHeader({
- minimumBlockSize,
- maximumBlockSize,
- minimumFrameSize,
- maximumFrameSize,
- sampleRate: this.sampleRate,
- channels: this.channels,
- bitsPerSample: this.bitsPerSample,
- totalSamples,
- });
-
release();
}
}
diff --git a/src/isobmff/isobmff-demuxer.ts b/src/isobmff/isobmff-demuxer.ts
index e247193..02be3fb 100644
--- a/src/isobmff/isobmff-demuxer.ts
+++ b/src/isobmff/isobmff-demuxer.ts
@@ -2947,15 +2947,23 @@ class IsobmffVideoTrackBacking extends IsobmffTrackBacking implements InputVideo
this.internalTrack.info.av1CodecInfo = firstPacket && extractAv1CodecInfoFromPacket(firstPacket.data);
}
- return {
+ const config: VideoDecoderConfig = {
codec: extractVideoCodecString(this.internalTrack.info),
codedWidth: this.internalTrack.info.width,
codedHeight: this.internalTrack.info.height,
- displayAspectWidth: this.internalTrack.info.squarePixelWidth,
- displayAspectHeight: this.internalTrack.info.squarePixelHeight,
description: this.internalTrack.info.codecDescription ?? undefined,
colorSpace: this.internalTrack.info.colorSpace ?? undefined,
};
+
+ if (
+ this.internalTrack.info.width !== this.internalTrack.info.squarePixelWidth
+ || this.internalTrack.info.height !== this.internalTrack.info.squarePixelHeight
+ ) {
+ config.displayAspectWidth = this.internalTrack.info.squarePixelWidth;
+ config.displayAspectHeight = this.internalTrack.info.squarePixelHeight;
+ }
+
+ return config;
})();
}
}
@@ -3169,22 +3177,16 @@ const offsetFragmentTrackDataByTimestamp = (trackData: FragmentTrackData, timest
/** Extracts the rotation component from a transformation matrix, in degrees. */
const extractRotationFromMatrix = (matrix: TransformationMatrix) => {
- const [m11, , , m21] = matrix;
+ const [a, b] = matrix; // (1, 0) projects onto (a, b), so that's all we need
- const scaleX = Math.hypot(m11, m21);
+ const radians = Math.atan2(b, a);
- const cosTheta = m11 / scaleX;
- const sinTheta = m21 / scaleX;
-
- // Invert the rotation because matrices are post-multiplied in ISOBMFF
- const result = -Math.atan2(sinTheta, cosTheta) * (180 / Math.PI);
-
- if (!Number.isFinite(result)) {
+ if (!Number.isFinite(radians)) {
// Can happen if the entire matrix is 0, for example
return 0;
}
- return result;
+ return radians * (180 / Math.PI);
};
const sampleTableIsEmpty = (sampleTable: SampleTable) => {
diff --git a/src/matroska/matroska-demuxer.ts b/src/matroska/matroska-demuxer.ts
index 28106c9..37a601d 100644
--- a/src/matroska/matroska-demuxer.ts
+++ b/src/matroska/matroska-demuxer.ts
@@ -2430,7 +2430,7 @@ class MatroskaVideoTrackBacking extends MatroskaTrackBacking implements InputVid
firstPacket = await this.getFirstPacket({});
}
- return {
+ const config: VideoDecoderConfig = {
codec: extractVideoCodecString({
width: this.internalTrack.info.width,
height: this.internalTrack.info.height,
@@ -2453,11 +2453,19 @@ class MatroskaVideoTrackBacking extends MatroskaTrackBacking implements InputVid
}),
codedWidth: this.internalTrack.info.width,
codedHeight: this.internalTrack.info.height,
- displayAspectWidth: this.internalTrack.info.squarePixelWidth,
- displayAspectHeight: this.internalTrack.info.squarePixelHeight,
description: this.internalTrack.info.codecDescription ?? undefined,
colorSpace: this.internalTrack.info.colorSpace ?? undefined,
};
+
+ if (
+ this.internalTrack.info.width !== this.internalTrack.info.squarePixelWidth
+ || this.internalTrack.info.height !== this.internalTrack.info.squarePixelHeight
+ ) {
+ config.displayAspectWidth = this.internalTrack.info.squarePixelWidth;
+ config.displayAspectHeight = this.internalTrack.info.squarePixelHeight;
+ }
+
+ return config;
})();
}
}
diff --git a/src/mpeg-ts/mpeg-ts-demuxer.ts b/src/mpeg-ts/mpeg-ts-demuxer.ts
index be61c08..fea0deb 100644
--- a/src/mpeg-ts/mpeg-ts-demuxer.ts
+++ b/src/mpeg-ts/mpeg-ts-demuxer.ts
@@ -236,12 +236,13 @@ export class MpegTsDemuxer extends Demuxer {
while (8 * (sectionLength + BYTES_BEFORE_SECTION_LENGTH) - bitstream.pos > BITS_IN_CRC_32) {
const programNumber = bitstream.readBits(16);
bitstream.skipBits(3); // Reserved
+ const id = bitstream.readBits(13);
if (programNumber !== 0) {
if (programMapPid !== null) {
throw new Error('Only files with a single program are supported.');
} else {
- programMapPid = bitstream.readBits(13);
+ programMapPid = id;
}
}
}
@@ -1031,8 +1032,6 @@ export abstract class MpegTsTrackBacking implements InputTrackBacking {
return null;
}
- // result.packet.randomAccessIndicator = 1; // Assume the first packet is always a key packet
-
const packet = this.createEncodedPacket(result.packet, result.duration, options);
this.packetBuffers.set(packet, buffer);
this.packetSectionStarts.set(packet, result.packet.sectionStartPos);
@@ -1514,10 +1513,16 @@ class MpegTsVideoTrackBacking extends MpegTsTrackBacking implements InputVideoTr
}),
codedWidth: this.elementaryStream.info.width,
codedHeight: this.elementaryStream.info.height,
- displayAspectWidth: this.elementaryStream.info.squarePixelWidth,
- displayAspectHeight: this.elementaryStream.info.squarePixelHeight,
colorSpace: this.elementaryStream.info.colorSpace,
};
+
+ if (
+ this.elementaryStream.info.width !== this.elementaryStream.info.squarePixelWidth
+ || this.elementaryStream.info.height !== this.elementaryStream.info.squarePixelHeight
+ ) {
+ this.decoderConfig.displayAspectWidth = this.elementaryStream.info.squarePixelWidth;
+ this.decoderConfig.displayAspectHeight = this.elementaryStream.info.squarePixelHeight;
+ }
}
override getCodec(): VideoCodec {
diff --git a/src/output-format.ts b/src/output-format.ts
index adef3a9..1608cf8 100644
--- a/src/output-format.ts
+++ b/src/output-format.ts
@@ -928,6 +928,13 @@ export class AdtsOutputFormat extends OutputFormat {
* @public
*/
export type FlacOutputFormatOptions = {
+ /**
+ * Configures the output to only append new data at the end, useful for live-streaming the file as it's being
+ * created. When enabled, the STREAMINFO block will not be finalized with accurate min/max block sizes, frame sizes,
+ * or total sample count, so don't use this option when you want to write out a clean file for later use.
+ */
+ appendOnly?: boolean;
+
/**
* Will be called for each FLAC frame that is written.
*
@@ -951,6 +958,9 @@ export class FlacOutputFormat extends OutputFormat {
if (!options || typeof options !== 'object') {
throw new TypeError('options must be an object.');
}
+ if (options.appendOnly !== undefined && typeof options.appendOnly !== 'boolean') {
+ throw new TypeError('options.appendOnly, when provided, must be a boolean.');
+ }
super();
diff --git a/test/browser/conversion.test.ts b/test/browser/conversion.test.ts
new file mode 100644
index 0000000..da0cbfb
--- /dev/null
+++ b/test/browser/conversion.test.ts
@@ -0,0 +1,49 @@
+import { ALL_FORMATS } from '../../src/input-format.js';
+import { Input } from '../../src/input.js';
+import { Mp4OutputFormat } from '../../src/output-format.js';
+import { Output } from '../../src/output.js';
+import { BufferSource, UrlSource } from '../../src/source.js';
+import { expect, test } from 'vitest';
+import { BufferTarget } from '../../src/target.js';
+import { Conversion } from '../../src/conversion.js';
+import { assert } from '../../src/misc.js';
+
+test('Rotation is baked-in when rerendering', async () => {
+ using input = new Input({
+ source: new UrlSource('/rotate-buck-bunny.mp4'),
+ formats: ALL_FORMATS,
+ });
+
+ const ogTrack = await input.getPrimaryVideoTrack();
+ assert(ogTrack);
+
+ expect(ogTrack.rotation).toBe(90);
+ expect(ogTrack.codedWidth).toBe(1920);
+ expect(ogTrack.codedHeight).toBe(1080);
+ expect(ogTrack.displayWidth).toBe(1080);
+ expect(ogTrack.displayHeight).toBe(1920);
+
+ const output = new Output({
+ format: new Mp4OutputFormat(),
+ target: new BufferTarget(),
+ });
+
+ const conversion = await Conversion.init({ input, output, video: {
+ width: 320,
+ } });
+ await conversion.execute();
+
+ using newInput = new Input({
+ source: new BufferSource(output.target.buffer!),
+ formats: ALL_FORMATS,
+ });
+
+ const track = await newInput.getPrimaryVideoTrack();
+ assert(track);
+
+ expect(track.codedWidth).toBe(320);
+ expect(track.codedHeight).toBe(570);
+ expect(track.displayWidth).toBe(320);
+ expect(track.displayHeight).toBe(570);
+ expect(track.rotation).toBe(0);
+});
diff --git a/test/browser/par.test.ts b/test/browser/par.test.ts
index 744999d..0f04c53 100644
--- a/test/browser/par.test.ts
+++ b/test/browser/par.test.ts
@@ -87,8 +87,8 @@ test('Pixel aspect ratio transcode conversion', async () => {
expect(mp4.snapshot.squarePixelHeight).toBe(mp4.snapshot.codedHeight);
expect(mp4.snapshot.displayWidth).toBe(mp4.snapshot.codedWidth);
expect(mp4.snapshot.displayHeight).toBe(mp4.snapshot.codedHeight);
- expect(mp4.snapshot.decoderDisplayAspectWidth).toBe(mp4.snapshot.codedWidth);
- expect(mp4.snapshot.decoderDisplayAspectHeight).toBe(mp4.snapshot.codedHeight);
+ expect(mp4.snapshot.decoderDisplayAspectWidth).toBeUndefined();
+ expect(mp4.snapshot.decoderDisplayAspectHeight).toBeUndefined();
expect(mp4.snapshot.codedWidth).toBe(source.squarePixelWidth);
expect(mp4.snapshot.codedHeight).toBe(source.squarePixelHeight);
diff --git a/test/node/flac.test.ts b/test/node/flac.test.ts
index a7efd4e..f31e19c 100644
--- a/test/node/flac.test.ts
+++ b/test/node/flac.test.ts
@@ -254,3 +254,33 @@ test('can re-mux a .flac', async () => {
expect(otherInputDecoderConfig).toEqual(otherOutputDecoderConfig);
});
+
+test('appendOnly writes correct STREAMINFO header', async () => {
+ const filePath = path.join(__dirname, '..', 'public/sample.flac');
+ using input = new Input({
+ source: new FilePathSource(filePath),
+ formats: ALL_FORMATS,
+ });
+
+ const target = new BufferTarget();
+ const output = new Output({
+ format: new FlacOutputFormat({ appendOnly: true }),
+ target,
+ });
+
+ const conversion = await Conversion.init({ input, output });
+ await conversion.execute();
+
+ assert(target.buffer);
+ const bytes = new Uint8Array(target.buffer);
+
+ // STREAMINFO: min_block=16, max_block=65535, min_frame=0, max_frame=0
+ expect(bytes.slice(8, 18)).toEqual(new Uint8Array([
+ // minimum_block_size=16
+ 0x00, 0x10,
+ // maximum_block_size=65535
+ 0xFF, 0xFF,
+ // minimum_frame_size=0
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ ]));
+});
diff --git a/test/node/mpeg-ts-demuxing.test.ts b/test/node/mpeg-ts-demuxing.test.ts
index e2a6a99..62349b9 100644
--- a/test/node/mpeg-ts-demuxing.test.ts
+++ b/test/node/mpeg-ts-demuxing.test.ts
@@ -46,8 +46,6 @@ test('MPEG-TS metadata reading', async () => {
codec: 'avc1.640020',
codedWidth: 720,
codedHeight: 720,
- displayAspectWidth: 720,
- displayAspectHeight: 720,
colorSpace: {
primaries: 'bt2020',
transfer: 'hlg',
@@ -561,8 +559,6 @@ test('MPEG-TS with HEVC video', async () => {
codec: 'hev1.1.6.L120.90',
codedWidth: 1920,
codedHeight: 1080,
- displayAspectWidth: 1920,
- displayAspectHeight: 1080,
colorSpace: {
primaries: 'bt709',
transfer: 'bt709',
diff --git a/test/public/rotate-buck-bunny.mp4 b/test/public/rotate-buck-bunny.mp4
new file mode 100644
index 0000000..4903e7a
Binary files /dev/null and b/test/public/rotate-buck-bunny.mp4 differ
diff --git a/tsconfig.vitest.json b/tsconfig.vitest.json
index b2de3f3..129fafc 100644
--- a/tsconfig.vitest.json
+++ b/tsconfig.vitest.json
@@ -6,7 +6,7 @@
"composite": true,
"noEmit": false,
"paths": {
- "mediabunny": ["./src/index.ts"],
+ //"mediabunny": ["./src/index.ts"], So that the direct source imports are preferred
"@mediabunny/ac3": ["./packages/ac3/src/index.ts"],
"@mediabunny/aac-encoder": ["./packages/aac-encoder/src/index.ts"],
"@mediabunny/flac-encoder": ["./packages/flac-encoder/src/index.ts"],