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"],