Files

184 lines
5.1 KiB
TypeScript

import { expect, test } from 'vitest';
import path from 'node:path';
import {
ALL_FORMATS,
BufferSource,
BufferTarget,
EncodedPacket,
EncodedPacketSink,
EncodedVideoPacketSource,
FilePathSource,
Input,
MP4,
MovOutputFormat,
Mp4OutputFormat,
Output,
} from '../../src/index.js';
import { assert, toUint8Array } from '../../src/misc.js';
const __dirname = new URL('.', import.meta.url).pathname;
test('Should be able to get packets from a .MP4 file', async () => {
const filePath = path.join(__dirname, '..', 'public/video.mp4');
using input = new Input({
source: new FilePathSource(filePath),
formats: ALL_FORMATS,
});
expect(await input.getFormat()).toBe(MP4);
expect(await input.getMimeType()).toBe('video/mp4; codecs="avc1.640028, mp4a.40.2"');
expect(await input.computeDuration()).toBe(5.056);
const track = await input.getPrimaryVideoTrack();
if (!track) throw new Error('No video track found');
const sink = new EncodedPacketSink(track);
let samples = 0;
const timestamps: number[] = [];
for await (const packet of sink.packets()) {
timestamps.push(packet.timestamp);
samples++;
}
expect(samples).toBe(125);
expect(timestamps.slice(0, 10)).toEqual([
0, 0.16, 0.08, 0.04, 0.12, 0.32, 0.24, 0.2, 0.28, 0.48,
]);
});
test('MP4 nclx color information', async () => {
const output = new Output({
format: new Mp4OutputFormat(),
target: new BufferTarget(),
});
const source = new EncodedVideoPacketSource('vp8');
output.addVideoTrack(source);
await output.start();
await source.add(
new EncodedPacket(new Uint8Array(1024), 'key', 0, 0.1),
{
decoderConfig: {
codec: 'vp8',
codedWidth: 1280,
codedHeight: 720,
colorSpace: {
primaries: 'bt2020' as VideoColorPrimaries,
transfer: 'pq' as VideoTransferCharacteristics,
matrix: 'bt2020-ncl' as VideoMatrixCoefficients,
fullRange: false,
},
},
},
);
await output.finalize();
const str = String.fromCharCode(...new Uint8Array(output.target.buffer!));
expect(str.includes('nclc')).toBe(false);
expect(str.includes('nclx')).toBe(true);
using input = new Input({
source: new BufferSource(output.target.buffer!),
formats: ALL_FORMATS,
});
const track = await input.getPrimaryVideoTrack();
if (!track) throw new Error('No video track found');
expect(await track.getColorSpace()).toEqual({
primaries: 'bt2020',
transfer: 'pq',
matrix: 'bt2020-ncl',
fullRange: false,
});
expect(await track.hasHighDynamicRange()).toBe(true);
});
test('QuickTime nclc color information', async () => {
const output = new Output({
format: new MovOutputFormat(),
target: new BufferTarget(),
});
const source = new EncodedVideoPacketSource('vp8');
output.addVideoTrack(source);
await output.start();
await source.add(
new EncodedPacket(new Uint8Array(1024), 'key', 0, 0.1),
{
decoderConfig: {
codec: 'vp8',
codedWidth: 1280,
codedHeight: 720,
colorSpace: {
primaries: 'bt2020' as VideoColorPrimaries,
transfer: 'pq' as VideoTransferCharacteristics,
matrix: 'bt2020-ncl' as VideoMatrixCoefficients,
fullRange: false,
},
},
},
);
await output.finalize();
const str = String.fromCharCode(...new Uint8Array(output.target.buffer!));
expect(str.includes('nclc')).toBe(true);
expect(str.includes('nclx')).toBe(false);
using input = new Input({
source: new BufferSource(output.target.buffer!),
formats: ALL_FORMATS,
});
const track = await input.getPrimaryVideoTrack();
if (!track) throw new Error('No video track found');
expect(await track.getColorSpace()).toEqual({
primaries: 'bt2020',
transfer: 'pq',
matrix: 'bt2020-ncl',
fullRange: undefined,
});
expect(await track.hasHighDynamicRange()).toBe(true);
});
// Annex B isn't supposed to exist in MP4, but some files have it anyway, with an empty avcC box
test('Annex B', async () => {
const filePath = path.join(__dirname, '..', 'public/annex-b.mp4');
using input = new Input({
source: new FilePathSource(filePath),
formats: ALL_FORMATS,
});
const track = await input.getPrimaryVideoTrack();
if (!track) throw new Error('No video track found');
const decoderConfig = (await track.getDecoderConfig())!;
expect(decoderConfig.codec).toBe('avc1.424028');
expect(decoderConfig.description).toBeUndefined();
const sink = new EncodedPacketSink(track);
const firstPacket = (await sink.getFirstPacket())!;
expect([...firstPacket.data.slice(0, 4)]).toEqual([0, 0, 0, 1]);
});
test('HE-AAC v2 audio config is parsed correctly', async () => {
const filePath = path.join(__dirname, '..', 'public/he-aac-v2.mp4');
using input = new Input({
source: new FilePathSource(filePath),
formats: ALL_FORMATS,
});
const track = await input.getPrimaryAudioTrack();
assert(track);
expect(await track.getSampleRate()).toBe(44100);
expect(await track.getNumberOfChannels()).toBe(2);
const decoderConfig = (await track.getDecoderConfig())!;
expect(decoderConfig.codec).toBe('mp4a.40.29');
expect(decoderConfig.sampleRate).toBe(44100);
expect(decoderConfig.numberOfChannels).toBe(2);
expect([...toUint8Array(decoderConfig.description!)]).toEqual([0xeb, 0x8a, 0x08, 0x00]);
});