diff --git a/src/atem.ts b/src/atem.ts index 9efa19c9c..99581b927 100644 --- a/src/atem.ts +++ b/src/atem.ts @@ -19,6 +19,7 @@ import { InputChannel } from './state/input' import { DownstreamKeyerGeneral, DownstreamKeyerMask } from './state/video/downstreamKeyers' import * as DT from './dataTransfer' import * as Util from './lib/atemUtil' +import { getVideoModeInfo } from './lib/videoMode' import * as Enums from './enums' import { ClassicAudioMonitorChannel, @@ -54,6 +55,7 @@ import { TimeCommand } from './commands' import { TimeInfo } from './state/info' import { SomeAtemAudioLevels } from './state/levels' import { generateUploadBufferInfo, UploadBufferInfo } from './dataTransfer/dataTransferUploadBuffer' +import { convertWAVToRaw } from './lib/converters/wavAudio' export interface AtemOptions { address?: string @@ -776,7 +778,7 @@ export class Atem extends BasicAtem { options?: DT.UploadStillEncodingOptions ): Promise { if (!this.state) throw new Error('Unable to check current resolution') - const resolution = Util.getVideoModeInfo(this.state.settings.videoMode) + const resolution = getVideoModeInfo(this.state.settings.videoMode) if (!resolution) throw new Error('Failed to determine required resolution') const encodedData = generateUploadBufferInfo(data, resolution, !options?.disableRLE) @@ -803,7 +805,7 @@ export class Atem extends BasicAtem { options?: DT.UploadStillEncodingOptions ): Promise { if (!this.state) throw new Error('Unable to check current resolution') - const resolution = Util.getVideoModeInfo(this.state.settings.videoMode) + const resolution = getVideoModeInfo(this.state.settings.videoMode) if (!resolution) throw new Error('Failed to determine required resolution') const provideFrame = async function* (): AsyncGenerator { @@ -822,7 +824,7 @@ export class Atem extends BasicAtem { * @returns Promise which resolves once the clip audio is uploaded */ public async uploadAudio(index: number, data: Buffer, name: string): Promise { - return this.dataTransferManager.uploadAudio(index, Util.convertWAVToRaw(data, this.state?.info?.model), name) + return this.dataTransferManager.uploadAudio(index, convertWAVToRaw(data, this.state?.info?.model), name) } public async setClassicAudioMixerInputProps( diff --git a/src/dataTransfer/dataTransferUploadBuffer.ts b/src/dataTransfer/dataTransferUploadBuffer.ts index 1d9bdfbd4..b009bb829 100644 --- a/src/dataTransfer/dataTransferUploadBuffer.ts +++ b/src/dataTransfer/dataTransferUploadBuffer.ts @@ -10,7 +10,9 @@ import { import * as crypto from 'crypto' import { DataTransfer, ProgressTransferResult, DataTransferState } from './dataTransfer' import debug0 = require('debug') -import * as Util from '../lib/atemUtil' +import { VideoModeInfo } from '../lib/videoMode' +import { convertRGBAToYUV422 } from '../lib/converters/rgbaToYuv422' +import { RLE_HEADER, encodeRLE } from '../lib/converters/rle' const debug = debug0('atem-connection:data-transfer:upload-buffer') @@ -41,7 +43,7 @@ export function generateHashForBuffer(data: Buffer): string { export function generateUploadBufferInfo( data: Buffer | UploadBufferInfo, - resolution: Util.VideoModeInfo, + resolution: VideoModeInfo, shouldEncodeRLE: boolean ): UploadBufferInfo { const expectedLength = resolution.width * resolution.height * 4 @@ -49,10 +51,10 @@ export function generateUploadBufferInfo( if (data.length !== expectedLength) throw new Error(`Pixel buffer has incorrect length. Received ${data.length} expected ${expectedLength}`) - const encodedData = Util.convertRGBAToYUV422(resolution.width, resolution.height, data) + const encodedData = convertRGBAToYUV422(resolution.width, resolution.height, data) return { - encodedData: shouldEncodeRLE ? Util.encodeRLE(encodedData) : encodedData, + encodedData: shouldEncodeRLE ? encodeRLE(encodedData) : encodedData, rawDataLength: encodedData.length, isRleEncoded: shouldEncodeRLE, hash: generateHashForBuffer(encodedData), @@ -66,7 +68,7 @@ export function generateUploadBufferInfo( if (shouldEncodeRLE && !data.isRleEncoded) { data.isRleEncoded = true - data.encodedData = Util.encodeRLE(data.encodedData) + data.encodedData = encodeRLE(data.encodedData) } return result @@ -166,10 +168,10 @@ export abstract class DataTransferUploadBuffer extends DataTransfer { if (chunkSize + this.#bytesSent > this.data.length) { // The last chunk can't end with a RLE header shortenBy = this.#bytesSent + chunkSize - this.data.length - } else if (Util.RLE_HEADER === this.data.readBigUint64BE(this.#bytesSent + chunkSize - 8)) { + } else if (RLE_HEADER === this.data.readBigUint64BE(this.#bytesSent + chunkSize - 8)) { // RLE header starts 8 bytes before the end shortenBy = 8 - } else if (Util.RLE_HEADER === this.data.readBigUint64BE(this.#bytesSent + chunkSize - 16)) { + } else if (RLE_HEADER === this.data.readBigUint64BE(this.#bytesSent + chunkSize - 16)) { // RLE header starts 16 bytes before the end shortenBy = 16 } diff --git a/src/lib/atemUtil.ts b/src/lib/atemUtil.ts index b12c917e9..fc7fa4b6a 100644 --- a/src/lib/atemUtil.ts +++ b/src/lib/atemUtil.ts @@ -1,5 +1,3 @@ -import * as Enums from '../enums' -import WaveFile = require('wavefile') import type { IDeserializedCommand, ISerializableCommand } from '../commands' export function bufToBase64String(buffer: Buffer, start: number, length: number): string { @@ -12,281 +10,6 @@ export function bufToNullTerminatedString(buffer: Buffer, start: number, length: return slice.toString('utf8', 0, nullIndex < 0 ? slice.length : nullIndex) } -/** - * @todo: MINT - 2018-5-24: - * Create util functions that handle proper colour spaces in UHD. - */ -export function convertRGBAToYUV422(width: number, height: number, data: Buffer): Buffer { - // BT.709 or BT.601 - const KR = height >= 720 ? 0.2126 : 0.299 - const KB = height >= 720 ? 0.0722 : 0.114 - const KG = 1 - KR - KB - - const KRi = 1 - KR - const KBi = 1 - KB - - const YRange = 219 - const CbCrRange = 224 - const HalfCbCrRange = CbCrRange / 2 - - const YOffset = 16 << 8 - const CbCrOffset = 128 << 8 - - const KRoKBi = (KR / KBi) * HalfCbCrRange - const KGoKBi = (KG / KBi) * HalfCbCrRange - const KBoKRi = (KB / KRi) * HalfCbCrRange - const KGoKRi = (KG / KRi) * HalfCbCrRange - - const genColor = (rawA: number, uv16: number, y16: number): number => { - const a = ((rawA << 2) * 219) / 255 + (16 << 2) - const y = Math.round(y16) >> 6 - const uv = Math.round(uv16) >> 6 - - return (a << 20) + (uv << 10) + y - } - - const buffer = Buffer.alloc(width * height * 4) - for (let i = 0; i < width * height * 4; i += 8) { - const r1 = data[i + 0] - const g1 = data[i + 1] - const b1 = data[i + 2] - - const r2 = data[i + 4] - const g2 = data[i + 5] - const b2 = data[i + 6] - - const a1 = data[i + 3] - const a2 = data[i + 7] - - const y16a = YOffset + KR * YRange * r1 + KG * YRange * g1 + KB * YRange * b1 - const cb16 = CbCrOffset + (-KRoKBi * r1 - KGoKBi * g1 + HalfCbCrRange * b1) - const y16b = YOffset + KR * YRange * r2 + KG * YRange * g2 + KB * YRange * b2 - const cr16 = CbCrOffset + (HalfCbCrRange * r1 - KGoKRi * g1 - KBoKRi * b1) - - buffer.writeUInt32BE(genColor(a1, cb16, y16a), i) - buffer.writeUInt32BE(genColor(a2, cr16, y16b), i + 4) - } - return buffer -} - -export const RLE_HEADER = 0xfefefefefefefefen - -export function encodeRLE(data: Buffer): Buffer { - const result = Buffer.alloc(data.length) - let lastBlock = data.readBigUInt64BE() - let identicalCount = 0 - let differentCount = 0 - let resultOffset = -8 - - for (let sourceOffset = 8; sourceOffset < data.length; sourceOffset += 8) { - const block = data.readBigUInt64BE(sourceOffset) - - if (block === lastBlock) { - ++identicalCount - if (differentCount) { - data.copy(result, resultOffset + 8, sourceOffset - 8 * (differentCount + 1), sourceOffset - 8) - resultOffset += differentCount * 8 - differentCount = 0 - } - lastBlock = block - continue - } - if (identicalCount > 2) { - result.writeBigUInt64BE(RLE_HEADER, (resultOffset += 8)) - result.writeBigUInt64BE(BigInt(identicalCount + 1), (resultOffset += 8)) - result.writeBigUInt64BE(lastBlock, (resultOffset += 8)) - } else if (identicalCount > 0) { - for (let i = 0; i <= identicalCount; ++i) { - result.writeBigUInt64BE(lastBlock, (resultOffset += 8)) - } - } else { - ++differentCount - } - lastBlock = block - identicalCount = 0 - } - - if (identicalCount > 2) { - result.writeBigUInt64BE(RLE_HEADER, (resultOffset += 8)) - result.writeBigUInt64BE(BigInt(identicalCount + 1), (resultOffset += 8)) - result.writeBigUInt64BE(lastBlock, (resultOffset += 8)) - } else if (identicalCount > 0) { - for (let i = 0; i <= identicalCount; ++i) { - result.writeBigUInt64BE(lastBlock, (resultOffset += 8)) - } - } else { - ++differentCount - data.copy(result, resultOffset + 8, data.length - 8 * differentCount, data.length) - resultOffset += differentCount * 8 - } - - return result.slice(0, resultOffset + 8) -} - -export interface VideoModeInfo { - format: Enums.VideoFormat - width: number - height: number -} - -const dimsPAL: Pick = { - format: Enums.VideoFormat.SD, - width: 720, - height: 576, -} -const dimsNTSC: Pick = { - format: Enums.VideoFormat.SD, - width: 640, - height: 480, -} -const dims720p: Pick = { - format: Enums.VideoFormat.HD720, - width: 1280, - height: 720, -} -const dims1080p: Pick = { - format: Enums.VideoFormat.HD1080, - width: 1920, - height: 1080, -} -const dims4k: Pick = { - format: Enums.VideoFormat.UHD4K, - width: 3840, - height: 2160, -} -const dims8k: Pick = { - format: Enums.VideoFormat.UDH8K, - width: 7680, - height: 4260, -} -const VideoModeInfoImpl: { [key in Enums.VideoMode]: VideoModeInfo } = { - [Enums.VideoMode.N525i5994NTSC]: { - ...dimsNTSC, - }, - [Enums.VideoMode.P625i50PAL]: { - ...dimsPAL, - }, - [Enums.VideoMode.N525i5994169]: { - ...dimsNTSC, - }, - [Enums.VideoMode.P625i50169]: { - ...dimsPAL, - }, - - [Enums.VideoMode.P720p50]: { - ...dims720p, - }, - [Enums.VideoMode.N720p5994]: { - ...dims720p, - }, - [Enums.VideoMode.P1080i50]: { - ...dims1080p, - }, - [Enums.VideoMode.N1080i5994]: { - ...dims1080p, - }, - [Enums.VideoMode.N1080p2398]: { - ...dims1080p, - }, - [Enums.VideoMode.N1080p24]: { - ...dims1080p, - }, - [Enums.VideoMode.P1080p25]: { - ...dims1080p, - }, - [Enums.VideoMode.N1080p2997]: { - ...dims1080p, - }, - [Enums.VideoMode.P1080p50]: { - ...dims1080p, - }, - [Enums.VideoMode.N1080p5994]: { - ...dims1080p, - }, - - [Enums.VideoMode.N4KHDp2398]: { - ...dims4k, - }, - [Enums.VideoMode.N4KHDp24]: { - ...dims4k, - }, - [Enums.VideoMode.P4KHDp25]: { - ...dims4k, - }, - [Enums.VideoMode.N4KHDp2997]: { - ...dims4k, - }, - - [Enums.VideoMode.P4KHDp5000]: { - ...dims4k, - }, - [Enums.VideoMode.N4KHDp5994]: { - ...dims4k, - }, - - [Enums.VideoMode.N8KHDp2398]: { - ...dims8k, - }, - [Enums.VideoMode.N8KHDp24]: { - ...dims8k, - }, - [Enums.VideoMode.P8KHDp25]: { - ...dims8k, - }, - [Enums.VideoMode.N8KHDp2997]: { - ...dims8k, - }, - [Enums.VideoMode.P8KHDp50]: { - ...dims8k, - }, - [Enums.VideoMode.N8KHDp5994]: { - ...dims8k, - }, - - [Enums.VideoMode.N1080p30]: { - ...dims1080p, - }, - [Enums.VideoMode.N1080p60]: { - ...dims1080p, - }, -} - -export function getVideoModeInfo(videoMode: Enums.VideoMode): VideoModeInfo | undefined { - return VideoModeInfoImpl[videoMode] -} - -export function convertWAVToRaw(inputBuffer: Buffer, model: Enums.Model | undefined): Buffer { - const wav = new (WaveFile as any)(inputBuffer) - - if (wav.fmt.bitsPerSample !== 24) { - throw new Error(`Invalid wav bit bits per sample: ${wav.fmt.bitsPerSample}`) - } - - if (wav.fmt.numChannels !== 2) { - throw new Error(`Invalid number of wav channels: ${wav.fmt.numChannel}`) - } - - const buffer = Buffer.from(wav.data.samples) - const buffer2 = Buffer.alloc(buffer.length) - for (let i = 0; i < buffer.length; i += 3) { - // 24bit samples, change endian from wavfile to atem requirements - buffer2.writeUIntBE(buffer.readUIntLE(i, 3), i, 3) - } - - if (model === undefined || model >= Enums.Model.PS4K) { - // If we don't know the model, assume we want the newer mode as that is more likely - // Newer models want a weird byte order - const buffer3 = Buffer.alloc(buffer2.length) - for (let i = 0; i < buffer.length; i += 4) { - buffer3.writeUIntBE(buffer2.readUIntLE(i, 4), i, 4) - } - - return buffer3 - } else { - return buffer2 - } -} - export function UInt16BEToDecibel(input: number): number { // 0 = -inf, 32768 = 0, 65381 = +6db return Math.round(Math.log10(input / 32768) * 20 * 100) / 100 diff --git a/src/lib/__tests__/atemUtil.spec.ts b/src/lib/converters/__tests__/rle.spec.ts similarity index 98% rename from src/lib/__tests__/atemUtil.spec.ts rename to src/lib/converters/__tests__/rle.spec.ts index 6a72bc583..dbb819ec3 100644 --- a/src/lib/__tests__/atemUtil.spec.ts +++ b/src/lib/converters/__tests__/rle.spec.ts @@ -1,6 +1,6 @@ -import { encodeRLE } from '../atemUtil' +import { encodeRLE } from '../rle' -describe('RLE', () => { +describe('encodeRLE', () => { test('no repetitions', () => { const source = `abababababababab\ cdcdcdcdcdcdcdcd\ diff --git a/src/lib/converters/colorConstants.ts b/src/lib/converters/colorConstants.ts new file mode 100644 index 000000000..67990ed3c --- /dev/null +++ b/src/lib/converters/colorConstants.ts @@ -0,0 +1,55 @@ +export interface ColorConvertConstants { + readonly KR: number + readonly KB: number + readonly KG: number + + readonly KRi: number + readonly KBi: number + + readonly YRange: number + readonly CbCrRange: number + readonly HalfCbCrRange: number + + readonly YOffset: number + readonly CbCrOffset: number + + readonly KRoKBi: number + readonly KGoKBi: number + readonly KBoKRi: number + readonly KGoKRi: number +} + +function createColorConvertConstants(KR: number, KB: number): ColorConvertConstants { + const KG = 1 - KR - KB + + const KRi = 1 - KR + const KBi = 1 - KB + + const YRange = 219 + const CbCrRange = 224 + const HalfCbCrRange = CbCrRange / 2 + + return { + KR, + KB, + KG, + + KRi, + KBi, + + YRange, + CbCrRange, + HalfCbCrRange, + + YOffset: 16 << 8, + CbCrOffset: 128 << 8, + + KRoKBi: (KR / KBi) * HalfCbCrRange, + KGoKBi: (KG / KBi) * HalfCbCrRange, + KBoKRi: (KB / KRi) * HalfCbCrRange, + KGoKRi: (KG / KRi) * HalfCbCrRange, + } +} + +export const ColorConvertConstantsBT709 = createColorConvertConstants(0.2126, 0.0722) +export const ColorConvertConstantsBT601 = createColorConvertConstants(0.299, 0.114) diff --git a/src/lib/converters/rgbaToYuv422.ts b/src/lib/converters/rgbaToYuv422.ts new file mode 100644 index 000000000..def501c04 --- /dev/null +++ b/src/lib/converters/rgbaToYuv422.ts @@ -0,0 +1,51 @@ +/** + * @todo: MINT - 2018-5-24: + * Create util functions that handle proper colour spaces in UHD. + */ + +import { ColorConvertConstantsBT709, ColorConvertConstantsBT601 } from './colorConstants' + +export function convertRGBAToYUV422(width: number, height: number, data: Buffer): Buffer { + const constants = height >= 720 ? ColorConvertConstantsBT709 : ColorConvertConstantsBT601 + + const genColor = (rawA: number, uv16: number, y16: number): number => { + const a = ((rawA << 2) * 219) / 255 + (16 << 2) + const y = Math.round(y16) >> 6 + const uv = Math.round(uv16) >> 6 + + return (a << 20) + (uv << 10) + y + } + + const buffer = Buffer.alloc(width * height * 4) + for (let i = 0; i < width * height * 4; i += 8) { + const r1 = data[i + 0] + const g1 = data[i + 1] + const b1 = data[i + 2] + + const r2 = data[i + 4] + const g2 = data[i + 5] + const b2 = data[i + 6] + + const a1 = data[i + 3] + const a2 = data[i + 7] + + const y16a = + constants.YOffset + + constants.KR * constants.YRange * r1 + + constants.KG * constants.YRange * g1 + + constants.KB * constants.YRange * b1 + const cb16 = + constants.CbCrOffset + (-constants.KRoKBi * r1 - constants.KGoKBi * g1 + constants.HalfCbCrRange * b1) + const y16b = + constants.YOffset + + constants.KR * constants.YRange * r2 + + constants.KG * constants.YRange * g2 + + constants.KB * constants.YRange * b2 + const cr16 = + constants.CbCrOffset + (constants.HalfCbCrRange * r1 - constants.KGoKRi * g1 - constants.KBoKRi * b1) + + buffer.writeUInt32BE(genColor(a1, cb16, y16a), i) + buffer.writeUInt32BE(genColor(a2, cr16, y16b), i + 4) + } + return buffer +} diff --git a/src/lib/converters/rle.ts b/src/lib/converters/rle.ts new file mode 100644 index 000000000..f87f51092 --- /dev/null +++ b/src/lib/converters/rle.ts @@ -0,0 +1,53 @@ +export const RLE_HEADER = 0xfefefefefefefefen + +export function encodeRLE(data: Buffer): Buffer { + const result = Buffer.alloc(data.length) + let lastBlock = data.readBigUInt64BE() + let identicalCount = 0 + let differentCount = 0 + let resultOffset = -8 + + for (let sourceOffset = 8; sourceOffset < data.length; sourceOffset += 8) { + const block = data.readBigUInt64BE(sourceOffset) + + if (block === lastBlock) { + ++identicalCount + if (differentCount) { + data.copy(result, resultOffset + 8, sourceOffset - 8 * (differentCount + 1), sourceOffset - 8) + resultOffset += differentCount * 8 + differentCount = 0 + } + lastBlock = block + continue + } + if (identicalCount > 2) { + result.writeBigUInt64BE(RLE_HEADER, (resultOffset += 8)) + result.writeBigUInt64BE(BigInt(identicalCount + 1), (resultOffset += 8)) + result.writeBigUInt64BE(lastBlock, (resultOffset += 8)) + } else if (identicalCount > 0) { + for (let i = 0; i <= identicalCount; ++i) { + result.writeBigUInt64BE(lastBlock, (resultOffset += 8)) + } + } else { + ++differentCount + } + lastBlock = block + identicalCount = 0 + } + + if (identicalCount > 2) { + result.writeBigUInt64BE(RLE_HEADER, (resultOffset += 8)) + result.writeBigUInt64BE(BigInt(identicalCount + 1), (resultOffset += 8)) + result.writeBigUInt64BE(lastBlock, (resultOffset += 8)) + } else if (identicalCount > 0) { + for (let i = 0; i <= identicalCount; ++i) { + result.writeBigUInt64BE(lastBlock, (resultOffset += 8)) + } + } else { + ++differentCount + data.copy(result, resultOffset + 8, data.length - 8 * differentCount, data.length) + resultOffset += differentCount * 8 + } + + return result.slice(0, resultOffset + 8) +} diff --git a/src/lib/converters/wavAudio.ts b/src/lib/converters/wavAudio.ts new file mode 100644 index 000000000..dbfb808bf --- /dev/null +++ b/src/lib/converters/wavAudio.ts @@ -0,0 +1,34 @@ +import * as Enums from '../../enums' +import WaveFile = require('wavefile') + +export function convertWAVToRaw(inputBuffer: Buffer, model: Enums.Model | undefined): Buffer { + const wav = new (WaveFile as any)(inputBuffer) + + if (wav.fmt.bitsPerSample !== 24) { + throw new Error(`Invalid wav bit bits per sample: ${wav.fmt.bitsPerSample}`) + } + + if (wav.fmt.numChannels !== 2) { + throw new Error(`Invalid number of wav channels: ${wav.fmt.numChannel}`) + } + + const buffer = Buffer.from(wav.data.samples) + const buffer2 = Buffer.alloc(buffer.length) + for (let i = 0; i < buffer.length; i += 3) { + // 24bit samples, change endian from wavfile to atem requirements + buffer2.writeUIntBE(buffer.readUIntLE(i, 3), i, 3) + } + + if (model === undefined || model >= Enums.Model.PS4K) { + // If we don't know the model, assume we want the newer mode as that is more likely + // Newer models want a weird byte order + const buffer3 = Buffer.alloc(buffer2.length) + for (let i = 0; i < buffer.length; i += 4) { + buffer3.writeUIntBE(buffer2.readUIntLE(i, 4), i, 4) + } + + return buffer3 + } else { + return buffer2 + } +} diff --git a/src/lib/multiviewLabel.ts b/src/lib/multiviewLabel.ts index 8845228a6..be6737126 100644 --- a/src/lib/multiviewLabel.ts +++ b/src/lib/multiviewLabel.ts @@ -1,7 +1,7 @@ import { FontFace, NewMemoryFace } from '@julusian/freetype2' import { Model, VideoFormat, VideoMode } from '../enums' import { AtemState } from '../state' -import { getVideoModeInfo } from './atemUtil' +import { getVideoModeInfo } from './videoMode' import { readFile } from 'fs/promises' import path = require('path') diff --git a/src/lib/videoMode.ts b/src/lib/videoMode.ts new file mode 100644 index 000000000..e1dda804b --- /dev/null +++ b/src/lib/videoMode.ts @@ -0,0 +1,134 @@ +import * as Enums from '../enums' + +export interface VideoModeInfo { + format: Enums.VideoFormat + width: number + height: number +} +const dimsPAL: Pick = { + format: Enums.VideoFormat.SD, + width: 720, + height: 576, +} +const dimsNTSC: Pick = { + format: Enums.VideoFormat.SD, + width: 640, + height: 480, +} +const dims720p: Pick = { + format: Enums.VideoFormat.HD720, + width: 1280, + height: 720, +} +const dims1080p: Pick = { + format: Enums.VideoFormat.HD1080, + width: 1920, + height: 1080, +} +const dims4k: Pick = { + format: Enums.VideoFormat.UHD4K, + width: 3840, + height: 2160, +} +const dims8k: Pick = { + format: Enums.VideoFormat.UDH8K, + width: 7680, + height: 4260, +} +const VideoModeInfoImpl: { + [key in Enums.VideoMode]: VideoModeInfo +} = { + [Enums.VideoMode.N525i5994NTSC]: { + ...dimsNTSC, + }, + [Enums.VideoMode.P625i50PAL]: { + ...dimsPAL, + }, + [Enums.VideoMode.N525i5994169]: { + ...dimsNTSC, + }, + [Enums.VideoMode.P625i50169]: { + ...dimsPAL, + }, + + [Enums.VideoMode.P720p50]: { + ...dims720p, + }, + [Enums.VideoMode.N720p5994]: { + ...dims720p, + }, + [Enums.VideoMode.P1080i50]: { + ...dims1080p, + }, + [Enums.VideoMode.N1080i5994]: { + ...dims1080p, + }, + [Enums.VideoMode.N1080p2398]: { + ...dims1080p, + }, + [Enums.VideoMode.N1080p24]: { + ...dims1080p, + }, + [Enums.VideoMode.P1080p25]: { + ...dims1080p, + }, + [Enums.VideoMode.N1080p2997]: { + ...dims1080p, + }, + [Enums.VideoMode.P1080p50]: { + ...dims1080p, + }, + [Enums.VideoMode.N1080p5994]: { + ...dims1080p, + }, + + [Enums.VideoMode.N4KHDp2398]: { + ...dims4k, + }, + [Enums.VideoMode.N4KHDp24]: { + ...dims4k, + }, + [Enums.VideoMode.P4KHDp25]: { + ...dims4k, + }, + [Enums.VideoMode.N4KHDp2997]: { + ...dims4k, + }, + + [Enums.VideoMode.P4KHDp5000]: { + ...dims4k, + }, + [Enums.VideoMode.N4KHDp5994]: { + ...dims4k, + }, + + [Enums.VideoMode.N8KHDp2398]: { + ...dims8k, + }, + [Enums.VideoMode.N8KHDp24]: { + ...dims8k, + }, + [Enums.VideoMode.P8KHDp25]: { + ...dims8k, + }, + [Enums.VideoMode.N8KHDp2997]: { + ...dims8k, + }, + [Enums.VideoMode.P8KHDp50]: { + ...dims8k, + }, + [Enums.VideoMode.N8KHDp5994]: { + ...dims8k, + }, + + [Enums.VideoMode.N1080p30]: { + ...dims1080p, + }, + [Enums.VideoMode.N1080p60]: { + ...dims1080p, + }, +} + +export function getVideoModeInfo(videoMode: Enums.VideoMode): VideoModeInfo | undefined { + return VideoModeInfoImpl[videoMode] +}