Merge branch 'main' into prores

This commit is contained in:
Vanilagy
2026-06-19 13:24:33 +02:00
58 changed files with 1315 additions and 396 deletions
+57
View File
@@ -1,5 +1,6 @@
import { expect, test } from 'vitest';
import path from 'node:path';
import fs from 'node:fs/promises';
import { assert, toUint8Array } from '../../src/misc.js';
import { Input } from '../../src/input.js';
import { BufferSource, FilePathSource } from '../../src/source.js';
@@ -284,3 +285,59 @@ test('appendOnly writes correct STREAMINFO header', async () => {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
]));
});
test('can read a FLAC file with leading ID3v2 tags', async () => {
const createId3V23TitleTag = (title: string) => {
const titleBytes = new TextEncoder().encode(title);
const frame = new Uint8Array(11 + titleBytes.length);
frame.set([0x54, 0x49, 0x54, 0x32]); // TIT2
frame[7] = 1 + titleBytes.length;
frame[10] = 0; // ISO-8859-1
frame.set(titleBytes, 11);
const tag = new Uint8Array(10 + frame.length);
tag.set([0x49, 0x44, 0x33, 0x03, 0x00, 0x00]); // ID3v2.3
tag[9] = frame.length;
tag.set(frame, 10);
return tag;
};
const concatenateBytes = (...chunks: Uint8Array[]) => {
const result = new Uint8Array(chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0));
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.byteLength;
}
return result;
};
using input = new Input({
source: new BufferSource(concatenateBytes(
createId3V23TitleTag('First tag'),
createId3V23TitleTag('Second tag'),
await fs.readFile(new URL('../public/sample.flac', import.meta.url)),
)),
formats: ALL_FORMATS,
});
expect(await input.canRead()).toBe(true);
expect(await input.getFormat()).toBe(FLAC);
const track = await input.getPrimaryAudioTrack();
assert(track);
expect(await track.getDurationFromMetadata()).toEqual(19.714285714285715);
const firstPacket = await new EncodedPacketSink(track).getPacket(0);
assert(firstPacket);
expect(firstPacket.sequenceNumber).toBe(0);
expect(firstPacket.timestamp).toBe(0);
const metadataTags = await input.getMetadataTags();
expect(metadataTags.title).toBe('First tag');
expect(metadataTags.raw!['TIT2']).toBe('First tag');
expect(metadataTags.raw!['TITLE']).toBe('The Happy Meeting');
});
+46
View File
@@ -526,11 +526,14 @@ test.concurrent('Single-value PDT', { timeout: 15_000 }, async () => {
const tracks = await input.getTracks();
expect((await Promise.all(tracks.map(x => x.isRelativeToUnixEpoch()))).every(x => x)).toBe(true);
expect((await Promise.all(tracks.map(x => x.hasUnixTimeMapping()))).every(x => x)).toBe(true);
const track = tracks[0]!;
const firstTimestamp = await track.getFirstTimestamp();
expect(firstTimestamp).toBe(Date.parse('2013-05-08T17:40:50Z') / 1000);
expect(await track.getUnixTimeForTimestamp(firstTimestamp)).toBe(firstTimestamp);
const endTimestamp = await track.computeDuration();
expect(endTimestamp).toBe(firstTimestamp + 50);
@@ -600,6 +603,49 @@ test.concurrent('PDT with bad values', { timeout: 15_000 }, async () => {
expect(await audioTrack.isRelativeToUnixEpoch()).toBe(false);
});
test.concurrent('Single-value PDT with unix offsets disabled', { timeout: 15_000 }, async () => {
using input = new Input({
source: new UrlSource('https://playertest.longtailvideo.com/adaptive/aviion/manifest.m3u8'),
formats: ALL_FORMATS,
formatOptions: {
hls: {
offsetTimestampsByDateTime: false,
},
},
});
const tracks = await input.getTracks();
expect((await Promise.all(tracks.map(x => x.isRelativeToUnixEpoch()))).every(x => x)).toBe(false);
expect((await Promise.all(tracks.map(x => x.hasUnixTimeMapping()))).every(x => x)).toBe(true);
const track = tracks[0]!;
const firstTimestamp = await track.getFirstTimestamp();
expect(firstTimestamp).toBe(0);
const endTimestamp = await track.computeDuration();
expect(endTimestamp).toBe(firstTimestamp + 50);
const firstPacket = await new EncodedPacketSink(track).getFirstPacket();
assert(firstPacket);
expect(firstPacket.timestamp).toBe(firstTimestamp); // Kinda obvious check tbh
const unixStartTime = await track.getUnixTimeForTimestamp(firstTimestamp);
expect(unixStartTime).toBe(Date.parse('2013-05-08T17:40:50Z') / 1000);
const unixEndTime = await track.getUnixTimeForTimestamp(endTimestamp);
expect(unixEndTime).toBe(Date.parse('2013-05-08T17:41:40Z') / 1000);
const unixTimeBeforeStart = await track.getUnixTimeForTimestamp(firstTimestamp - 10);
expect(unixTimeBeforeStart).toBe(Date.parse('2013-05-08T17:40:40Z') / 1000);
const unixTimeAfterEnd = await track.getUnixTimeForTimestamp(endTimestamp + 10);
expect(unixTimeAfterEnd).toBe(Date.parse('2013-05-08T17:41:50Z') / 1000);
const timestampDt = 0.001;
const unixTimeDt = (await track.getUnixTimeForTimestamp(firstTimestamp + 0.001))! - unixStartTime!;
expect(unixTimeDt).toBeCloseTo(timestampDt);
});
test.concurrent('Alternative audio only', { timeout: 15_000 }, async () => {
using input = new Input({
source: new UrlSource('https://playertest.longtailvideo.com/adaptive/alt-audio-no-video/sintel/playlist.m3u8'),
+28
View File
@@ -0,0 +1,28 @@
import { expect, test } from 'vitest';
import { Input, UnsupportedInputFormatError } from '../../src/input.js';
import { ALL_FORMATS } from '../../src/input-format.js';
import { BufferSource } from '../../src/source.js';
test('Disposing after failed format detection does not emit an unhandled rejection', async () => {
const input = new Input({
source: new BufferSource(new Uint8Array([1, 2, 3, 4])),
formats: ALL_FORMATS,
});
await expect(input.getFormat()).rejects.toThrow(UnsupportedInputFormatError);
const unhandledRejections: unknown[] = [];
const onUnhandledRejection = (reason: unknown) => {
unhandledRejections.push(reason);
};
process.on('unhandledRejection', onUnhandledRejection);
try {
input.dispose();
await new Promise(resolve => setTimeout(resolve, 0));
} finally {
process.off('unhandledRejection', onUnhandledRejection);
}
expect(unhandledRejections).toEqual([]);
});
+24 -1
View File
@@ -354,7 +354,7 @@ test('MPEG-TS seeking race condition test', async () => {
}
});
test('MPEG-TS video key packets', async () => {
test('MPEG-TS video key packets', { timeout: 10_000 }, async () => {
for (let i = 0; i < 2; i++) {
using input = new Input({
source: new FilePathSource(path.join(__dirname, '../public/trim-buck-bunny-ffmpeg.ts')),
@@ -875,3 +875,26 @@ test('MPEG-TS with "extension" PES packets without PTS', async () => {
4693, 223, 144, 174, 118, 9155, 1188, 379, 169, 213,
]);
});
test('MPEG-TS with AUD-less video packets', async () => {
using input = new Input({
source: new FilePathSource(path.join(__dirname, '../public/no-aud.ts')),
formats: ALL_FORMATS,
});
const videoTrack = await input.getPrimaryVideoTrack();
assert(videoTrack);
const sink = new EncodedPacketSink(videoTrack);
const firstPacket = await sink.getFirstPacket();
assert(firstPacket);
const secondPacket = await sink.getNextPacket(firstPacket);
assert(secondPacket);
const thirdPacket = await sink.getNextPacket(secondPacket);
assert(thirdPacket);
expect(firstPacket.data.byteLength).toBe(331774);
expect(secondPacket.data.byteLength).toBe(1749);
expect(thirdPacket.data.byteLength).toBe(4273);
});