mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-27 02:43:48 +02:00
Support QuickTime nclc color information (#397)
* Support QuickTime nclc color information * Tighten QuickTime nclc color test fixture * Make QuickTime muxer write nclc color box --------- Co-authored-by: Vanilagy <[email protected]>
This commit is contained in:
+1
-1
@@ -691,7 +691,7 @@ export class InputVideoTrack extends InputTrack {
|
|||||||
const colorSpace = await this._backing.getColorSpace();
|
const colorSpace = await this._backing.getColorSpace();
|
||||||
|
|
||||||
return (colorSpace.primaries as string) === 'bt2020' || (colorSpace.primaries as string) === 'smpte432'
|
return (colorSpace.primaries as string) === 'bt2020' || (colorSpace.primaries as string) === 'smpte432'
|
||||||
|| (colorSpace.transfer as string) === 'pg' || (colorSpace.transfer as string) === 'hlg'
|
|| (colorSpace.transfer as string) === 'pq' || (colorSpace.transfer as string) === 'hlg'
|
||||||
|| (colorSpace.matrix as string) === 'bt2020-ncl';
|
|| (colorSpace.matrix as string) === 'bt2020-ncl';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -741,11 +741,13 @@ export const pasp = (trackData: IsobmffVideoTrackData) => {
|
|||||||
|
|
||||||
/** Colour Information Box: Specifies the color space of the video. */
|
/** Colour Information Box: Specifies the color space of the video. */
|
||||||
export const colr = (trackData: IsobmffVideoTrackData) => box('colr', [
|
export const colr = (trackData: IsobmffVideoTrackData) => box('colr', [
|
||||||
ascii('nclx'), // Colour type
|
ascii(trackData.muxer.isQuickTime ? 'nclc' : 'nclx'), // Colour type
|
||||||
u16(COLOR_PRIMARIES_MAP[trackData.info.decoderConfig.colorSpace!.primaries!]), // Colour primaries
|
u16(COLOR_PRIMARIES_MAP[trackData.info.decoderConfig.colorSpace!.primaries!]), // Colour primaries
|
||||||
u16(TRANSFER_CHARACTERISTICS_MAP[trackData.info.decoderConfig.colorSpace!.transfer!]), // Transfer characteristics
|
u16(TRANSFER_CHARACTERISTICS_MAP[trackData.info.decoderConfig.colorSpace!.transfer!]), // Transfer characteristics
|
||||||
u16(MATRIX_COEFFICIENTS_MAP[trackData.info.decoderConfig.colorSpace!.matrix!]), // Matrix coefficients
|
u16(MATRIX_COEFFICIENTS_MAP[trackData.info.decoderConfig.colorSpace!.matrix!]), // Matrix coefficients
|
||||||
u8((trackData.info.decoderConfig.colorSpace!.fullRange ? 1 : 0) << 7), // Full range flag
|
trackData.muxer.isQuickTime
|
||||||
|
? [] // Doesn't have it
|
||||||
|
: u8((trackData.info.decoderConfig.colorSpace!.fullRange ? 1 : 0) << 7), // Full range flag
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/** AVC Configuration Box: Provides additional information to the decoder. */
|
/** AVC Configuration Box: Provides additional information to the decoder. */
|
||||||
|
|||||||
@@ -1448,20 +1448,24 @@ export class IsobmffDemuxer extends Demuxer {
|
|||||||
assert(track.info?.type === 'video');
|
assert(track.info?.type === 'video');
|
||||||
|
|
||||||
const colourType = readAscii(slice, 4);
|
const colourType = readAscii(slice, 4);
|
||||||
if (colourType !== 'nclx') {
|
if (colourType !== 'nclx' && colourType !== 'nclc') {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const colourPrimaries = readU16Be(slice);
|
const colourPrimaries = readU16Be(slice);
|
||||||
const transferCharacteristics = readU16Be(slice);
|
const transferCharacteristics = readU16Be(slice);
|
||||||
const matrixCoefficients = readU16Be(slice);
|
const matrixCoefficients = readU16Be(slice);
|
||||||
const fullRangeFlag = Boolean(readU8(slice) & 0x80);
|
|
||||||
|
let fullRange: boolean | undefined = undefined;
|
||||||
|
if (colourType === 'nclx') {
|
||||||
|
fullRange = Boolean(readU8(slice) & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
track.info.colorSpace = {
|
track.info.colorSpace = {
|
||||||
primaries: COLOR_PRIMARIES_MAP_INVERSE[colourPrimaries],
|
primaries: COLOR_PRIMARIES_MAP_INVERSE[colourPrimaries],
|
||||||
transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[transferCharacteristics],
|
transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[transferCharacteristics],
|
||||||
matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[matrixCoefficients],
|
matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[matrixCoefficients],
|
||||||
fullRange: fullRangeFlag,
|
fullRange,
|
||||||
} as VideoColorSpaceInit;
|
} as VideoColorSpaceInit;
|
||||||
}; break;
|
}; break;
|
||||||
|
|
||||||
|
|||||||
+108
-1
@@ -1,6 +1,19 @@
|
|||||||
import { expect, test } from 'vitest';
|
import { expect, test } from 'vitest';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { ALL_FORMATS, MP4, EncodedPacketSink, Input, FilePathSource } from '../../src/index.js';
|
import {
|
||||||
|
ALL_FORMATS,
|
||||||
|
BufferSource,
|
||||||
|
BufferTarget,
|
||||||
|
EncodedPacket,
|
||||||
|
EncodedPacketSink,
|
||||||
|
EncodedVideoPacketSource,
|
||||||
|
FilePathSource,
|
||||||
|
Input,
|
||||||
|
MP4,
|
||||||
|
MovOutputFormat,
|
||||||
|
Mp4OutputFormat,
|
||||||
|
Output,
|
||||||
|
} from '../../src/index.js';
|
||||||
|
|
||||||
const __dirname = new URL('.', import.meta.url).pathname;
|
const __dirname = new URL('.', import.meta.url).pathname;
|
||||||
|
|
||||||
@@ -33,3 +46,97 @@ test('Should be able to get packets from a .MP4 file', async () => {
|
|||||||
0, 0.16, 0.08, 0.04, 0.12, 0.32, 0.24, 0.2, 0.28, 0.48,
|
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);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user