@kenzuya/mediabunny 1.26.0 → 1.28.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bundles/{mediabunny.mjs → mediabunny.js} +21963 -21388
- package/dist/bundles/mediabunny.min.js +490 -0
- package/dist/modules/shared/mp3-misc.d.ts.map +1 -1
- package/dist/modules/src/adts/adts-demuxer.d.ts +6 -6
- package/dist/modules/src/adts/adts-demuxer.d.ts.map +1 -1
- package/dist/modules/src/adts/adts-muxer.d.ts +4 -4
- package/dist/modules/src/adts/adts-muxer.d.ts.map +1 -1
- package/dist/modules/src/adts/adts-reader.d.ts +1 -1
- package/dist/modules/src/adts/adts-reader.d.ts.map +1 -1
- package/dist/modules/src/avi/avi-demuxer.d.ts +44 -0
- package/dist/modules/src/avi/avi-demuxer.d.ts.map +1 -0
- package/dist/modules/src/avi/avi-misc.d.ts +88 -0
- package/dist/modules/src/avi/avi-misc.d.ts.map +1 -0
- package/dist/modules/src/avi/avi-muxer.d.ts +45 -0
- package/dist/modules/src/avi/avi-muxer.d.ts.map +1 -0
- package/dist/modules/src/avi/riff-writer.d.ts +26 -0
- package/dist/modules/src/avi/riff-writer.d.ts.map +1 -0
- package/dist/modules/src/codec-data.d.ts +8 -3
- package/dist/modules/src/codec-data.d.ts.map +1 -1
- package/dist/modules/src/codec.d.ts +10 -10
- package/dist/modules/src/codec.d.ts.map +1 -1
- package/dist/modules/src/conversion.d.ts +33 -16
- package/dist/modules/src/conversion.d.ts.map +1 -1
- package/dist/modules/src/custom-coder.d.ts +8 -8
- package/dist/modules/src/custom-coder.d.ts.map +1 -1
- package/dist/modules/src/demuxer.d.ts +3 -3
- package/dist/modules/src/demuxer.d.ts.map +1 -1
- package/dist/modules/src/encode.d.ts +8 -8
- package/dist/modules/src/encode.d.ts.map +1 -1
- package/dist/modules/src/flac/flac-demuxer.d.ts +7 -7
- package/dist/modules/src/flac/flac-demuxer.d.ts.map +1 -1
- package/dist/modules/src/flac/flac-misc.d.ts +3 -3
- package/dist/modules/src/flac/flac-misc.d.ts.map +1 -1
- package/dist/modules/src/flac/flac-muxer.d.ts +5 -5
- package/dist/modules/src/flac/flac-muxer.d.ts.map +1 -1
- package/dist/modules/src/id3.d.ts +3 -3
- package/dist/modules/src/id3.d.ts.map +1 -1
- package/dist/modules/src/index.d.ts +20 -20
- package/dist/modules/src/index.d.ts.map +1 -1
- package/dist/modules/src/input-format.d.ts +22 -0
- package/dist/modules/src/input-format.d.ts.map +1 -1
- package/dist/modules/src/input-track.d.ts +8 -8
- package/dist/modules/src/input-track.d.ts.map +1 -1
- package/dist/modules/src/input.d.ts +12 -12
- package/dist/modules/src/isobmff/isobmff-boxes.d.ts +2 -2
- package/dist/modules/src/isobmff/isobmff-boxes.d.ts.map +1 -1
- package/dist/modules/src/isobmff/isobmff-demuxer.d.ts +12 -12
- package/dist/modules/src/isobmff/isobmff-demuxer.d.ts.map +1 -1
- package/dist/modules/src/isobmff/isobmff-misc.d.ts.map +1 -1
- package/dist/modules/src/isobmff/isobmff-muxer.d.ts +11 -11
- package/dist/modules/src/isobmff/isobmff-muxer.d.ts.map +1 -1
- package/dist/modules/src/isobmff/isobmff-reader.d.ts +2 -2
- package/dist/modules/src/isobmff/isobmff-reader.d.ts.map +1 -1
- package/dist/modules/src/matroska/ebml.d.ts +3 -3
- package/dist/modules/src/matroska/ebml.d.ts.map +1 -1
- package/dist/modules/src/matroska/matroska-demuxer.d.ts +13 -13
- package/dist/modules/src/matroska/matroska-demuxer.d.ts.map +1 -1
- package/dist/modules/src/matroska/matroska-input.d.ts +33 -0
- package/dist/modules/src/matroska/matroska-input.d.ts.map +1 -0
- package/dist/modules/src/matroska/matroska-misc.d.ts.map +1 -1
- package/dist/modules/src/matroska/matroska-muxer.d.ts +5 -5
- package/dist/modules/src/matroska/matroska-muxer.d.ts.map +1 -1
- package/dist/modules/src/media-sink.d.ts +5 -5
- package/dist/modules/src/media-sink.d.ts.map +1 -1
- package/dist/modules/src/media-source.d.ts +22 -4
- package/dist/modules/src/media-source.d.ts.map +1 -1
- package/dist/modules/src/metadata.d.ts +2 -2
- package/dist/modules/src/metadata.d.ts.map +1 -1
- package/dist/modules/src/misc.d.ts +5 -4
- package/dist/modules/src/misc.d.ts.map +1 -1
- package/dist/modules/src/mp3/mp3-demuxer.d.ts +7 -7
- package/dist/modules/src/mp3/mp3-demuxer.d.ts.map +1 -1
- package/dist/modules/src/mp3/mp3-muxer.d.ts +4 -4
- package/dist/modules/src/mp3/mp3-muxer.d.ts.map +1 -1
- package/dist/modules/src/mp3/mp3-reader.d.ts +2 -2
- package/dist/modules/src/mp3/mp3-reader.d.ts.map +1 -1
- package/dist/modules/src/mp3/mp3-writer.d.ts +1 -1
- package/dist/modules/src/mp3/mp3-writer.d.ts.map +1 -1
- package/dist/modules/src/muxer.d.ts +4 -4
- package/dist/modules/src/muxer.d.ts.map +1 -1
- package/dist/modules/src/node.d.ts +1 -1
- package/dist/modules/src/ogg/ogg-demuxer.d.ts +7 -7
- package/dist/modules/src/ogg/ogg-demuxer.d.ts.map +1 -1
- package/dist/modules/src/ogg/ogg-misc.d.ts +1 -1
- package/dist/modules/src/ogg/ogg-misc.d.ts.map +1 -1
- package/dist/modules/src/ogg/ogg-muxer.d.ts +5 -5
- package/dist/modules/src/ogg/ogg-muxer.d.ts.map +1 -1
- package/dist/modules/src/ogg/ogg-reader.d.ts +1 -1
- package/dist/modules/src/ogg/ogg-reader.d.ts.map +1 -1
- package/dist/modules/src/output-format.d.ts +51 -6
- package/dist/modules/src/output-format.d.ts.map +1 -1
- package/dist/modules/src/output.d.ts +13 -13
- package/dist/modules/src/output.d.ts.map +1 -1
- package/dist/modules/src/packet.d.ts +1 -1
- package/dist/modules/src/packet.d.ts.map +1 -1
- package/dist/modules/src/pcm.d.ts.map +1 -1
- package/dist/modules/src/reader.d.ts +2 -2
- package/dist/modules/src/reader.d.ts.map +1 -1
- package/dist/modules/src/sample.d.ts +57 -15
- package/dist/modules/src/sample.d.ts.map +1 -1
- package/dist/modules/src/source.d.ts +3 -3
- package/dist/modules/src/source.d.ts.map +1 -1
- package/dist/modules/src/subtitles.d.ts +1 -1
- package/dist/modules/src/subtitles.d.ts.map +1 -1
- package/dist/modules/src/target.d.ts +2 -2
- package/dist/modules/src/target.d.ts.map +1 -1
- package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
- package/dist/modules/src/wave/riff-writer.d.ts +1 -1
- package/dist/modules/src/wave/riff-writer.d.ts.map +1 -1
- package/dist/modules/src/wave/wave-demuxer.d.ts +6 -6
- package/dist/modules/src/wave/wave-demuxer.d.ts.map +1 -1
- package/dist/modules/src/wave/wave-muxer.d.ts +4 -4
- package/dist/modules/src/wave/wave-muxer.d.ts.map +1 -1
- package/dist/modules/src/writer.d.ts +1 -1
- package/dist/modules/src/writer.d.ts.map +1 -1
- package/dist/packages/eac3/eac3.wasm +0 -0
- package/dist/packages/eac3/mediabunny-eac3.js +1058 -0
- package/dist/packages/eac3/mediabunny-eac3.min.js +44 -0
- package/dist/packages/mp3-encoder/mediabunny-mp3-encoder.js +694 -0
- package/dist/packages/mp3-encoder/mediabunny-mp3-encoder.min.js +58 -0
- package/dist/packages/mpeg4/mediabunny-mpeg4.js +1198 -0
- package/dist/packages/mpeg4/mediabunny-mpeg4.min.js +44 -0
- package/dist/packages/mpeg4/xvid.wasm +0 -0
- package/package.json +18 -57
- package/dist/bundles/mediabunny.cjs +0 -26140
- package/dist/bundles/mediabunny.min.cjs +0 -147
- package/dist/bundles/mediabunny.min.mjs +0 -146
- package/dist/mediabunny.d.ts +0 -3319
- package/dist/modules/shared/mp3-misc.js +0 -147
- package/dist/modules/src/adts/adts-demuxer.js +0 -239
- package/dist/modules/src/adts/adts-muxer.js +0 -80
- package/dist/modules/src/adts/adts-reader.js +0 -63
- package/dist/modules/src/codec-data.js +0 -1730
- package/dist/modules/src/codec.js +0 -869
- package/dist/modules/src/conversion.js +0 -1459
- package/dist/modules/src/custom-coder.js +0 -117
- package/dist/modules/src/demuxer.js +0 -12
- package/dist/modules/src/encode.js +0 -442
- package/dist/modules/src/flac/flac-demuxer.js +0 -504
- package/dist/modules/src/flac/flac-misc.js +0 -135
- package/dist/modules/src/flac/flac-muxer.js +0 -222
- package/dist/modules/src/id3.js +0 -848
- package/dist/modules/src/index.js +0 -28
- package/dist/modules/src/input-format.js +0 -480
- package/dist/modules/src/input-track.js +0 -372
- package/dist/modules/src/input.js +0 -188
- package/dist/modules/src/isobmff/isobmff-boxes.js +0 -1480
- package/dist/modules/src/isobmff/isobmff-demuxer.js +0 -2618
- package/dist/modules/src/isobmff/isobmff-misc.js +0 -20
- package/dist/modules/src/isobmff/isobmff-muxer.js +0 -966
- package/dist/modules/src/isobmff/isobmff-reader.js +0 -72
- package/dist/modules/src/matroska/ebml.js +0 -653
- package/dist/modules/src/matroska/matroska-demuxer.js +0 -2133
- package/dist/modules/src/matroska/matroska-misc.js +0 -20
- package/dist/modules/src/matroska/matroska-muxer.js +0 -1017
- package/dist/modules/src/media-sink.js +0 -1736
- package/dist/modules/src/media-source.js +0 -1825
- package/dist/modules/src/metadata.js +0 -193
- package/dist/modules/src/misc.js +0 -623
- package/dist/modules/src/mp3/mp3-demuxer.js +0 -285
- package/dist/modules/src/mp3/mp3-muxer.js +0 -123
- package/dist/modules/src/mp3/mp3-reader.js +0 -26
- package/dist/modules/src/mp3/mp3-writer.js +0 -78
- package/dist/modules/src/muxer.js +0 -50
- package/dist/modules/src/node.js +0 -9
- package/dist/modules/src/ogg/ogg-demuxer.js +0 -763
- package/dist/modules/src/ogg/ogg-misc.js +0 -78
- package/dist/modules/src/ogg/ogg-muxer.js +0 -353
- package/dist/modules/src/ogg/ogg-reader.js +0 -65
- package/dist/modules/src/output-format.js +0 -527
- package/dist/modules/src/output.js +0 -300
- package/dist/modules/src/packet.js +0 -182
- package/dist/modules/src/pcm.js +0 -85
- package/dist/modules/src/reader.js +0 -236
- package/dist/modules/src/sample.js +0 -1056
- package/dist/modules/src/source.js +0 -1182
- package/dist/modules/src/subtitles.js +0 -575
- package/dist/modules/src/target.js +0 -140
- package/dist/modules/src/wave/riff-writer.js +0 -30
- package/dist/modules/src/wave/wave-demuxer.js +0 -447
- package/dist/modules/src/wave/wave-muxer.js +0 -318
- package/dist/modules/src/writer.js +0 -370
- package/src/adts/adts-demuxer.ts +0 -331
- package/src/adts/adts-muxer.ts +0 -111
- package/src/adts/adts-reader.ts +0 -85
- package/src/codec-data.ts +0 -2078
- package/src/codec.ts +0 -1092
- package/src/conversion.ts +0 -2112
- package/src/custom-coder.ts +0 -197
- package/src/demuxer.ts +0 -24
- package/src/encode.ts +0 -739
- package/src/flac/flac-demuxer.ts +0 -730
- package/src/flac/flac-misc.ts +0 -164
- package/src/flac/flac-muxer.ts +0 -320
- package/src/id3.ts +0 -925
- package/src/index.ts +0 -221
- package/src/input-format.ts +0 -541
- package/src/input-track.ts +0 -529
- package/src/input.ts +0 -235
- package/src/isobmff/isobmff-boxes.ts +0 -1719
- package/src/isobmff/isobmff-demuxer.ts +0 -3190
- package/src/isobmff/isobmff-misc.ts +0 -29
- package/src/isobmff/isobmff-muxer.ts +0 -1348
- package/src/isobmff/isobmff-reader.ts +0 -91
- package/src/matroska/ebml.ts +0 -730
- package/src/matroska/matroska-demuxer.ts +0 -2481
- package/src/matroska/matroska-misc.ts +0 -29
- package/src/matroska/matroska-muxer.ts +0 -1276
- package/src/media-sink.ts +0 -2179
- package/src/media-source.ts +0 -2243
- package/src/metadata.ts +0 -320
- package/src/misc.ts +0 -798
- package/src/mp3/mp3-demuxer.ts +0 -383
- package/src/mp3/mp3-muxer.ts +0 -166
- package/src/mp3/mp3-reader.ts +0 -34
- package/src/mp3/mp3-writer.ts +0 -120
- package/src/muxer.ts +0 -88
- package/src/node.ts +0 -11
- package/src/ogg/ogg-demuxer.ts +0 -1053
- package/src/ogg/ogg-misc.ts +0 -116
- package/src/ogg/ogg-muxer.ts +0 -497
- package/src/ogg/ogg-reader.ts +0 -93
- package/src/output-format.ts +0 -945
- package/src/output.ts +0 -488
- package/src/packet.ts +0 -263
- package/src/pcm.ts +0 -112
- package/src/reader.ts +0 -323
- package/src/sample.ts +0 -1461
- package/src/source.ts +0 -1688
- package/src/subtitles.ts +0 -711
- package/src/target.ts +0 -204
- package/src/tsconfig.json +0 -16
- package/src/wave/riff-writer.ts +0 -36
- package/src/wave/wave-demuxer.ts +0 -529
- package/src/wave/wave-muxer.ts +0 -371
- package/src/writer.ts +0 -490
|
@@ -1,869 +0,0 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) 2025-present, Vanilagy and contributors
|
|
3
|
-
*
|
|
4
|
-
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
-
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
-
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
-
*/
|
|
8
|
-
import { Bitstream, COLOR_PRIMARIES_MAP, MATRIX_COEFFICIENTS_MAP, TRANSFER_CHARACTERISTICS_MAP, assert, bytesToHexString, isAllowSharedBufferSource, last, reverseBitsU32, toDataView, } from './misc.js';
|
|
9
|
-
/**
|
|
10
|
-
* List of known video codecs, ordered by encoding preference.
|
|
11
|
-
* @group Codecs
|
|
12
|
-
* @public
|
|
13
|
-
*/
|
|
14
|
-
export const VIDEO_CODECS = [
|
|
15
|
-
'avc',
|
|
16
|
-
'hevc',
|
|
17
|
-
'vp9',
|
|
18
|
-
'av1',
|
|
19
|
-
'vp8',
|
|
20
|
-
];
|
|
21
|
-
/**
|
|
22
|
-
* List of known PCM (uncompressed) audio codecs, ordered by encoding preference.
|
|
23
|
-
* @group Codecs
|
|
24
|
-
* @public
|
|
25
|
-
*/
|
|
26
|
-
export const PCM_AUDIO_CODECS = [
|
|
27
|
-
'pcm-s16', // We don't prefix 'le' so we're compatible with the WebCodecs-registered PCM codec strings
|
|
28
|
-
'pcm-s16be',
|
|
29
|
-
'pcm-s24',
|
|
30
|
-
'pcm-s24be',
|
|
31
|
-
'pcm-s32',
|
|
32
|
-
'pcm-s32be',
|
|
33
|
-
'pcm-f32',
|
|
34
|
-
'pcm-f32be',
|
|
35
|
-
'pcm-f64',
|
|
36
|
-
'pcm-f64be',
|
|
37
|
-
'pcm-u8',
|
|
38
|
-
'pcm-s8',
|
|
39
|
-
'ulaw',
|
|
40
|
-
'alaw',
|
|
41
|
-
];
|
|
42
|
-
/**
|
|
43
|
-
* List of known compressed audio codecs, ordered by encoding preference.
|
|
44
|
-
* @group Codecs
|
|
45
|
-
* @public
|
|
46
|
-
*/
|
|
47
|
-
export const NON_PCM_AUDIO_CODECS = [
|
|
48
|
-
'aac',
|
|
49
|
-
'opus',
|
|
50
|
-
'mp3',
|
|
51
|
-
'vorbis',
|
|
52
|
-
'flac',
|
|
53
|
-
];
|
|
54
|
-
/**
|
|
55
|
-
* List of known audio codecs, ordered by encoding preference.
|
|
56
|
-
* @group Codecs
|
|
57
|
-
* @public
|
|
58
|
-
*/
|
|
59
|
-
export const AUDIO_CODECS = [
|
|
60
|
-
...NON_PCM_AUDIO_CODECS,
|
|
61
|
-
...PCM_AUDIO_CODECS,
|
|
62
|
-
];
|
|
63
|
-
/**
|
|
64
|
-
* List of known subtitle codecs, ordered by encoding preference.
|
|
65
|
-
* @group Codecs
|
|
66
|
-
* @public
|
|
67
|
-
*/
|
|
68
|
-
export const SUBTITLE_CODECS = [
|
|
69
|
-
'webvtt',
|
|
70
|
-
'tx3g',
|
|
71
|
-
'ttml',
|
|
72
|
-
'srt',
|
|
73
|
-
'ass',
|
|
74
|
-
'ssa',
|
|
75
|
-
];
|
|
76
|
-
// https://en.wikipedia.org/wiki/Advanced_Video_Coding
|
|
77
|
-
const AVC_LEVEL_TABLE = [
|
|
78
|
-
{ maxMacroblocks: 99, maxBitrate: 64000, level: 0x0A }, // Level 1
|
|
79
|
-
{ maxMacroblocks: 396, maxBitrate: 192000, level: 0x0B }, // Level 1.1
|
|
80
|
-
{ maxMacroblocks: 396, maxBitrate: 384000, level: 0x0C }, // Level 1.2
|
|
81
|
-
{ maxMacroblocks: 396, maxBitrate: 768000, level: 0x0D }, // Level 1.3
|
|
82
|
-
{ maxMacroblocks: 396, maxBitrate: 2000000, level: 0x14 }, // Level 2
|
|
83
|
-
{ maxMacroblocks: 792, maxBitrate: 4000000, level: 0x15 }, // Level 2.1
|
|
84
|
-
{ maxMacroblocks: 1620, maxBitrate: 4000000, level: 0x16 }, // Level 2.2
|
|
85
|
-
{ maxMacroblocks: 1620, maxBitrate: 10000000, level: 0x1E }, // Level 3
|
|
86
|
-
{ maxMacroblocks: 3600, maxBitrate: 14000000, level: 0x1F }, // Level 3.1
|
|
87
|
-
{ maxMacroblocks: 5120, maxBitrate: 20000000, level: 0x20 }, // Level 3.2
|
|
88
|
-
{ maxMacroblocks: 8192, maxBitrate: 20000000, level: 0x28 }, // Level 4
|
|
89
|
-
{ maxMacroblocks: 8192, maxBitrate: 50000000, level: 0x29 }, // Level 4.1
|
|
90
|
-
{ maxMacroblocks: 8704, maxBitrate: 50000000, level: 0x2A }, // Level 4.2
|
|
91
|
-
{ maxMacroblocks: 22080, maxBitrate: 135000000, level: 0x32 }, // Level 5
|
|
92
|
-
{ maxMacroblocks: 36864, maxBitrate: 240000000, level: 0x33 }, // Level 5.1
|
|
93
|
-
{ maxMacroblocks: 36864, maxBitrate: 240000000, level: 0x34 }, // Level 5.2
|
|
94
|
-
{ maxMacroblocks: 139264, maxBitrate: 240000000, level: 0x3C }, // Level 6
|
|
95
|
-
{ maxMacroblocks: 139264, maxBitrate: 480000000, level: 0x3D }, // Level 6.1
|
|
96
|
-
{ maxMacroblocks: 139264, maxBitrate: 800000000, level: 0x3E }, // Level 6.2
|
|
97
|
-
];
|
|
98
|
-
// https://en.wikipedia.org/wiki/High_Efficiency_Video_Coding
|
|
99
|
-
const HEVC_LEVEL_TABLE = [
|
|
100
|
-
{ maxPictureSize: 36864, maxBitrate: 128000, tier: 'L', level: 30 }, // Level 1 (Low Tier)
|
|
101
|
-
{ maxPictureSize: 122880, maxBitrate: 1500000, tier: 'L', level: 60 }, // Level 2 (Low Tier)
|
|
102
|
-
{ maxPictureSize: 245760, maxBitrate: 3000000, tier: 'L', level: 63 }, // Level 2.1 (Low Tier)
|
|
103
|
-
{ maxPictureSize: 552960, maxBitrate: 6000000, tier: 'L', level: 90 }, // Level 3 (Low Tier)
|
|
104
|
-
{ maxPictureSize: 983040, maxBitrate: 10000000, tier: 'L', level: 93 }, // Level 3.1 (Low Tier)
|
|
105
|
-
{ maxPictureSize: 2228224, maxBitrate: 12000000, tier: 'L', level: 120 }, // Level 4 (Low Tier)
|
|
106
|
-
{ maxPictureSize: 2228224, maxBitrate: 30000000, tier: 'H', level: 120 }, // Level 4 (High Tier)
|
|
107
|
-
{ maxPictureSize: 2228224, maxBitrate: 20000000, tier: 'L', level: 123 }, // Level 4.1 (Low Tier)
|
|
108
|
-
{ maxPictureSize: 2228224, maxBitrate: 50000000, tier: 'H', level: 123 }, // Level 4.1 (High Tier)
|
|
109
|
-
{ maxPictureSize: 8912896, maxBitrate: 25000000, tier: 'L', level: 150 }, // Level 5 (Low Tier)
|
|
110
|
-
{ maxPictureSize: 8912896, maxBitrate: 100000000, tier: 'H', level: 150 }, // Level 5 (High Tier)
|
|
111
|
-
{ maxPictureSize: 8912896, maxBitrate: 40000000, tier: 'L', level: 153 }, // Level 5.1 (Low Tier)
|
|
112
|
-
{ maxPictureSize: 8912896, maxBitrate: 160000000, tier: 'H', level: 153 }, // Level 5.1 (High Tier)
|
|
113
|
-
{ maxPictureSize: 8912896, maxBitrate: 60000000, tier: 'L', level: 156 }, // Level 5.2 (Low Tier)
|
|
114
|
-
{ maxPictureSize: 8912896, maxBitrate: 240000000, tier: 'H', level: 156 }, // Level 5.2 (High Tier)
|
|
115
|
-
{ maxPictureSize: 35651584, maxBitrate: 60000000, tier: 'L', level: 180 }, // Level 6 (Low Tier)
|
|
116
|
-
{ maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'H', level: 180 }, // Level 6 (High Tier)
|
|
117
|
-
{ maxPictureSize: 35651584, maxBitrate: 120000000, tier: 'L', level: 183 }, // Level 6.1 (Low Tier)
|
|
118
|
-
{ maxPictureSize: 35651584, maxBitrate: 480000000, tier: 'H', level: 183 }, // Level 6.1 (High Tier)
|
|
119
|
-
{ maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'L', level: 186 }, // Level 6.2 (Low Tier)
|
|
120
|
-
{ maxPictureSize: 35651584, maxBitrate: 800000000, tier: 'H', level: 186 }, // Level 6.2 (High Tier)
|
|
121
|
-
];
|
|
122
|
-
// https://en.wikipedia.org/wiki/VP9
|
|
123
|
-
export const VP9_LEVEL_TABLE = [
|
|
124
|
-
{ maxPictureSize: 36864, maxBitrate: 200000, level: 10 }, // Level 1
|
|
125
|
-
{ maxPictureSize: 73728, maxBitrate: 800000, level: 11 }, // Level 1.1
|
|
126
|
-
{ maxPictureSize: 122880, maxBitrate: 1800000, level: 20 }, // Level 2
|
|
127
|
-
{ maxPictureSize: 245760, maxBitrate: 3600000, level: 21 }, // Level 2.1
|
|
128
|
-
{ maxPictureSize: 552960, maxBitrate: 7200000, level: 30 }, // Level 3
|
|
129
|
-
{ maxPictureSize: 983040, maxBitrate: 12000000, level: 31 }, // Level 3.1
|
|
130
|
-
{ maxPictureSize: 2228224, maxBitrate: 18000000, level: 40 }, // Level 4
|
|
131
|
-
{ maxPictureSize: 2228224, maxBitrate: 30000000, level: 41 }, // Level 4.1
|
|
132
|
-
{ maxPictureSize: 8912896, maxBitrate: 60000000, level: 50 }, // Level 5
|
|
133
|
-
{ maxPictureSize: 8912896, maxBitrate: 120000000, level: 51 }, // Level 5.1
|
|
134
|
-
{ maxPictureSize: 8912896, maxBitrate: 180000000, level: 52 }, // Level 5.2
|
|
135
|
-
{ maxPictureSize: 35651584, maxBitrate: 180000000, level: 60 }, // Level 6
|
|
136
|
-
{ maxPictureSize: 35651584, maxBitrate: 240000000, level: 61 }, // Level 6.1
|
|
137
|
-
{ maxPictureSize: 35651584, maxBitrate: 480000000, level: 62 }, // Level 6.2
|
|
138
|
-
];
|
|
139
|
-
// https://en.wikipedia.org/wiki/AV1
|
|
140
|
-
const AV1_LEVEL_TABLE = [
|
|
141
|
-
{ maxPictureSize: 147456, maxBitrate: 1500000, tier: 'M', level: 0 }, // Level 2.0 (Main Tier)
|
|
142
|
-
{ maxPictureSize: 278784, maxBitrate: 3000000, tier: 'M', level: 1 }, // Level 2.1 (Main Tier)
|
|
143
|
-
{ maxPictureSize: 665856, maxBitrate: 6000000, tier: 'M', level: 4 }, // Level 3.0 (Main Tier)
|
|
144
|
-
{ maxPictureSize: 1065024, maxBitrate: 10000000, tier: 'M', level: 5 }, // Level 3.1 (Main Tier)
|
|
145
|
-
{ maxPictureSize: 2359296, maxBitrate: 12000000, tier: 'M', level: 8 }, // Level 4.0 (Main Tier)
|
|
146
|
-
{ maxPictureSize: 2359296, maxBitrate: 30000000, tier: 'H', level: 8 }, // Level 4.0 (High Tier)
|
|
147
|
-
{ maxPictureSize: 2359296, maxBitrate: 20000000, tier: 'M', level: 9 }, // Level 4.1 (Main Tier)
|
|
148
|
-
{ maxPictureSize: 2359296, maxBitrate: 50000000, tier: 'H', level: 9 }, // Level 4.1 (High Tier)
|
|
149
|
-
{ maxPictureSize: 8912896, maxBitrate: 30000000, tier: 'M', level: 12 }, // Level 5.0 (Main Tier)
|
|
150
|
-
{ maxPictureSize: 8912896, maxBitrate: 100000000, tier: 'H', level: 12 }, // Level 5.0 (High Tier)
|
|
151
|
-
{ maxPictureSize: 8912896, maxBitrate: 40000000, tier: 'M', level: 13 }, // Level 5.1 (Main Tier)
|
|
152
|
-
{ maxPictureSize: 8912896, maxBitrate: 160000000, tier: 'H', level: 13 }, // Level 5.1 (High Tier)
|
|
153
|
-
{ maxPictureSize: 8912896, maxBitrate: 60000000, tier: 'M', level: 14 }, // Level 5.2 (Main Tier)
|
|
154
|
-
{ maxPictureSize: 8912896, maxBitrate: 240000000, tier: 'H', level: 14 }, // Level 5.2 (High Tier)
|
|
155
|
-
{ maxPictureSize: 35651584, maxBitrate: 60000000, tier: 'M', level: 15 }, // Level 5.3 (Main Tier)
|
|
156
|
-
{ maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'H', level: 15 }, // Level 5.3 (High Tier)
|
|
157
|
-
{ maxPictureSize: 35651584, maxBitrate: 60000000, tier: 'M', level: 16 }, // Level 6.0 (Main Tier)
|
|
158
|
-
{ maxPictureSize: 35651584, maxBitrate: 240000000, tier: 'H', level: 16 }, // Level 6.0 (High Tier)
|
|
159
|
-
{ maxPictureSize: 35651584, maxBitrate: 100000000, tier: 'M', level: 17 }, // Level 6.1 (Main Tier)
|
|
160
|
-
{ maxPictureSize: 35651584, maxBitrate: 480000000, tier: 'H', level: 17 }, // Level 6.1 (High Tier)
|
|
161
|
-
{ maxPictureSize: 35651584, maxBitrate: 160000000, tier: 'M', level: 18 }, // Level 6.2 (Main Tier)
|
|
162
|
-
{ maxPictureSize: 35651584, maxBitrate: 800000000, tier: 'H', level: 18 }, // Level 6.2 (High Tier)
|
|
163
|
-
{ maxPictureSize: 35651584, maxBitrate: 160000000, tier: 'M', level: 19 }, // Level 6.3 (Main Tier)
|
|
164
|
-
{ maxPictureSize: 35651584, maxBitrate: 800000000, tier: 'H', level: 19 }, // Level 6.3 (High Tier)
|
|
165
|
-
];
|
|
166
|
-
const VP9_DEFAULT_SUFFIX = '.01.01.01.01.00';
|
|
167
|
-
const AV1_DEFAULT_SUFFIX = '.0.110.01.01.01.0';
|
|
168
|
-
export const buildVideoCodecString = (codec, width, height, bitrate) => {
|
|
169
|
-
if (codec === 'avc') {
|
|
170
|
-
const profileIndication = 0x64; // High Profile
|
|
171
|
-
const totalMacroblocks = Math.ceil(width / 16) * Math.ceil(height / 16);
|
|
172
|
-
// Determine the level based on the table
|
|
173
|
-
const levelInfo = AVC_LEVEL_TABLE.find(level => totalMacroblocks <= level.maxMacroblocks && bitrate <= level.maxBitrate) ?? last(AVC_LEVEL_TABLE);
|
|
174
|
-
const levelIndication = levelInfo ? levelInfo.level : 0;
|
|
175
|
-
const hexProfileIndication = profileIndication.toString(16).padStart(2, '0');
|
|
176
|
-
const hexProfileCompatibility = '00';
|
|
177
|
-
const hexLevelIndication = levelIndication.toString(16).padStart(2, '0');
|
|
178
|
-
return `avc1.${hexProfileIndication}${hexProfileCompatibility}${hexLevelIndication}`;
|
|
179
|
-
}
|
|
180
|
-
else if (codec === 'hevc') {
|
|
181
|
-
const profilePrefix = ''; // Profile space 0
|
|
182
|
-
const profileIdc = 1; // Main Profile
|
|
183
|
-
const compatibilityFlags = '6'; // Taken from the example in ISO 14496-15
|
|
184
|
-
const pictureSize = width * height;
|
|
185
|
-
const levelInfo = HEVC_LEVEL_TABLE.find(level => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate) ?? last(HEVC_LEVEL_TABLE);
|
|
186
|
-
const constraintFlags = 'B0'; // Progressive source flag
|
|
187
|
-
return 'hev1.'
|
|
188
|
-
+ `${profilePrefix}${profileIdc}.`
|
|
189
|
-
+ `${compatibilityFlags}.`
|
|
190
|
-
+ `${levelInfo.tier}${levelInfo.level}.`
|
|
191
|
-
+ `${constraintFlags}`;
|
|
192
|
-
}
|
|
193
|
-
else if (codec === 'vp8') {
|
|
194
|
-
return 'vp8'; // Easy, this one
|
|
195
|
-
}
|
|
196
|
-
else if (codec === 'vp9') {
|
|
197
|
-
const profile = '00'; // Profile 0
|
|
198
|
-
const pictureSize = width * height;
|
|
199
|
-
const levelInfo = VP9_LEVEL_TABLE.find(level => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate) ?? last(VP9_LEVEL_TABLE);
|
|
200
|
-
const bitDepth = '08'; // 8-bit
|
|
201
|
-
return `vp09.${profile}.${levelInfo.level.toString().padStart(2, '0')}.${bitDepth}`;
|
|
202
|
-
}
|
|
203
|
-
else if (codec === 'av1') {
|
|
204
|
-
const profile = 0; // Main Profile, single digit
|
|
205
|
-
const pictureSize = width * height;
|
|
206
|
-
const levelInfo = AV1_LEVEL_TABLE.find(level => pictureSize <= level.maxPictureSize && bitrate <= level.maxBitrate) ?? last(AV1_LEVEL_TABLE);
|
|
207
|
-
const level = levelInfo.level.toString().padStart(2, '0');
|
|
208
|
-
const bitDepth = '08'; // 8-bit
|
|
209
|
-
return `av01.${profile}.${level}${levelInfo.tier}.${bitDepth}`;
|
|
210
|
-
}
|
|
211
|
-
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
212
|
-
throw new TypeError(`Unhandled codec '${codec}'.`);
|
|
213
|
-
};
|
|
214
|
-
export const generateVp9CodecConfigurationFromCodecString = (codecString) => {
|
|
215
|
-
// Reference: https://www.webmproject.org/docs/container/#vp9-codec-feature-metadata-codecprivate
|
|
216
|
-
const parts = codecString.split('.'); // We can derive the required values from the codec string
|
|
217
|
-
const profile = Number(parts[1]);
|
|
218
|
-
const level = Number(parts[2]);
|
|
219
|
-
const bitDepth = Number(parts[3]);
|
|
220
|
-
const chromaSubsampling = parts[4] ? Number(parts[4]) : 1;
|
|
221
|
-
return [
|
|
222
|
-
1, 1, profile,
|
|
223
|
-
2, 1, level,
|
|
224
|
-
3, 1, bitDepth,
|
|
225
|
-
4, 1, chromaSubsampling,
|
|
226
|
-
];
|
|
227
|
-
};
|
|
228
|
-
export const generateAv1CodecConfigurationFromCodecString = (codecString) => {
|
|
229
|
-
// Reference: https://aomediacodec.github.io/av1-isobmff/
|
|
230
|
-
const parts = codecString.split('.'); // We can derive the required values from the codec string
|
|
231
|
-
const marker = 1;
|
|
232
|
-
const version = 1;
|
|
233
|
-
const firstByte = (marker << 7) + version;
|
|
234
|
-
const profile = Number(parts[1]);
|
|
235
|
-
const levelAndTier = parts[2];
|
|
236
|
-
const level = Number(levelAndTier.slice(0, -1));
|
|
237
|
-
const secondByte = (profile << 5) + level;
|
|
238
|
-
const tier = levelAndTier.slice(-1) === 'H' ? 1 : 0;
|
|
239
|
-
const bitDepth = Number(parts[3]);
|
|
240
|
-
const highBitDepth = bitDepth === 8 ? 0 : 1;
|
|
241
|
-
const twelveBit = 0;
|
|
242
|
-
const monochrome = parts[4] ? Number(parts[4]) : 0;
|
|
243
|
-
const chromaSubsamplingX = parts[5] ? Number(parts[5][0]) : 1;
|
|
244
|
-
const chromaSubsamplingY = parts[5] ? Number(parts[5][1]) : 1;
|
|
245
|
-
const chromaSamplePosition = parts[5] ? Number(parts[5][2]) : 0; // CSP_UNKNOWN
|
|
246
|
-
const thirdByte = (tier << 7)
|
|
247
|
-
+ (highBitDepth << 6)
|
|
248
|
-
+ (twelveBit << 5)
|
|
249
|
-
+ (monochrome << 4)
|
|
250
|
-
+ (chromaSubsamplingX << 3)
|
|
251
|
-
+ (chromaSubsamplingY << 2)
|
|
252
|
-
+ chromaSamplePosition;
|
|
253
|
-
const initialPresentationDelayPresent = 0; // Should be fine
|
|
254
|
-
const fourthByte = initialPresentationDelayPresent;
|
|
255
|
-
return [firstByte, secondByte, thirdByte, fourthByte];
|
|
256
|
-
};
|
|
257
|
-
export const extractVideoCodecString = (trackInfo) => {
|
|
258
|
-
const { codec, codecDescription, colorSpace, avcCodecInfo, hevcCodecInfo, vp9CodecInfo, av1CodecInfo } = trackInfo;
|
|
259
|
-
if (codec === 'avc') {
|
|
260
|
-
assert(trackInfo.avcType !== null);
|
|
261
|
-
if (avcCodecInfo) {
|
|
262
|
-
const bytes = new Uint8Array([
|
|
263
|
-
avcCodecInfo.avcProfileIndication,
|
|
264
|
-
avcCodecInfo.profileCompatibility,
|
|
265
|
-
avcCodecInfo.avcLevelIndication,
|
|
266
|
-
]);
|
|
267
|
-
return `avc${trackInfo.avcType}.${bytesToHexString(bytes)}`;
|
|
268
|
-
}
|
|
269
|
-
if (!codecDescription || codecDescription.byteLength < 4) {
|
|
270
|
-
throw new TypeError('AVC decoder description is not provided or is not at least 4 bytes long.');
|
|
271
|
-
}
|
|
272
|
-
return `avc${trackInfo.avcType}.${bytesToHexString(codecDescription.subarray(1, 4))}`;
|
|
273
|
-
}
|
|
274
|
-
else if (codec === 'hevc') {
|
|
275
|
-
let generalProfileSpace;
|
|
276
|
-
let generalProfileIdc;
|
|
277
|
-
let compatibilityFlags;
|
|
278
|
-
let generalTierFlag;
|
|
279
|
-
let generalLevelIdc;
|
|
280
|
-
let constraintFlags;
|
|
281
|
-
if (hevcCodecInfo) {
|
|
282
|
-
generalProfileSpace = hevcCodecInfo.generalProfileSpace;
|
|
283
|
-
generalProfileIdc = hevcCodecInfo.generalProfileIdc;
|
|
284
|
-
compatibilityFlags = reverseBitsU32(hevcCodecInfo.generalProfileCompatibilityFlags);
|
|
285
|
-
generalTierFlag = hevcCodecInfo.generalTierFlag;
|
|
286
|
-
generalLevelIdc = hevcCodecInfo.generalLevelIdc;
|
|
287
|
-
constraintFlags = [...hevcCodecInfo.generalConstraintIndicatorFlags];
|
|
288
|
-
}
|
|
289
|
-
else {
|
|
290
|
-
if (!codecDescription || codecDescription.byteLength < 23) {
|
|
291
|
-
throw new TypeError('HEVC decoder description is not provided or is not at least 23 bytes long.');
|
|
292
|
-
}
|
|
293
|
-
const view = toDataView(codecDescription);
|
|
294
|
-
const profileByte = view.getUint8(1);
|
|
295
|
-
generalProfileSpace = (profileByte >> 6) & 0x03;
|
|
296
|
-
generalProfileIdc = profileByte & 0x1F;
|
|
297
|
-
compatibilityFlags = reverseBitsU32(view.getUint32(2));
|
|
298
|
-
generalTierFlag = (profileByte >> 5) & 0x01;
|
|
299
|
-
generalLevelIdc = view.getUint8(12);
|
|
300
|
-
constraintFlags = [];
|
|
301
|
-
for (let i = 0; i < 6; i++) {
|
|
302
|
-
constraintFlags.push(view.getUint8(6 + i));
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
let codecString = 'hev1.';
|
|
306
|
-
codecString += ['', 'A', 'B', 'C'][generalProfileSpace] + generalProfileIdc;
|
|
307
|
-
codecString += '.';
|
|
308
|
-
codecString += compatibilityFlags.toString(16).toUpperCase();
|
|
309
|
-
codecString += '.';
|
|
310
|
-
codecString += generalTierFlag === 0 ? 'L' : 'H';
|
|
311
|
-
codecString += generalLevelIdc;
|
|
312
|
-
while (constraintFlags.length > 0 && constraintFlags[constraintFlags.length - 1] === 0) {
|
|
313
|
-
constraintFlags.pop();
|
|
314
|
-
}
|
|
315
|
-
if (constraintFlags.length > 0) {
|
|
316
|
-
codecString += '.';
|
|
317
|
-
codecString += constraintFlags.map(x => x.toString(16).toUpperCase()).join('.');
|
|
318
|
-
}
|
|
319
|
-
return codecString;
|
|
320
|
-
}
|
|
321
|
-
else if (codec === 'vp8') {
|
|
322
|
-
return 'vp8'; // Easy, this one
|
|
323
|
-
}
|
|
324
|
-
else if (codec === 'vp9') {
|
|
325
|
-
if (!vp9CodecInfo) {
|
|
326
|
-
// Calculate level based on dimensions
|
|
327
|
-
const pictureSize = trackInfo.width * trackInfo.height;
|
|
328
|
-
let level = last(VP9_LEVEL_TABLE).level; // Default to highest level
|
|
329
|
-
for (const entry of VP9_LEVEL_TABLE) {
|
|
330
|
-
if (pictureSize <= entry.maxPictureSize) {
|
|
331
|
-
level = entry.level;
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
// We don't really know better, so let's return a general-purpose, common codec string and hope for the best
|
|
336
|
-
return `vp09.00.${level.toString().padStart(2, '0')}.08`;
|
|
337
|
-
}
|
|
338
|
-
const profile = vp9CodecInfo.profile.toString().padStart(2, '0');
|
|
339
|
-
const level = vp9CodecInfo.level.toString().padStart(2, '0');
|
|
340
|
-
const bitDepth = vp9CodecInfo.bitDepth.toString().padStart(2, '0');
|
|
341
|
-
const chromaSubsampling = vp9CodecInfo.chromaSubsampling.toString().padStart(2, '0');
|
|
342
|
-
const colourPrimaries = vp9CodecInfo.colourPrimaries.toString().padStart(2, '0');
|
|
343
|
-
const transferCharacteristics = vp9CodecInfo.transferCharacteristics.toString().padStart(2, '0');
|
|
344
|
-
const matrixCoefficients = vp9CodecInfo.matrixCoefficients.toString().padStart(2, '0');
|
|
345
|
-
const videoFullRangeFlag = vp9CodecInfo.videoFullRangeFlag.toString().padStart(2, '0');
|
|
346
|
-
let string = `vp09.${profile}.${level}.${bitDepth}.${chromaSubsampling}`;
|
|
347
|
-
string += `.${colourPrimaries}.${transferCharacteristics}.${matrixCoefficients}.${videoFullRangeFlag}`;
|
|
348
|
-
if (string.endsWith(VP9_DEFAULT_SUFFIX)) {
|
|
349
|
-
string = string.slice(0, -VP9_DEFAULT_SUFFIX.length);
|
|
350
|
-
}
|
|
351
|
-
return string;
|
|
352
|
-
}
|
|
353
|
-
else if (codec === 'av1') {
|
|
354
|
-
if (!av1CodecInfo) {
|
|
355
|
-
// Calculate level based on dimensions
|
|
356
|
-
const pictureSize = trackInfo.width * trackInfo.height;
|
|
357
|
-
let level = last(VP9_LEVEL_TABLE).level; // Default to highest level
|
|
358
|
-
for (const entry of VP9_LEVEL_TABLE) {
|
|
359
|
-
if (pictureSize <= entry.maxPictureSize) {
|
|
360
|
-
level = entry.level;
|
|
361
|
-
break;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
// We don't really know better, so let's return a general-purpose, common codec string and hope for the best
|
|
365
|
-
return `av01.0.${level.toString().padStart(2, '0')}M.08`;
|
|
366
|
-
}
|
|
367
|
-
// https://aomediacodec.github.io/av1-isobmff/#codecsparam
|
|
368
|
-
const profile = av1CodecInfo.profile; // Single digit
|
|
369
|
-
const level = av1CodecInfo.level.toString().padStart(2, '0');
|
|
370
|
-
const tier = av1CodecInfo.tier ? 'H' : 'M';
|
|
371
|
-
const bitDepth = av1CodecInfo.bitDepth.toString().padStart(2, '0');
|
|
372
|
-
const monochrome = av1CodecInfo.monochrome ? '1' : '0';
|
|
373
|
-
const chromaSubsampling = 100 * av1CodecInfo.chromaSubsamplingX
|
|
374
|
-
+ 10 * av1CodecInfo.chromaSubsamplingY
|
|
375
|
-
+ 1 * (av1CodecInfo.chromaSubsamplingX && av1CodecInfo.chromaSubsamplingY
|
|
376
|
-
? av1CodecInfo.chromaSamplePosition
|
|
377
|
-
: 0);
|
|
378
|
-
// The defaults are 1 (ITU-R BT.709)
|
|
379
|
-
const colorPrimaries = colorSpace?.primaries ? COLOR_PRIMARIES_MAP[colorSpace.primaries] : 1;
|
|
380
|
-
const transferCharacteristics = colorSpace?.transfer ? TRANSFER_CHARACTERISTICS_MAP[colorSpace.transfer] : 1;
|
|
381
|
-
const matrixCoefficients = colorSpace?.matrix ? MATRIX_COEFFICIENTS_MAP[colorSpace.matrix] : 1;
|
|
382
|
-
const videoFullRangeFlag = colorSpace?.fullRange ? 1 : 0;
|
|
383
|
-
let string = `av01.${profile}.${level}${tier}.${bitDepth}`;
|
|
384
|
-
string += `.${monochrome}.${chromaSubsampling.toString().padStart(3, '0')}`;
|
|
385
|
-
string += `.${colorPrimaries.toString().padStart(2, '0')}`;
|
|
386
|
-
string += `.${transferCharacteristics.toString().padStart(2, '0')}`;
|
|
387
|
-
string += `.${matrixCoefficients.toString().padStart(2, '0')}`;
|
|
388
|
-
string += `.${videoFullRangeFlag}`;
|
|
389
|
-
if (string.endsWith(AV1_DEFAULT_SUFFIX)) {
|
|
390
|
-
string = string.slice(0, -AV1_DEFAULT_SUFFIX.length);
|
|
391
|
-
}
|
|
392
|
-
return string;
|
|
393
|
-
}
|
|
394
|
-
throw new TypeError(`Unhandled codec '${codec}'.`);
|
|
395
|
-
};
|
|
396
|
-
export const buildAudioCodecString = (codec, numberOfChannels, sampleRate) => {
|
|
397
|
-
if (codec === 'aac') {
|
|
398
|
-
// If stereo or higher channels and lower sample rate, likely using HE-AAC v2 with PS
|
|
399
|
-
if (numberOfChannels >= 2 && sampleRate <= 24000) {
|
|
400
|
-
return 'mp4a.40.29'; // HE-AAC v2 (AAC LC + SBR + PS)
|
|
401
|
-
}
|
|
402
|
-
// If sample rate is low, likely using HE-AAC v1 with SBR
|
|
403
|
-
if (sampleRate <= 24000) {
|
|
404
|
-
return 'mp4a.40.5'; // HE-AAC v1 (AAC LC + SBR)
|
|
405
|
-
}
|
|
406
|
-
// Default to standard AAC-LC for higher sample rates
|
|
407
|
-
return 'mp4a.40.2'; // AAC-LC
|
|
408
|
-
}
|
|
409
|
-
else if (codec === 'mp3') {
|
|
410
|
-
return 'mp3';
|
|
411
|
-
}
|
|
412
|
-
else if (codec === 'opus') {
|
|
413
|
-
return 'opus';
|
|
414
|
-
}
|
|
415
|
-
else if (codec === 'vorbis') {
|
|
416
|
-
return 'vorbis';
|
|
417
|
-
}
|
|
418
|
-
else if (codec === 'flac') {
|
|
419
|
-
return 'flac';
|
|
420
|
-
}
|
|
421
|
-
else if (PCM_AUDIO_CODECS.includes(codec)) {
|
|
422
|
-
return codec;
|
|
423
|
-
}
|
|
424
|
-
throw new TypeError(`Unhandled codec '${codec}'.`);
|
|
425
|
-
};
|
|
426
|
-
export const extractAudioCodecString = (trackInfo) => {
|
|
427
|
-
const { codec, codecDescription, aacCodecInfo } = trackInfo;
|
|
428
|
-
if (codec === 'aac') {
|
|
429
|
-
if (!aacCodecInfo) {
|
|
430
|
-
throw new TypeError('AAC codec info must be provided.');
|
|
431
|
-
}
|
|
432
|
-
if (aacCodecInfo.isMpeg2) {
|
|
433
|
-
return 'mp4a.67';
|
|
434
|
-
}
|
|
435
|
-
else {
|
|
436
|
-
const audioSpecificConfig = parseAacAudioSpecificConfig(codecDescription);
|
|
437
|
-
return `mp4a.40.${audioSpecificConfig.objectType}`;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
else if (codec === 'mp3') {
|
|
441
|
-
return 'mp3';
|
|
442
|
-
}
|
|
443
|
-
else if (codec === 'opus') {
|
|
444
|
-
return 'opus';
|
|
445
|
-
}
|
|
446
|
-
else if (codec === 'vorbis') {
|
|
447
|
-
return 'vorbis';
|
|
448
|
-
}
|
|
449
|
-
else if (codec === 'flac') {
|
|
450
|
-
return 'flac';
|
|
451
|
-
}
|
|
452
|
-
else if (codec && PCM_AUDIO_CODECS.includes(codec)) {
|
|
453
|
-
return codec;
|
|
454
|
-
}
|
|
455
|
-
throw new TypeError(`Unhandled codec '${codec}'.`);
|
|
456
|
-
};
|
|
457
|
-
export const aacFrequencyTable = [
|
|
458
|
-
96000, 88200, 64000, 48000, 44100, 32000,
|
|
459
|
-
24000, 22050, 16000, 12000, 11025, 8000, 7350,
|
|
460
|
-
];
|
|
461
|
-
export const aacChannelMap = [-1, 1, 2, 3, 4, 5, 6, 8];
|
|
462
|
-
export const parseAacAudioSpecificConfig = (bytes) => {
|
|
463
|
-
if (!bytes || bytes.byteLength < 2) {
|
|
464
|
-
throw new TypeError('AAC description must be at least 2 bytes long.');
|
|
465
|
-
}
|
|
466
|
-
const bitstream = new Bitstream(bytes);
|
|
467
|
-
let objectType = bitstream.readBits(5);
|
|
468
|
-
if (objectType === 31) {
|
|
469
|
-
objectType = 32 + bitstream.readBits(6);
|
|
470
|
-
}
|
|
471
|
-
const frequencyIndex = bitstream.readBits(4);
|
|
472
|
-
let sampleRate = null;
|
|
473
|
-
if (frequencyIndex === 15) {
|
|
474
|
-
sampleRate = bitstream.readBits(24);
|
|
475
|
-
}
|
|
476
|
-
else {
|
|
477
|
-
if (frequencyIndex < aacFrequencyTable.length) {
|
|
478
|
-
sampleRate = aacFrequencyTable[frequencyIndex];
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
const channelConfiguration = bitstream.readBits(4);
|
|
482
|
-
let numberOfChannels = null;
|
|
483
|
-
if (channelConfiguration >= 1 && channelConfiguration <= 7) {
|
|
484
|
-
numberOfChannels = aacChannelMap[channelConfiguration];
|
|
485
|
-
}
|
|
486
|
-
return {
|
|
487
|
-
objectType,
|
|
488
|
-
frequencyIndex,
|
|
489
|
-
sampleRate,
|
|
490
|
-
channelConfiguration,
|
|
491
|
-
numberOfChannels,
|
|
492
|
-
};
|
|
493
|
-
};
|
|
494
|
-
export const buildAacAudioSpecificConfig = (config) => {
|
|
495
|
-
let frequencyIndex = aacFrequencyTable.indexOf(config.sampleRate);
|
|
496
|
-
let customSampleRate = null;
|
|
497
|
-
if (frequencyIndex === -1) {
|
|
498
|
-
frequencyIndex = 15;
|
|
499
|
-
customSampleRate = config.sampleRate;
|
|
500
|
-
}
|
|
501
|
-
const channelConfiguration = aacChannelMap.indexOf(config.numberOfChannels);
|
|
502
|
-
if (channelConfiguration === -1) {
|
|
503
|
-
throw new TypeError(`Unsupported number of channels: ${config.numberOfChannels}`);
|
|
504
|
-
}
|
|
505
|
-
let bitCount = 5 + 4 + 4;
|
|
506
|
-
if (config.objectType >= 32) {
|
|
507
|
-
bitCount += 6;
|
|
508
|
-
}
|
|
509
|
-
if (frequencyIndex === 15) {
|
|
510
|
-
bitCount += 24;
|
|
511
|
-
}
|
|
512
|
-
const byteCount = Math.ceil(bitCount / 8);
|
|
513
|
-
const bytes = new Uint8Array(byteCount);
|
|
514
|
-
const bitstream = new Bitstream(bytes);
|
|
515
|
-
if (config.objectType < 32) {
|
|
516
|
-
bitstream.writeBits(5, config.objectType);
|
|
517
|
-
}
|
|
518
|
-
else {
|
|
519
|
-
bitstream.writeBits(5, 31);
|
|
520
|
-
bitstream.writeBits(6, config.objectType - 32);
|
|
521
|
-
}
|
|
522
|
-
bitstream.writeBits(4, frequencyIndex);
|
|
523
|
-
if (frequencyIndex === 15) {
|
|
524
|
-
bitstream.writeBits(24, customSampleRate);
|
|
525
|
-
}
|
|
526
|
-
bitstream.writeBits(4, channelConfiguration);
|
|
527
|
-
return bytes;
|
|
528
|
-
};
|
|
529
|
-
export const OPUS_SAMPLE_RATE = 48_000;
|
|
530
|
-
const PCM_CODEC_REGEX = /^pcm-([usf])(\d+)+(be)?$/;
|
|
531
|
-
export const parsePcmCodec = (codec) => {
|
|
532
|
-
assert(PCM_AUDIO_CODECS.includes(codec));
|
|
533
|
-
if (codec === 'ulaw') {
|
|
534
|
-
return { dataType: 'ulaw', sampleSize: 1, littleEndian: true, silentValue: 255 };
|
|
535
|
-
}
|
|
536
|
-
else if (codec === 'alaw') {
|
|
537
|
-
return { dataType: 'alaw', sampleSize: 1, littleEndian: true, silentValue: 213 };
|
|
538
|
-
}
|
|
539
|
-
const match = PCM_CODEC_REGEX.exec(codec);
|
|
540
|
-
assert(match);
|
|
541
|
-
let dataType;
|
|
542
|
-
if (match[1] === 'u') {
|
|
543
|
-
dataType = 'unsigned';
|
|
544
|
-
}
|
|
545
|
-
else if (match[1] === 's') {
|
|
546
|
-
dataType = 'signed';
|
|
547
|
-
}
|
|
548
|
-
else {
|
|
549
|
-
dataType = 'float';
|
|
550
|
-
}
|
|
551
|
-
const sampleSize = (Number(match[2]) / 8);
|
|
552
|
-
const littleEndian = match[3] !== 'be';
|
|
553
|
-
const silentValue = codec === 'pcm-u8' ? 2 ** 7 : 0;
|
|
554
|
-
return { dataType, sampleSize, littleEndian, silentValue };
|
|
555
|
-
};
|
|
556
|
-
export const inferCodecFromCodecString = (codecString) => {
|
|
557
|
-
// Video codecs
|
|
558
|
-
if (codecString.startsWith('avc1') || codecString.startsWith('avc3')) {
|
|
559
|
-
return 'avc';
|
|
560
|
-
}
|
|
561
|
-
else if (codecString.startsWith('hev1') || codecString.startsWith('hvc1')) {
|
|
562
|
-
return 'hevc';
|
|
563
|
-
}
|
|
564
|
-
else if (codecString === 'vp8') {
|
|
565
|
-
return 'vp8';
|
|
566
|
-
}
|
|
567
|
-
else if (codecString.startsWith('vp09')) {
|
|
568
|
-
return 'vp9';
|
|
569
|
-
}
|
|
570
|
-
else if (codecString.startsWith('av01')) {
|
|
571
|
-
return 'av1';
|
|
572
|
-
}
|
|
573
|
-
// Audio codecs
|
|
574
|
-
if (codecString.startsWith('mp4a.40') || codecString === 'mp4a.67') {
|
|
575
|
-
return 'aac';
|
|
576
|
-
}
|
|
577
|
-
else if (codecString === 'mp3'
|
|
578
|
-
|| codecString === 'mp4a.69'
|
|
579
|
-
|| codecString === 'mp4a.6B'
|
|
580
|
-
|| codecString === 'mp4a.6b') {
|
|
581
|
-
return 'mp3';
|
|
582
|
-
}
|
|
583
|
-
else if (codecString === 'opus') {
|
|
584
|
-
return 'opus';
|
|
585
|
-
}
|
|
586
|
-
else if (codecString === 'vorbis') {
|
|
587
|
-
return 'vorbis';
|
|
588
|
-
}
|
|
589
|
-
else if (codecString === 'flac') {
|
|
590
|
-
return 'flac';
|
|
591
|
-
}
|
|
592
|
-
else if (codecString === 'ulaw') {
|
|
593
|
-
return 'ulaw';
|
|
594
|
-
}
|
|
595
|
-
else if (codecString === 'alaw') {
|
|
596
|
-
return 'alaw';
|
|
597
|
-
}
|
|
598
|
-
else if (PCM_CODEC_REGEX.test(codecString)) {
|
|
599
|
-
return codecString;
|
|
600
|
-
}
|
|
601
|
-
// Subtitle codecs
|
|
602
|
-
if (codecString === 'webvtt') {
|
|
603
|
-
return 'webvtt';
|
|
604
|
-
}
|
|
605
|
-
else if (codecString === 'srt') {
|
|
606
|
-
return 'srt';
|
|
607
|
-
}
|
|
608
|
-
else if (codecString === 'ass') {
|
|
609
|
-
return 'ass';
|
|
610
|
-
}
|
|
611
|
-
else if (codecString === 'ssa') {
|
|
612
|
-
return 'ssa';
|
|
613
|
-
}
|
|
614
|
-
return null;
|
|
615
|
-
};
|
|
616
|
-
export const getVideoEncoderConfigExtension = (codec) => {
|
|
617
|
-
if (codec === 'avc') {
|
|
618
|
-
return {
|
|
619
|
-
avc: {
|
|
620
|
-
format: 'avc', // Ensure the format is not Annex B
|
|
621
|
-
},
|
|
622
|
-
};
|
|
623
|
-
}
|
|
624
|
-
else if (codec === 'hevc') {
|
|
625
|
-
return {
|
|
626
|
-
hevc: {
|
|
627
|
-
format: 'hevc', // Ensure the format is not Annex B
|
|
628
|
-
},
|
|
629
|
-
};
|
|
630
|
-
}
|
|
631
|
-
return {};
|
|
632
|
-
};
|
|
633
|
-
export const getAudioEncoderConfigExtension = (codec) => {
|
|
634
|
-
if (codec === 'aac') {
|
|
635
|
-
return {
|
|
636
|
-
aac: {
|
|
637
|
-
format: 'aac', // Ensure the format is not ADTS
|
|
638
|
-
},
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
else if (codec === 'opus') {
|
|
642
|
-
return {
|
|
643
|
-
opus: {
|
|
644
|
-
format: 'opus',
|
|
645
|
-
},
|
|
646
|
-
};
|
|
647
|
-
}
|
|
648
|
-
return {};
|
|
649
|
-
};
|
|
650
|
-
const VALID_VIDEO_CODEC_STRING_PREFIXES = ['avc1', 'avc3', 'hev1', 'hvc1', 'vp8', 'vp09', 'av01'];
|
|
651
|
-
const AVC_CODEC_STRING_REGEX = /^(avc1|avc3)\.[0-9a-fA-F]{6}$/;
|
|
652
|
-
const HEVC_CODEC_STRING_REGEX = /^(hev1|hvc1)\.(?:[ABC]?\d+)\.[0-9a-fA-F]{1,8}\.[LH]\d+(?:\.[0-9a-fA-F]{1,2}){0,6}$/;
|
|
653
|
-
const VP9_CODEC_STRING_REGEX = /^vp09(?:\.\d{2}){3}(?:(?:\.\d{2}){5})?$/;
|
|
654
|
-
const AV1_CODEC_STRING_REGEX = /^av01\.\d\.\d{2}[MH]\.\d{2}(?:\.\d\.\d{3}\.\d{2}\.\d{2}\.\d{2}\.\d)?$/;
|
|
655
|
-
export const validateVideoChunkMetadata = (metadata) => {
|
|
656
|
-
if (!metadata) {
|
|
657
|
-
throw new TypeError('Video chunk metadata must be provided.');
|
|
658
|
-
}
|
|
659
|
-
if (typeof metadata !== 'object') {
|
|
660
|
-
throw new TypeError('Video chunk metadata must be an object.');
|
|
661
|
-
}
|
|
662
|
-
if (!metadata.decoderConfig) {
|
|
663
|
-
throw new TypeError('Video chunk metadata must include a decoder configuration.');
|
|
664
|
-
}
|
|
665
|
-
if (typeof metadata.decoderConfig !== 'object') {
|
|
666
|
-
throw new TypeError('Video chunk metadata decoder configuration must be an object.');
|
|
667
|
-
}
|
|
668
|
-
if (typeof metadata.decoderConfig.codec !== 'string') {
|
|
669
|
-
throw new TypeError('Video chunk metadata decoder configuration must specify a codec string.');
|
|
670
|
-
}
|
|
671
|
-
if (!VALID_VIDEO_CODEC_STRING_PREFIXES.some(prefix => metadata.decoderConfig.codec.startsWith(prefix))) {
|
|
672
|
-
throw new TypeError('Video chunk metadata decoder configuration codec string must be a valid video codec string as specified in'
|
|
673
|
-
+ ' the WebCodecs Codec Registry.');
|
|
674
|
-
}
|
|
675
|
-
if (!Number.isInteger(metadata.decoderConfig.codedWidth) || metadata.decoderConfig.codedWidth <= 0) {
|
|
676
|
-
throw new TypeError('Video chunk metadata decoder configuration must specify a valid codedWidth (positive integer).');
|
|
677
|
-
}
|
|
678
|
-
if (!Number.isInteger(metadata.decoderConfig.codedHeight) || metadata.decoderConfig.codedHeight <= 0) {
|
|
679
|
-
throw new TypeError('Video chunk metadata decoder configuration must specify a valid codedHeight (positive integer).');
|
|
680
|
-
}
|
|
681
|
-
if (metadata.decoderConfig.description !== undefined) {
|
|
682
|
-
if (!isAllowSharedBufferSource(metadata.decoderConfig.description)) {
|
|
683
|
-
throw new TypeError('Video chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an'
|
|
684
|
-
+ ' ArrayBuffer view.');
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
if (metadata.decoderConfig.colorSpace !== undefined) {
|
|
688
|
-
const { colorSpace } = metadata.decoderConfig;
|
|
689
|
-
if (typeof colorSpace !== 'object') {
|
|
690
|
-
throw new TypeError('Video chunk metadata decoder configuration colorSpace, when provided, must be an object.');
|
|
691
|
-
}
|
|
692
|
-
const primariesValues = Object.keys(COLOR_PRIMARIES_MAP);
|
|
693
|
-
if (colorSpace.primaries != null && !primariesValues.includes(colorSpace.primaries)) {
|
|
694
|
-
throw new TypeError(`Video chunk metadata decoder configuration colorSpace primaries, when defined, must be one of`
|
|
695
|
-
+ ` ${primariesValues.join(', ')}.`);
|
|
696
|
-
}
|
|
697
|
-
const transferValues = Object.keys(TRANSFER_CHARACTERISTICS_MAP);
|
|
698
|
-
if (colorSpace.transfer != null && !transferValues.includes(colorSpace.transfer)) {
|
|
699
|
-
throw new TypeError(`Video chunk metadata decoder configuration colorSpace transfer, when defined, must be one of`
|
|
700
|
-
+ ` ${transferValues.join(', ')}.`);
|
|
701
|
-
}
|
|
702
|
-
const matrixValues = Object.keys(MATRIX_COEFFICIENTS_MAP);
|
|
703
|
-
if (colorSpace.matrix != null && !matrixValues.includes(colorSpace.matrix)) {
|
|
704
|
-
throw new TypeError(`Video chunk metadata decoder configuration colorSpace matrix, when defined, must be one of`
|
|
705
|
-
+ ` ${matrixValues.join(', ')}.`);
|
|
706
|
-
}
|
|
707
|
-
if (colorSpace.fullRange != null && typeof colorSpace.fullRange !== 'boolean') {
|
|
708
|
-
throw new TypeError('Video chunk metadata decoder configuration colorSpace fullRange, when defined, must be a boolean.');
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
if (metadata.decoderConfig.codec.startsWith('avc1') || metadata.decoderConfig.codec.startsWith('avc3')) {
|
|
712
|
-
// AVC-specific validation
|
|
713
|
-
if (!AVC_CODEC_STRING_REGEX.test(metadata.decoderConfig.codec)) {
|
|
714
|
-
throw new TypeError('Video chunk metadata decoder configuration codec string for AVC must be a valid AVC codec string as'
|
|
715
|
-
+ ' specified in Section 3.4 of RFC 6381.');
|
|
716
|
-
}
|
|
717
|
-
// `description` may or may not be set, depending on if the format is AVCC or Annex B, so don't perform any
|
|
718
|
-
// validation for it.
|
|
719
|
-
// https://www.w3.org/TR/webcodecs-avc-codec-registration
|
|
720
|
-
}
|
|
721
|
-
else if (metadata.decoderConfig.codec.startsWith('hev1') || metadata.decoderConfig.codec.startsWith('hvc1')) {
|
|
722
|
-
// HEVC-specific validation
|
|
723
|
-
if (!HEVC_CODEC_STRING_REGEX.test(metadata.decoderConfig.codec)) {
|
|
724
|
-
throw new TypeError('Video chunk metadata decoder configuration codec string for HEVC must be a valid HEVC codec string as'
|
|
725
|
-
+ ' specified in Section E.3 of ISO 14496-15.');
|
|
726
|
-
}
|
|
727
|
-
// `description` may or may not be set, depending on if the format is HEVC or Annex B, so don't perform any
|
|
728
|
-
// validation for it.
|
|
729
|
-
// https://www.w3.org/TR/webcodecs-hevc-codec-registration
|
|
730
|
-
}
|
|
731
|
-
else if (metadata.decoderConfig.codec.startsWith('vp8')) {
|
|
732
|
-
// VP8-specific validation
|
|
733
|
-
if (metadata.decoderConfig.codec !== 'vp8') {
|
|
734
|
-
throw new TypeError('Video chunk metadata decoder configuration codec string for VP8 must be "vp8".');
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
else if (metadata.decoderConfig.codec.startsWith('vp09')) {
|
|
738
|
-
// VP9-specific validation
|
|
739
|
-
if (!VP9_CODEC_STRING_REGEX.test(metadata.decoderConfig.codec)) {
|
|
740
|
-
throw new TypeError('Video chunk metadata decoder configuration codec string for VP9 must be a valid VP9 codec string as'
|
|
741
|
-
+ ' specified in Section "Codecs Parameter String" of https://www.webmproject.org/vp9/mp4/.');
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
else if (metadata.decoderConfig.codec.startsWith('av01')) {
|
|
745
|
-
// AV1-specific validation
|
|
746
|
-
if (!AV1_CODEC_STRING_REGEX.test(metadata.decoderConfig.codec)) {
|
|
747
|
-
throw new TypeError('Video chunk metadata decoder configuration codec string for AV1 must be a valid AV1 codec string as'
|
|
748
|
-
+ ' specified in Section "Codecs Parameter String" of https://aomediacodec.github.io/av1-isobmff/.');
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
};
|
|
752
|
-
const VALID_AUDIO_CODEC_STRING_PREFIXES = ['mp4a', 'mp3', 'opus', 'vorbis', 'flac', 'ulaw', 'alaw', 'pcm'];
|
|
753
|
-
export const validateAudioChunkMetadata = (metadata) => {
|
|
754
|
-
if (!metadata) {
|
|
755
|
-
throw new TypeError('Audio chunk metadata must be provided.');
|
|
756
|
-
}
|
|
757
|
-
if (typeof metadata !== 'object') {
|
|
758
|
-
throw new TypeError('Audio chunk metadata must be an object.');
|
|
759
|
-
}
|
|
760
|
-
if (!metadata.decoderConfig) {
|
|
761
|
-
throw new TypeError('Audio chunk metadata must include a decoder configuration.');
|
|
762
|
-
}
|
|
763
|
-
if (typeof metadata.decoderConfig !== 'object') {
|
|
764
|
-
throw new TypeError('Audio chunk metadata decoder configuration must be an object.');
|
|
765
|
-
}
|
|
766
|
-
if (typeof metadata.decoderConfig.codec !== 'string') {
|
|
767
|
-
throw new TypeError('Audio chunk metadata decoder configuration must specify a codec string.');
|
|
768
|
-
}
|
|
769
|
-
if (!VALID_AUDIO_CODEC_STRING_PREFIXES.some(prefix => metadata.decoderConfig.codec.startsWith(prefix))) {
|
|
770
|
-
throw new TypeError('Audio chunk metadata decoder configuration codec string must be a valid audio codec string as specified in'
|
|
771
|
-
+ ' the WebCodecs Codec Registry.');
|
|
772
|
-
}
|
|
773
|
-
if (!Number.isInteger(metadata.decoderConfig.sampleRate) || metadata.decoderConfig.sampleRate <= 0) {
|
|
774
|
-
throw new TypeError('Audio chunk metadata decoder configuration must specify a valid sampleRate (positive integer).');
|
|
775
|
-
}
|
|
776
|
-
if (!Number.isInteger(metadata.decoderConfig.numberOfChannels) || metadata.decoderConfig.numberOfChannels <= 0) {
|
|
777
|
-
throw new TypeError('Audio chunk metadata decoder configuration must specify a valid numberOfChannels (positive integer).');
|
|
778
|
-
}
|
|
779
|
-
if (metadata.decoderConfig.description !== undefined) {
|
|
780
|
-
if (!isAllowSharedBufferSource(metadata.decoderConfig.description)) {
|
|
781
|
-
throw new TypeError('Audio chunk metadata decoder configuration description, when defined, must be an ArrayBuffer or an'
|
|
782
|
-
+ ' ArrayBuffer view.');
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
if (metadata.decoderConfig.codec.startsWith('mp4a')
|
|
786
|
-
// These three refer to MP3:
|
|
787
|
-
&& metadata.decoderConfig.codec !== 'mp4a.69'
|
|
788
|
-
&& metadata.decoderConfig.codec !== 'mp4a.6B'
|
|
789
|
-
&& metadata.decoderConfig.codec !== 'mp4a.6b') {
|
|
790
|
-
// AAC-specific validation
|
|
791
|
-
const validStrings = ['mp4a.40.2', 'mp4a.40.02', 'mp4a.40.5', 'mp4a.40.05', 'mp4a.40.29', 'mp4a.67'];
|
|
792
|
-
if (!validStrings.includes(metadata.decoderConfig.codec)) {
|
|
793
|
-
throw new TypeError('Audio chunk metadata decoder configuration codec string for AAC must be a valid AAC codec string as'
|
|
794
|
-
+ ' specified in https://www.w3.org/TR/webcodecs-aac-codec-registration/.');
|
|
795
|
-
}
|
|
796
|
-
if (!metadata.decoderConfig.description) {
|
|
797
|
-
throw new TypeError('Audio chunk metadata decoder configuration for AAC must include a description, which is expected to be'
|
|
798
|
-
+ ' an AudioSpecificConfig as specified in ISO 14496-3.');
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
else if (metadata.decoderConfig.codec.startsWith('mp3') || metadata.decoderConfig.codec.startsWith('mp4a')) {
|
|
802
|
-
// MP3-specific validation
|
|
803
|
-
if (metadata.decoderConfig.codec !== 'mp3'
|
|
804
|
-
&& metadata.decoderConfig.codec !== 'mp4a.69'
|
|
805
|
-
&& metadata.decoderConfig.codec !== 'mp4a.6B'
|
|
806
|
-
&& metadata.decoderConfig.codec !== 'mp4a.6b') {
|
|
807
|
-
throw new TypeError('Audio chunk metadata decoder configuration codec string for MP3 must be "mp3", "mp4a.69" or'
|
|
808
|
-
+ ' "mp4a.6B".');
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
else if (metadata.decoderConfig.codec.startsWith('opus')) {
|
|
812
|
-
// Opus-specific validation
|
|
813
|
-
if (metadata.decoderConfig.codec !== 'opus') {
|
|
814
|
-
throw new TypeError('Audio chunk metadata decoder configuration codec string for Opus must be "opus".');
|
|
815
|
-
}
|
|
816
|
-
if (metadata.decoderConfig.description && metadata.decoderConfig.description.byteLength < 18) {
|
|
817
|
-
// Description is optional for Opus per-spec, so we shouldn't enforce it
|
|
818
|
-
throw new TypeError('Audio chunk metadata decoder configuration description, when specified, is expected to be an'
|
|
819
|
-
+ ' Identification Header as specified in Section 5.1 of RFC 7845.');
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
else if (metadata.decoderConfig.codec.startsWith('vorbis')) {
|
|
823
|
-
// Vorbis-specific validation
|
|
824
|
-
if (metadata.decoderConfig.codec !== 'vorbis') {
|
|
825
|
-
throw new TypeError('Audio chunk metadata decoder configuration codec string for Vorbis must be "vorbis".');
|
|
826
|
-
}
|
|
827
|
-
if (!metadata.decoderConfig.description) {
|
|
828
|
-
throw new TypeError('Audio chunk metadata decoder configuration for Vorbis must include a description, which is expected to'
|
|
829
|
-
+ ' adhere to the format described in https://www.w3.org/TR/webcodecs-vorbis-codec-registration/.');
|
|
830
|
-
}
|
|
831
|
-
}
|
|
832
|
-
else if (metadata.decoderConfig.codec.startsWith('flac')) {
|
|
833
|
-
// FLAC-specific validation
|
|
834
|
-
if (metadata.decoderConfig.codec !== 'flac') {
|
|
835
|
-
throw new TypeError('Audio chunk metadata decoder configuration codec string for FLAC must be "flac".');
|
|
836
|
-
}
|
|
837
|
-
const minDescriptionSize = 4 + 4 + 34; // 'fLaC' + metadata block header + STREAMINFO block
|
|
838
|
-
if (!metadata.decoderConfig.description || metadata.decoderConfig.description.byteLength < minDescriptionSize) {
|
|
839
|
-
throw new TypeError('Audio chunk metadata decoder configuration for FLAC must include a description, which is expected to'
|
|
840
|
-
+ ' adhere to the format described in https://www.w3.org/TR/webcodecs-flac-codec-registration/.');
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
else if (metadata.decoderConfig.codec.startsWith('pcm')
|
|
844
|
-
|| metadata.decoderConfig.codec.startsWith('ulaw')
|
|
845
|
-
|| metadata.decoderConfig.codec.startsWith('alaw')) {
|
|
846
|
-
// PCM-specific validation
|
|
847
|
-
if (!PCM_AUDIO_CODECS.includes(metadata.decoderConfig.codec)) {
|
|
848
|
-
throw new TypeError('Audio chunk metadata decoder configuration codec string for PCM must be one of the supported PCM'
|
|
849
|
-
+ ` codecs (${PCM_AUDIO_CODECS.join(', ')}).`);
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
};
|
|
853
|
-
export const validateSubtitleMetadata = (metadata) => {
|
|
854
|
-
if (!metadata) {
|
|
855
|
-
throw new TypeError('Subtitle metadata must be provided.');
|
|
856
|
-
}
|
|
857
|
-
if (typeof metadata !== 'object') {
|
|
858
|
-
throw new TypeError('Subtitle metadata must be an object.');
|
|
859
|
-
}
|
|
860
|
-
if (!metadata.config) {
|
|
861
|
-
throw new TypeError('Subtitle metadata must include a config object.');
|
|
862
|
-
}
|
|
863
|
-
if (typeof metadata.config !== 'object') {
|
|
864
|
-
throw new TypeError('Subtitle metadata config must be an object.');
|
|
865
|
-
}
|
|
866
|
-
if (typeof metadata.config.description !== 'string') {
|
|
867
|
-
throw new TypeError('Subtitle metadata config description must be a string.');
|
|
868
|
-
}
|
|
869
|
-
};
|