import { describe, expect, it } from 'vitest'; import { avcConfigSps, avcConfigWithColour, spsColour, spsWithColour, } from '../../src/core/recording/avcColour'; import type { MatroskaColour } from '../../src/core/recording/matroska'; const hex = (text: string) => Uint8Array.from(text.match(/../g)!.map((pair) => parseInt(pair, 16))); const toHex = (bytes: Uint8Array) => [...bytes].map((value) => value.toString(16).padStart(2, '0')).join(''); /** Chrome 136 on Windows, a game view recording (RGBX frames): a VUI, but no `video_signal_type`. */ const GAME_VIEW = hex( '0164002affe100196764002aac2b403c0113f2e022000007d00003a9801e38554001000468ee3cb0fdf8f800', ); /** The same file after the rewrite, as ffmpeg's `trace_headers` read it: full range, BT.709, sRGB. */ const GAME_VIEW_FIXED = hex( '0164002affe1001c6764002aac2b403c0113f2e02dc043405000003e80001d4c00f1c2aa01000468ee3cb0fdf8f800', ); /** Chrome 136 on Windows, a recording of the editor (canvas frames): the colour already in the SPS. */ const EDITOR = hex('01640c33ffe1001367640c33ac18d0050016b4d40434041e1108d401000468ce3c80fdf8f800'); /** A baseline SPS without a VUI: 320×240, written by hand. */ const BASELINE = hex('6742c01eda0507e4'); const FULL_SRGB: MatroskaColour = { matrix: 1, transfer: 13, primaries: 1, fullRange: true }; const TV_SRGB: MatroskaColour = { matrix: 1, transfer: 13, primaries: 1, fullRange: false }; describe('avcConfigWithColour', () => { it('writes the colour into an SPS that lacks it, as ffmpeg reads it back', () => { expect(spsColour(avcConfigSps(GAME_VIEW)[0]!)).toBeNull(); const fixed = avcConfigWithColour(GAME_VIEW, FULL_SRGB); expect(toHex(fixed)).toBe(toHex(GAME_VIEW_FIXED)); expect(spsColour(avcConfigSps(fixed)[0]!)).toEqual(FULL_SRGB); }); it('keeps everything around the SPS byte for byte', () => { const fixed = avcConfigWithColour(GAME_VIEW, FULL_SRGB); expect(toHex(fixed.subarray(0, 6))).toBe(toHex(GAME_VIEW.subarray(0, 6))); const pps = (config: Uint8Array) => config.subarray(6 + 2 + avcConfigSps(config)[0]!.length); expect(toHex(pps(fixed))).toBe(toHex(pps(GAME_VIEW))); }); it('changes nothing where the SPS already says the same colour', () => { expect(spsColour(avcConfigSps(EDITOR)[0]!)).toEqual(TV_SRGB); expect(toHex(avcConfigWithColour(EDITOR, TV_SRGB))).toBe(toHex(EDITOR)); }); it('replaces a colour the SPS says with the one asked for', () => { const changed = avcConfigWithColour(EDITOR, FULL_SRGB); expect(spsColour(avcConfigSps(changed)[0]!)).toEqual(FULL_SRGB); expect(toHex(avcConfigWithColour(changed, TV_SRGB))).toBe(toHex(EDITOR)); }); it('is the same once written twice', () => { const once = avcConfigWithColour(GAME_VIEW, FULL_SRGB); expect(toHex(avcConfigWithColour(once, FULL_SRGB))).toBe(toHex(once)); }); it('refuses what is no avcC', () => { expect(() => avcConfigWithColour(hex('00000001'), FULL_SRGB)).toThrow(); }); }); describe('spsWithColour', () => { it('gives an SPS without a VUI one that says the colour', () => { expect(spsColour(BASELINE)).toBeNull(); const fixed = spsWithColour(BASELINE, FULL_SRGB); // As ffmpeg's `trace_headers` reads it: 320×240 still, then the VUI with the colour alone. expect(toHex(fixed)).toBe('6742c01eda0507e9b808680810'); expect(spsColour(fixed)).toEqual(FULL_SRGB); }); it('escapes what would read as a start code, and reads it back', () => { // Zeros in all three numbers put 00 00 00 into this SPS, which has to go out as 00 00 03 00. const zero: MatroskaColour = { matrix: 0, transfer: 0, primaries: 0, fullRange: false }; const escaped = avcConfigWithColour(EDITOR, zero); expect(toHex(avcConfigSps(escaped)[0]!)).toBe('67640c33ac18d0050016b4d4000003001e1108d4'); expect(spsColour(avcConfigSps(escaped)[0]!)).toEqual(zero); expect(toHex(avcConfigWithColour(escaped, TV_SRGB))).toBe(toHex(EDITOR)); }); it('refuses a NAL unit that is no SPS', () => { expect(() => spsWithColour(hex('68ee3cb0'), FULL_SRGB)).toThrow(); }); });