@remotion/media-parser
Version:
A pure JavaScript library for parsing video files
301 lines (300 loc) • 11.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.getTrack = exports.getMatroskaAudioCodecEnum = exports.NO_CODEC_PRIVATE_SHOULD_BE_DERIVED_FROM_SPS = void 0;
const buffer_iterator_1 = require("../../iterator/buffer-iterator");
const make_hvc1_codec_strings_1 = require("../../make-hvc1-codec-strings");
const webcodecs_timescale_1 = require("../../webcodecs-timescale");
const color_to_webcodecs_colors_1 = require("../iso-base-media/color-to-webcodecs-colors");
const av1_codec_private_1 = require("./av1-codec-private");
const color_1 = require("./color");
const description_1 = require("./description");
const track_entry_1 = require("./segments/track-entry");
const traversal_1 = require("./traversal");
exports.NO_CODEC_PRIVATE_SHOULD_BE_DERIVED_FROM_SPS = 'no-codec-private-should-be-derived-from-sps';
const getDescription = (track) => {
const codec = (0, traversal_1.getCodecSegment)(track);
if (!codec) {
return undefined;
}
if (codec.value === 'V_MPEG4/ISO/AVC' || codec.value === 'V_MPEGH/ISO/HEVC') {
const priv = (0, traversal_1.getPrivateData)(track);
if (priv) {
return priv;
}
}
return undefined;
};
const getMatroskaVideoCodecEnum = ({ codecSegment: codec, }) => {
if (codec.value === 'V_VP8') {
return 'vp8';
}
if (codec.value === 'V_VP9') {
return 'vp9';
}
if (codec.value === 'V_MPEG4/ISO/AVC') {
return 'h264';
}
if (codec.value === 'V_AV1') {
return 'av1';
}
if (codec.value === 'V_MPEGH/ISO/HEVC') {
return 'h265';
}
throw new Error(`Unknown codec: ${codec.value}`);
};
const getMatroskaVideoCodecString = ({ track, codecSegment: codec, }) => {
if (codec.value === 'V_VP8') {
return 'vp8';
}
if (codec.value === 'V_VP9') {
const priv = (0, traversal_1.getPrivateData)(track);
if (priv) {
throw new Error('@remotion/media-parser cannot handle the private data for VP9. Do you have an example file you could send so we can implement it? https://remotion.dev/report');
}
return 'vp09.00.10.08';
}
if (codec.value === 'V_MPEG4/ISO/AVC') {
const priv = (0, traversal_1.getPrivateData)(track);
if (priv) {
return `avc1.${priv[1].toString(16).padStart(2, '0')}${priv[2].toString(16).padStart(2, '0')}${priv[3].toString(16).padStart(2, '0')}`;
}
return exports.NO_CODEC_PRIVATE_SHOULD_BE_DERIVED_FROM_SPS;
}
if (codec.value === 'V_MPEGH/ISO/HEVC') {
const priv = (0, traversal_1.getPrivateData)(track);
const iterator = (0, buffer_iterator_1.getArrayBufferIterator)(priv, priv.length);
return 'hvc1.' + (0, make_hvc1_codec_strings_1.getHvc1CodecString)(iterator);
}
if (codec.value === 'V_AV1') {
const priv = (0, traversal_1.getPrivateData)(track);
if (!priv) {
throw new Error('Expected private data in AV1 track');
}
return (0, av1_codec_private_1.parseAv1PrivateData)(priv, null);
}
throw new Error(`Unknown codec: ${codec.value}`);
};
const getMatroskaAudioCodecEnum = ({ track, }) => {
const codec = (0, traversal_1.getCodecSegment)(track);
if (!codec) {
throw new Error('Expected codec segment');
}
if (codec.value === 'A_OPUS') {
return 'opus';
}
if (codec.value === 'A_VORBIS') {
return 'vorbis';
}
if (codec.value === 'A_PCM/INT/LIT') {
// https://github.com/ietf-wg-cellar/matroska-specification/issues/142#issuecomment-330004950
// Audio samples MUST be considered as signed values, except if the audio bit depth is 8 which MUST be interpreted as unsigned values.
const bitDepth = (0, traversal_1.getBitDepth)(track);
if (bitDepth === null) {
throw new Error('Expected bit depth');
}
if (bitDepth === 8) {
return 'pcm-u8';
}
if (bitDepth === 16) {
return 'pcm-s16';
}
if (bitDepth === 24) {
return 'pcm-s24';
}
throw new Error('Unknown audio format');
}
if (codec.value === 'A_AAC') {
return `aac`;
}
if (codec.value === 'A_MPEG/L3') {
return 'mp3';
}
throw new Error(`Unknown codec: ${codec.value}`);
};
exports.getMatroskaAudioCodecEnum = getMatroskaAudioCodecEnum;
const getMatroskaAudioCodecString = (track) => {
const codec = (0, traversal_1.getCodecSegment)(track);
if (!codec) {
throw new Error('Expected codec segment');
}
if (codec.value === 'A_OPUS') {
return 'opus';
}
if (codec.value === 'A_VORBIS') {
return 'vorbis';
}
if (codec.value === 'A_PCM/INT/LIT') {
// https://github.com/ietf-wg-cellar/matroska-specification/issues/142#issuecomment-330004950
// Audio samples MUST be considered as signed values, except if the audio bit depth is 8 which MUST be interpreted as unsigned values.
const bitDepth = (0, traversal_1.getBitDepth)(track);
if (bitDepth === null) {
throw new Error('Expected bit depth');
}
if (bitDepth === 8) {
return 'pcm-u8';
}
return 'pcm-s' + bitDepth;
}
if (codec.value === 'A_AAC') {
const priv = (0, traversal_1.getPrivateData)(track);
const iterator = (0, buffer_iterator_1.getArrayBufferIterator)(priv, priv.length);
iterator.startReadingBits();
/**
* ChatGPT
* ▪ The first 5 bits represent the AOT.
▪ Common values:
◦ 1 for AAC Main
◦ 2 for AAC LC (Low Complexity)
◦ 3 for AAC SSR (Scalable Sample Rate)
◦ 4 for AAC LTP (Long Term Prediction)
◦ 5 for SBR (Spectral Band Replication)
◦ 29 for HE-AAC (which uses SBR with AAC LC)
*/
/**
* Fully qualified codec:
* This codec has multiple possible codec strings:
"mp4a.40.2" — MPEG-4 AAC LC
"mp4a.40.02" — MPEG-4 AAC LC, leading 0 for Aud-OTI compatibility
"mp4a.40.5" — MPEG-4 HE-AAC v1 (AAC LC + SBR)
"mp4a.40.05" — MPEG-4 HE-AAC v1 (AAC LC + SBR), leading 0 for Aud-OTI compatibility
"mp4a.40.29" — MPEG-4 HE-AAC v2 (AAC LC + SBR + PS)
"mp4a.67" — MPEG-2 AAC LC
*/
const profile = iterator.getBits(5);
iterator.stopReadingBits();
iterator.destroy();
return `mp4a.40.${profile.toString().padStart(2, '0')}`;
}
if (codec.value === 'A_MPEG/L3') {
return 'mp3';
}
throw new Error(`Unknown codec: ${codec.value}`);
};
const getTrack = ({ timescale, track, }) => {
const trackType = (0, traversal_1.getTrackTypeSegment)(track);
if (!trackType) {
throw new Error('Expected track type segment');
}
const trackId = (0, traversal_1.getTrackId)(track);
if ((0, track_entry_1.trackTypeToString)(trackType.value.value) === 'video') {
const width = (0, traversal_1.getWidthSegment)(track);
if (width === null) {
throw new Error('Expected width segment');
}
const height = (0, traversal_1.getHeightSegment)(track);
if (height === null) {
throw new Error('Expected height segment');
}
const displayHeight = (0, traversal_1.getDisplayHeightSegment)(track);
const displayWidth = (0, traversal_1.getDisplayWidthSegment)(track);
const codec = (0, traversal_1.getCodecSegment)(track);
if (!codec) {
return null;
}
const codecPrivate = (0, traversal_1.getPrivateData)(track);
const codecString = getMatroskaVideoCodecString({
track,
codecSegment: codec,
});
const colour = (0, traversal_1.getColourSegment)(track);
if (!codecString) {
return null;
}
const codecEnum = getMatroskaVideoCodecEnum({
codecSegment: codec,
});
const codecData = codecPrivate === null
? null
: codecEnum === 'h264'
? { type: 'avc-sps-pps', data: codecPrivate }
: codecEnum === 'av1'
? {
type: 'av1c-data',
data: codecPrivate,
}
: codecEnum === 'h265'
? {
type: 'hvcc-data',
data: codecPrivate,
}
: codecEnum === 'vp8'
? {
type: 'unknown-data',
data: codecPrivate,
}
: codecEnum === 'vp9'
? {
type: 'unknown-data',
data: codecPrivate,
}
: null;
const advancedColor = colour
? (0, color_1.parseColorSegment)(colour)
: {
fullRange: null,
matrix: null,
primaries: null,
transfer: null,
};
return {
m3uStreamFormat: null,
type: 'video',
trackId,
codec: codecString,
description: getDescription(track),
height: displayHeight ? displayHeight.value.value : height.value.value,
width: displayWidth ? displayWidth.value.value : width.value.value,
sampleAspectRatio: {
numerator: 1,
denominator: 1,
},
originalTimescale: timescale,
codedHeight: height.value.value,
codedWidth: width.value.value,
displayAspectHeight: displayHeight
? displayHeight.value.value
: height.value.value,
displayAspectWidth: displayWidth
? displayWidth.value.value
: width.value.value,
rotation: 0,
codecData,
colorSpace: (0, color_to_webcodecs_colors_1.mediaParserAdvancedColorToWebCodecsColor)(advancedColor),
advancedColor,
codecEnum,
fps: null,
startInSeconds: 0,
timescale: webcodecs_timescale_1.WEBCODECS_TIMESCALE,
};
}
if ((0, track_entry_1.trackTypeToString)(trackType.value.value) === 'audio') {
const sampleRate = (0, traversal_1.getSampleRate)(track);
const numberOfChannels = (0, traversal_1.getNumberOfChannels)(track);
const codecPrivate = (0, traversal_1.getPrivateData)(track);
if (sampleRate === null) {
throw new Error('Could not find sample rate or number of channels');
}
const codecString = getMatroskaAudioCodecString(track);
return {
type: 'audio',
trackId,
codec: codecString,
originalTimescale: timescale,
numberOfChannels,
sampleRate,
description: (0, description_1.getAudioDescription)(track),
codecData: codecPrivate
? codecString === 'opus'
? { type: 'ogg-identification', data: codecPrivate }
: { type: 'unknown-data', data: codecPrivate }
: null,
codecEnum: (0, exports.getMatroskaAudioCodecEnum)({
track,
}),
startInSeconds: 0,
timescale: webcodecs_timescale_1.WEBCODECS_TIMESCALE,
};
}
return null;
};
exports.getTrack = getTrack;