@kenzuya/mediabunny 1.26.0 → 1.28.6

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.
Files changed (238) hide show
  1. package/README.md +1 -1
  2. package/dist/bundles/{mediabunny.mjs → mediabunny.js} +21963 -21390
  3. package/dist/bundles/mediabunny.min.js +490 -0
  4. package/dist/modules/shared/mp3-misc.d.ts.map +1 -1
  5. package/dist/modules/src/adts/adts-demuxer.d.ts +6 -6
  6. package/dist/modules/src/adts/adts-demuxer.d.ts.map +1 -1
  7. package/dist/modules/src/adts/adts-muxer.d.ts +4 -4
  8. package/dist/modules/src/adts/adts-muxer.d.ts.map +1 -1
  9. package/dist/modules/src/adts/adts-reader.d.ts +1 -1
  10. package/dist/modules/src/adts/adts-reader.d.ts.map +1 -1
  11. package/dist/modules/src/avi/avi-demuxer.d.ts +44 -0
  12. package/dist/modules/src/avi/avi-demuxer.d.ts.map +1 -0
  13. package/dist/modules/src/avi/avi-misc.d.ts +88 -0
  14. package/dist/modules/src/avi/avi-misc.d.ts.map +1 -0
  15. package/dist/modules/src/avi/avi-muxer.d.ts +45 -0
  16. package/dist/modules/src/avi/avi-muxer.d.ts.map +1 -0
  17. package/dist/modules/src/avi/riff-writer.d.ts +26 -0
  18. package/dist/modules/src/avi/riff-writer.d.ts.map +1 -0
  19. package/dist/modules/src/codec-data.d.ts +8 -3
  20. package/dist/modules/src/codec-data.d.ts.map +1 -1
  21. package/dist/modules/src/codec.d.ts +10 -10
  22. package/dist/modules/src/codec.d.ts.map +1 -1
  23. package/dist/modules/src/conversion.d.ts +33 -16
  24. package/dist/modules/src/conversion.d.ts.map +1 -1
  25. package/dist/modules/src/custom-coder.d.ts +8 -8
  26. package/dist/modules/src/custom-coder.d.ts.map +1 -1
  27. package/dist/modules/src/demuxer.d.ts +3 -3
  28. package/dist/modules/src/demuxer.d.ts.map +1 -1
  29. package/dist/modules/src/encode.d.ts +8 -8
  30. package/dist/modules/src/encode.d.ts.map +1 -1
  31. package/dist/modules/src/flac/flac-demuxer.d.ts +7 -7
  32. package/dist/modules/src/flac/flac-demuxer.d.ts.map +1 -1
  33. package/dist/modules/src/flac/flac-misc.d.ts +3 -3
  34. package/dist/modules/src/flac/flac-misc.d.ts.map +1 -1
  35. package/dist/modules/src/flac/flac-muxer.d.ts +5 -5
  36. package/dist/modules/src/flac/flac-muxer.d.ts.map +1 -1
  37. package/dist/modules/src/id3.d.ts +3 -3
  38. package/dist/modules/src/id3.d.ts.map +1 -1
  39. package/dist/modules/src/index.d.ts +20 -20
  40. package/dist/modules/src/index.d.ts.map +1 -1
  41. package/dist/modules/src/input-format.d.ts +22 -0
  42. package/dist/modules/src/input-format.d.ts.map +1 -1
  43. package/dist/modules/src/input-track.d.ts +8 -8
  44. package/dist/modules/src/input-track.d.ts.map +1 -1
  45. package/dist/modules/src/input.d.ts +12 -12
  46. package/dist/modules/src/isobmff/isobmff-boxes.d.ts +2 -2
  47. package/dist/modules/src/isobmff/isobmff-boxes.d.ts.map +1 -1
  48. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts +12 -12
  49. package/dist/modules/src/isobmff/isobmff-demuxer.d.ts.map +1 -1
  50. package/dist/modules/src/isobmff/isobmff-misc.d.ts.map +1 -1
  51. package/dist/modules/src/isobmff/isobmff-muxer.d.ts +11 -11
  52. package/dist/modules/src/isobmff/isobmff-muxer.d.ts.map +1 -1
  53. package/dist/modules/src/isobmff/isobmff-reader.d.ts +2 -2
  54. package/dist/modules/src/isobmff/isobmff-reader.d.ts.map +1 -1
  55. package/dist/modules/src/matroska/ebml.d.ts +3 -3
  56. package/dist/modules/src/matroska/ebml.d.ts.map +1 -1
  57. package/dist/modules/src/matroska/matroska-demuxer.d.ts +13 -13
  58. package/dist/modules/src/matroska/matroska-demuxer.d.ts.map +1 -1
  59. package/dist/modules/src/matroska/matroska-input.d.ts +33 -0
  60. package/dist/modules/src/matroska/matroska-input.d.ts.map +1 -0
  61. package/dist/modules/src/matroska/matroska-misc.d.ts.map +1 -1
  62. package/dist/modules/src/matroska/matroska-muxer.d.ts +5 -5
  63. package/dist/modules/src/matroska/matroska-muxer.d.ts.map +1 -1
  64. package/dist/modules/src/media-sink.d.ts +5 -5
  65. package/dist/modules/src/media-sink.d.ts.map +1 -1
  66. package/dist/modules/src/media-source.d.ts +22 -4
  67. package/dist/modules/src/media-source.d.ts.map +1 -1
  68. package/dist/modules/src/metadata.d.ts +2 -2
  69. package/dist/modules/src/metadata.d.ts.map +1 -1
  70. package/dist/modules/src/misc.d.ts +5 -4
  71. package/dist/modules/src/misc.d.ts.map +1 -1
  72. package/dist/modules/src/mp3/mp3-demuxer.d.ts +7 -7
  73. package/dist/modules/src/mp3/mp3-demuxer.d.ts.map +1 -1
  74. package/dist/modules/src/mp3/mp3-muxer.d.ts +4 -4
  75. package/dist/modules/src/mp3/mp3-muxer.d.ts.map +1 -1
  76. package/dist/modules/src/mp3/mp3-reader.d.ts +2 -2
  77. package/dist/modules/src/mp3/mp3-reader.d.ts.map +1 -1
  78. package/dist/modules/src/mp3/mp3-writer.d.ts +1 -1
  79. package/dist/modules/src/mp3/mp3-writer.d.ts.map +1 -1
  80. package/dist/modules/src/muxer.d.ts +4 -4
  81. package/dist/modules/src/muxer.d.ts.map +1 -1
  82. package/dist/modules/src/ogg/ogg-demuxer.d.ts +7 -7
  83. package/dist/modules/src/ogg/ogg-demuxer.d.ts.map +1 -1
  84. package/dist/modules/src/ogg/ogg-misc.d.ts +1 -1
  85. package/dist/modules/src/ogg/ogg-misc.d.ts.map +1 -1
  86. package/dist/modules/src/ogg/ogg-muxer.d.ts +5 -5
  87. package/dist/modules/src/ogg/ogg-muxer.d.ts.map +1 -1
  88. package/dist/modules/src/ogg/ogg-reader.d.ts +1 -1
  89. package/dist/modules/src/ogg/ogg-reader.d.ts.map +1 -1
  90. package/dist/modules/src/output-format.d.ts +51 -6
  91. package/dist/modules/src/output-format.d.ts.map +1 -1
  92. package/dist/modules/src/output.d.ts +13 -13
  93. package/dist/modules/src/output.d.ts.map +1 -1
  94. package/dist/modules/src/packet.d.ts +1 -1
  95. package/dist/modules/src/packet.d.ts.map +1 -1
  96. package/dist/modules/src/pcm.d.ts.map +1 -1
  97. package/dist/modules/src/reader.d.ts +2 -2
  98. package/dist/modules/src/reader.d.ts.map +1 -1
  99. package/dist/modules/src/sample.d.ts +57 -15
  100. package/dist/modules/src/sample.d.ts.map +1 -1
  101. package/dist/modules/src/source.d.ts +3 -3
  102. package/dist/modules/src/source.d.ts.map +1 -1
  103. package/dist/modules/src/subtitles.d.ts +1 -1
  104. package/dist/modules/src/subtitles.d.ts.map +1 -1
  105. package/dist/modules/src/target.d.ts +2 -2
  106. package/dist/modules/src/target.d.ts.map +1 -1
  107. package/dist/modules/src/tsconfig.tsbuildinfo +1 -1
  108. package/dist/modules/src/wave/riff-writer.d.ts +1 -1
  109. package/dist/modules/src/wave/riff-writer.d.ts.map +1 -1
  110. package/dist/modules/src/wave/wave-demuxer.d.ts +6 -6
  111. package/dist/modules/src/wave/wave-demuxer.d.ts.map +1 -1
  112. package/dist/modules/src/wave/wave-muxer.d.ts +4 -4
  113. package/dist/modules/src/wave/wave-muxer.d.ts.map +1 -1
  114. package/dist/modules/src/writer.d.ts +1 -1
  115. package/dist/modules/src/writer.d.ts.map +1 -1
  116. package/dist/packages/eac3/eac3.wasm +0 -0
  117. package/dist/packages/eac3/mediabunny-eac3.js +1058 -0
  118. package/dist/packages/eac3/mediabunny-eac3.min.js +44 -0
  119. package/dist/packages/mp3-encoder/mediabunny-mp3-encoder.js +694 -0
  120. package/dist/packages/mp3-encoder/mediabunny-mp3-encoder.min.js +58 -0
  121. package/dist/packages/mpeg4/mediabunny-mpeg4.js +1198 -0
  122. package/dist/packages/mpeg4/mediabunny-mpeg4.min.js +44 -0
  123. package/dist/packages/mpeg4/xvid.wasm +0 -0
  124. package/package.json +18 -57
  125. package/dist/bundles/mediabunny.cjs +0 -26140
  126. package/dist/bundles/mediabunny.min.cjs +0 -147
  127. package/dist/bundles/mediabunny.min.mjs +0 -146
  128. package/dist/mediabunny.d.ts +0 -3319
  129. package/dist/modules/shared/mp3-misc.js +0 -147
  130. package/dist/modules/src/adts/adts-demuxer.js +0 -239
  131. package/dist/modules/src/adts/adts-muxer.js +0 -80
  132. package/dist/modules/src/adts/adts-reader.js +0 -63
  133. package/dist/modules/src/codec-data.js +0 -1730
  134. package/dist/modules/src/codec.js +0 -869
  135. package/dist/modules/src/conversion.js +0 -1459
  136. package/dist/modules/src/custom-coder.js +0 -117
  137. package/dist/modules/src/demuxer.js +0 -12
  138. package/dist/modules/src/encode.js +0 -442
  139. package/dist/modules/src/flac/flac-demuxer.js +0 -504
  140. package/dist/modules/src/flac/flac-misc.js +0 -135
  141. package/dist/modules/src/flac/flac-muxer.js +0 -222
  142. package/dist/modules/src/id3.js +0 -848
  143. package/dist/modules/src/index.js +0 -28
  144. package/dist/modules/src/input-format.js +0 -480
  145. package/dist/modules/src/input-track.js +0 -372
  146. package/dist/modules/src/input.js +0 -188
  147. package/dist/modules/src/isobmff/isobmff-boxes.js +0 -1480
  148. package/dist/modules/src/isobmff/isobmff-demuxer.js +0 -2618
  149. package/dist/modules/src/isobmff/isobmff-misc.js +0 -20
  150. package/dist/modules/src/isobmff/isobmff-muxer.js +0 -966
  151. package/dist/modules/src/isobmff/isobmff-reader.js +0 -72
  152. package/dist/modules/src/matroska/ebml.js +0 -653
  153. package/dist/modules/src/matroska/matroska-demuxer.js +0 -2133
  154. package/dist/modules/src/matroska/matroska-misc.js +0 -20
  155. package/dist/modules/src/matroska/matroska-muxer.js +0 -1017
  156. package/dist/modules/src/media-sink.js +0 -1736
  157. package/dist/modules/src/media-source.js +0 -1825
  158. package/dist/modules/src/metadata.js +0 -193
  159. package/dist/modules/src/misc.js +0 -623
  160. package/dist/modules/src/mp3/mp3-demuxer.js +0 -285
  161. package/dist/modules/src/mp3/mp3-muxer.js +0 -123
  162. package/dist/modules/src/mp3/mp3-reader.js +0 -26
  163. package/dist/modules/src/mp3/mp3-writer.js +0 -78
  164. package/dist/modules/src/muxer.js +0 -50
  165. package/dist/modules/src/node.d.ts +0 -9
  166. package/dist/modules/src/node.d.ts.map +0 -1
  167. package/dist/modules/src/node.js +0 -9
  168. package/dist/modules/src/ogg/ogg-demuxer.js +0 -763
  169. package/dist/modules/src/ogg/ogg-misc.js +0 -78
  170. package/dist/modules/src/ogg/ogg-muxer.js +0 -353
  171. package/dist/modules/src/ogg/ogg-reader.js +0 -65
  172. package/dist/modules/src/output-format.js +0 -527
  173. package/dist/modules/src/output.js +0 -300
  174. package/dist/modules/src/packet.js +0 -182
  175. package/dist/modules/src/pcm.js +0 -85
  176. package/dist/modules/src/reader.js +0 -236
  177. package/dist/modules/src/sample.js +0 -1056
  178. package/dist/modules/src/source.js +0 -1182
  179. package/dist/modules/src/subtitles.js +0 -575
  180. package/dist/modules/src/target.js +0 -140
  181. package/dist/modules/src/wave/riff-writer.js +0 -30
  182. package/dist/modules/src/wave/wave-demuxer.js +0 -447
  183. package/dist/modules/src/wave/wave-muxer.js +0 -318
  184. package/dist/modules/src/writer.js +0 -370
  185. package/src/adts/adts-demuxer.ts +0 -331
  186. package/src/adts/adts-muxer.ts +0 -111
  187. package/src/adts/adts-reader.ts +0 -85
  188. package/src/codec-data.ts +0 -2078
  189. package/src/codec.ts +0 -1092
  190. package/src/conversion.ts +0 -2112
  191. package/src/custom-coder.ts +0 -197
  192. package/src/demuxer.ts +0 -24
  193. package/src/encode.ts +0 -739
  194. package/src/flac/flac-demuxer.ts +0 -730
  195. package/src/flac/flac-misc.ts +0 -164
  196. package/src/flac/flac-muxer.ts +0 -320
  197. package/src/id3.ts +0 -925
  198. package/src/index.ts +0 -221
  199. package/src/input-format.ts +0 -541
  200. package/src/input-track.ts +0 -529
  201. package/src/input.ts +0 -235
  202. package/src/isobmff/isobmff-boxes.ts +0 -1719
  203. package/src/isobmff/isobmff-demuxer.ts +0 -3190
  204. package/src/isobmff/isobmff-misc.ts +0 -29
  205. package/src/isobmff/isobmff-muxer.ts +0 -1348
  206. package/src/isobmff/isobmff-reader.ts +0 -91
  207. package/src/matroska/ebml.ts +0 -730
  208. package/src/matroska/matroska-demuxer.ts +0 -2481
  209. package/src/matroska/matroska-misc.ts +0 -29
  210. package/src/matroska/matroska-muxer.ts +0 -1276
  211. package/src/media-sink.ts +0 -2179
  212. package/src/media-source.ts +0 -2243
  213. package/src/metadata.ts +0 -320
  214. package/src/misc.ts +0 -798
  215. package/src/mp3/mp3-demuxer.ts +0 -383
  216. package/src/mp3/mp3-muxer.ts +0 -166
  217. package/src/mp3/mp3-reader.ts +0 -34
  218. package/src/mp3/mp3-writer.ts +0 -120
  219. package/src/muxer.ts +0 -88
  220. package/src/node.ts +0 -11
  221. package/src/ogg/ogg-demuxer.ts +0 -1053
  222. package/src/ogg/ogg-misc.ts +0 -116
  223. package/src/ogg/ogg-muxer.ts +0 -497
  224. package/src/ogg/ogg-reader.ts +0 -93
  225. package/src/output-format.ts +0 -945
  226. package/src/output.ts +0 -488
  227. package/src/packet.ts +0 -263
  228. package/src/pcm.ts +0 -112
  229. package/src/reader.ts +0 -323
  230. package/src/sample.ts +0 -1461
  231. package/src/source.ts +0 -1688
  232. package/src/subtitles.ts +0 -711
  233. package/src/target.ts +0 -204
  234. package/src/tsconfig.json +0 -16
  235. package/src/wave/riff-writer.ts +0 -36
  236. package/src/wave/wave-demuxer.ts +0 -529
  237. package/src/wave/wave-muxer.ts +0 -371
  238. package/src/writer.ts +0 -490
package/src/codec-data.ts DELETED
@@ -1,2078 +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
-
9
- import { VideoCodec, VP9_LEVEL_TABLE } from './codec';
10
- import {
11
- assert,
12
- assertNever,
13
- base64ToBytes,
14
- Bitstream,
15
- bytesToBase64,
16
- keyValueIterator,
17
- getUint24,
18
- last,
19
- readExpGolomb,
20
- readSignedExpGolomb,
21
- textDecoder,
22
- textEncoder,
23
- toDataView,
24
- toUint8Array,
25
- } from './misc';
26
- import { PacketType } from './packet';
27
- import { MetadataTags } from './metadata';
28
-
29
- // References for AVC/HEVC code:
30
- // ISO 14496-15
31
- // Rec. ITU-T H.264
32
- // Rec. ITU-T H.265
33
- // https://stackoverflow.com/questions/24884827
34
-
35
- export enum AvcNalUnitType {
36
- IDR = 5,
37
- SPS = 7,
38
- PPS = 8,
39
- SPS_EXT = 13,
40
- }
41
-
42
- export enum HevcNalUnitType {
43
- RASL_N = 8,
44
- RASL_R = 9,
45
- BLA_W_LP = 16,
46
- RSV_IRAP_VCL23 = 23,
47
- VPS_NUT = 32,
48
- SPS_NUT = 33,
49
- PPS_NUT = 34,
50
- PREFIX_SEI_NUT = 39,
51
- SUFFIX_SEI_NUT = 40,
52
- }
53
-
54
- /** Finds all NAL units in an AVC packet in Annex B format. */
55
- export const findNalUnitsInAnnexB = (packetData: Uint8Array) => {
56
- const nalUnits: Uint8Array[] = [];
57
- let i = 0;
58
-
59
- while (i < packetData.length) {
60
- let startCodePos = -1;
61
- let startCodeLength = 0;
62
-
63
- for (let j = i; j < packetData.length - 3; j++) {
64
- // Check for 3-byte start code (0x000001)
65
- if (packetData[j] === 0 && packetData[j + 1] === 0 && packetData[j + 2] === 1) {
66
- startCodePos = j;
67
- startCodeLength = 3;
68
- break;
69
- }
70
-
71
- // Check for 4-byte start code (0x00000001)
72
- if (
73
- j < packetData.length - 4
74
- && packetData[j] === 0
75
- && packetData[j + 1] === 0
76
- && packetData[j + 2] === 0
77
- && packetData[j + 3] === 1
78
- ) {
79
- startCodePos = j;
80
- startCodeLength = 4;
81
- break;
82
- }
83
- }
84
-
85
- if (startCodePos === -1) {
86
- break; // No more start codes found
87
- }
88
-
89
- // If this isn't the first start code, extract the previous NAL unit
90
- if (i > 0 && startCodePos > i) {
91
- const nalData = packetData.subarray(i, startCodePos);
92
- if (nalData.length > 0) {
93
- nalUnits.push(nalData);
94
- }
95
- }
96
-
97
- i = startCodePos + startCodeLength;
98
- }
99
-
100
- // Extract the last NAL unit if there is one
101
- if (i < packetData.length) {
102
- const nalData = packetData.subarray(i);
103
- if (nalData.length > 0) {
104
- nalUnits.push(nalData);
105
- }
106
- }
107
-
108
- return nalUnits;
109
- };
110
-
111
- /** Finds all NAL units in an AVC packet in length-prefixed format. */
112
- const findNalUnitsInLengthPrefixed = (packetData: Uint8Array, lengthSize: 1 | 2 | 3 | 4) => {
113
- const nalUnits: Uint8Array[] = [];
114
- let offset = 0;
115
-
116
- const dataView = new DataView(packetData.buffer, packetData.byteOffset, packetData.byteLength);
117
-
118
- while (offset + lengthSize <= packetData.length) {
119
- let nalUnitLength: number;
120
- if (lengthSize === 1) {
121
- nalUnitLength = dataView.getUint8(offset);
122
- } else if (lengthSize === 2) {
123
- nalUnitLength = dataView.getUint16(offset, false);
124
- } else if (lengthSize === 3) {
125
- nalUnitLength = getUint24(dataView, offset, false);
126
- } else if (lengthSize === 4) {
127
- nalUnitLength = dataView.getUint32(offset, false);
128
- } else {
129
- assertNever(lengthSize);
130
- assert(false);
131
- }
132
-
133
- offset += lengthSize;
134
-
135
- const nalUnit = packetData.subarray(offset, offset + nalUnitLength);
136
- nalUnits.push(nalUnit);
137
-
138
- offset += nalUnitLength;
139
- }
140
-
141
- return nalUnits;
142
- };
143
-
144
- const removeEmulationPreventionBytes = (data: Uint8Array) => {
145
- const result: number[] = [];
146
- const len = data.length;
147
-
148
- for (let i = 0; i < len; i++) {
149
- // Look for the 0x000003 pattern
150
- if (i + 2 < len && data[i] === 0x00 && data[i + 1] === 0x00 && data[i + 2] === 0x03) {
151
- result.push(0x00, 0x00); // Push the first two bytes
152
- i += 2; // Skip the 0x03 byte
153
- } else {
154
- result.push(data[i]!);
155
- }
156
- }
157
-
158
- return new Uint8Array(result);
159
- };
160
-
161
- /** Converts an AVC packet in Annex B format to length-prefixed format. */
162
- export const transformAnnexBToLengthPrefixed = (packetData: Uint8Array) => {
163
- const NAL_UNIT_LENGTH_SIZE = 4;
164
-
165
- const nalUnits = findNalUnitsInAnnexB(packetData);
166
-
167
- if (nalUnits.length === 0) {
168
- // If no NAL units were found, it's not valid Annex B data
169
- return null;
170
- }
171
-
172
- let totalSize = 0;
173
- for (const nalUnit of nalUnits) {
174
- totalSize += NAL_UNIT_LENGTH_SIZE + nalUnit.byteLength;
175
- }
176
-
177
- const avccData = new Uint8Array(totalSize);
178
- const dataView = new DataView(avccData.buffer);
179
- let offset = 0;
180
-
181
- // Write each NAL unit with its length prefix
182
- for (const nalUnit of nalUnits) {
183
- const length = nalUnit.byteLength;
184
-
185
- dataView.setUint32(offset, length, false);
186
- offset += 4;
187
-
188
- avccData.set(nalUnit, offset);
189
- offset += nalUnit.byteLength;
190
- }
191
-
192
- return avccData;
193
- };
194
-
195
- // Data specified in ISO 14496-15
196
- export type AvcDecoderConfigurationRecord = {
197
- configurationVersion: number;
198
- avcProfileIndication: number;
199
- profileCompatibility: number;
200
- avcLevelIndication: number;
201
- lengthSizeMinusOne: number;
202
- sequenceParameterSets: Uint8Array[];
203
- pictureParameterSets: Uint8Array[];
204
-
205
- // Fields only for specific profiles:
206
- chromaFormat: number | null;
207
- bitDepthLumaMinus8: number | null;
208
- bitDepthChromaMinus8: number | null;
209
- sequenceParameterSetExt: Uint8Array[] | null;
210
- };
211
-
212
- export const extractAvcNalUnits = (packetData: Uint8Array, decoderConfig: VideoDecoderConfig) => {
213
- if (decoderConfig.description) {
214
- // Stream is length-prefixed. Let's extract the size of the length prefix from the decoder config
215
-
216
- const bytes = toUint8Array(decoderConfig.description);
217
- const lengthSizeMinusOne = bytes[4]! & 0b11;
218
- const lengthSize = (lengthSizeMinusOne + 1) as 1 | 2 | 3 | 4;
219
-
220
- return findNalUnitsInLengthPrefixed(packetData, lengthSize);
221
- } else {
222
- // Stream is in Annex B format
223
- return findNalUnitsInAnnexB(packetData);
224
- }
225
- };
226
-
227
- const extractNalUnitTypeForAvc = (data: Uint8Array) => {
228
- return data[0]! & 0x1F;
229
- };
230
-
231
- /** Builds an AvcDecoderConfigurationRecord from an AVC packet in Annex B format. */
232
- export const extractAvcDecoderConfigurationRecord = (packetData: Uint8Array): AvcDecoderConfigurationRecord | null => {
233
- try {
234
- const nalUnits = findNalUnitsInAnnexB(packetData);
235
-
236
- const spsUnits = nalUnits.filter(unit => extractNalUnitTypeForAvc(unit) === AvcNalUnitType.SPS);
237
- const ppsUnits = nalUnits.filter(unit => extractNalUnitTypeForAvc(unit) === AvcNalUnitType.PPS);
238
- const spsExtUnits = nalUnits.filter(unit => extractNalUnitTypeForAvc(unit) === AvcNalUnitType.SPS_EXT);
239
-
240
- if (spsUnits.length === 0) {
241
- return null;
242
- }
243
-
244
- if (ppsUnits.length === 0) {
245
- return null;
246
- }
247
-
248
- // Let's get the first SPS for profile and level information
249
- const spsData = spsUnits[0]!;
250
- const spsInfo = parseAvcSps(spsData);
251
- assert(spsInfo !== null);
252
-
253
- const hasExtendedData = spsInfo.profileIdc === 100
254
- || spsInfo.profileIdc === 110
255
- || spsInfo.profileIdc === 122
256
- || spsInfo.profileIdc === 144;
257
-
258
- return {
259
- configurationVersion: 1,
260
- avcProfileIndication: spsInfo.profileIdc,
261
- profileCompatibility: spsInfo.constraintFlags,
262
- avcLevelIndication: spsInfo.levelIdc,
263
- lengthSizeMinusOne: 3, // Typically 4 bytes for length field
264
- sequenceParameterSets: spsUnits,
265
- pictureParameterSets: ppsUnits,
266
- chromaFormat: hasExtendedData ? spsInfo.chromaFormatIdc : null,
267
- bitDepthLumaMinus8: hasExtendedData ? spsInfo.bitDepthLumaMinus8 : null,
268
- bitDepthChromaMinus8: hasExtendedData ? spsInfo.bitDepthChromaMinus8 : null,
269
- sequenceParameterSetExt: hasExtendedData ? spsExtUnits : null,
270
- };
271
- } catch (error) {
272
- console.error('Error building AVC Decoder Configuration Record:', error);
273
- return null;
274
- }
275
- };
276
-
277
- /** Serializes an AvcDecoderConfigurationRecord into the format specified in Section 5.3.3.1 of ISO 14496-15. */
278
- export const serializeAvcDecoderConfigurationRecord = (record: AvcDecoderConfigurationRecord) => {
279
- const bytes: number[] = [];
280
-
281
- // Write header
282
- bytes.push(record.configurationVersion);
283
- bytes.push(record.avcProfileIndication);
284
- bytes.push(record.profileCompatibility);
285
- bytes.push(record.avcLevelIndication);
286
- bytes.push(0xFC | (record.lengthSizeMinusOne & 0x03)); // Reserved bits (6) + lengthSizeMinusOne (2)
287
-
288
- // Reserved bits (3) + numOfSequenceParameterSets (5)
289
- bytes.push(0xE0 | (record.sequenceParameterSets.length & 0x1F));
290
-
291
- // Write SPS
292
- for (const sps of record.sequenceParameterSets) {
293
- const length = sps.byteLength;
294
- bytes.push(length >> 8); // High byte
295
- bytes.push(length & 0xFF); // Low byte
296
-
297
- for (let i = 0; i < length; i++) {
298
- bytes.push(sps[i]!);
299
- }
300
- }
301
-
302
- bytes.push(record.pictureParameterSets.length);
303
-
304
- // Write PPS
305
- for (const pps of record.pictureParameterSets) {
306
- const length = pps.byteLength;
307
- bytes.push(length >> 8); // High byte
308
- bytes.push(length & 0xFF); // Low byte
309
-
310
- for (let i = 0; i < length; i++) {
311
- bytes.push(pps[i]!);
312
- }
313
- }
314
-
315
- if (
316
- record.avcProfileIndication === 100
317
- || record.avcProfileIndication === 110
318
- || record.avcProfileIndication === 122
319
- || record.avcProfileIndication === 144
320
- ) {
321
- assert(record.chromaFormat !== null);
322
- assert(record.bitDepthLumaMinus8 !== null);
323
- assert(record.bitDepthChromaMinus8 !== null);
324
- assert(record.sequenceParameterSetExt !== null);
325
-
326
- bytes.push(0xFC | (record.chromaFormat & 0x03)); // Reserved bits + chroma_format
327
- bytes.push(0xF8 | (record.bitDepthLumaMinus8 & 0x07)); // Reserved bits + bit_depth_luma_minus8
328
- bytes.push(0xF8 | (record.bitDepthChromaMinus8 & 0x07)); // Reserved bits + bit_depth_chroma_minus8
329
-
330
- bytes.push(record.sequenceParameterSetExt.length);
331
-
332
- // Write SPS Ext
333
- for (const spsExt of record.sequenceParameterSetExt) {
334
- const length = spsExt.byteLength;
335
- bytes.push(length >> 8); // High byte
336
- bytes.push(length & 0xFF); // Low byte
337
-
338
- for (let i = 0; i < length; i++) {
339
- bytes.push(spsExt[i]!);
340
- }
341
- }
342
- }
343
-
344
- return new Uint8Array(bytes);
345
- };
346
-
347
- /** Deserializes an AvcDecoderConfigurationRecord from the format specified in Section 5.3.3.1 of ISO 14496-15. */
348
- export const deserializeAvcDecoderConfigurationRecord = (data: Uint8Array): AvcDecoderConfigurationRecord | null => {
349
- try {
350
- const view = toDataView(data);
351
- let offset = 0;
352
-
353
- // Read header
354
- const configurationVersion = view.getUint8(offset++);
355
- const avcProfileIndication = view.getUint8(offset++);
356
- const profileCompatibility = view.getUint8(offset++);
357
- const avcLevelIndication = view.getUint8(offset++);
358
- const lengthSizeMinusOne = view.getUint8(offset++) & 0x03;
359
-
360
- const numOfSequenceParameterSets = view.getUint8(offset++) & 0x1F;
361
-
362
- // Read SPS
363
- const sequenceParameterSets: Uint8Array[] = [];
364
- for (let i = 0; i < numOfSequenceParameterSets; i++) {
365
- const length = view.getUint16(offset, false);
366
- offset += 2;
367
-
368
- sequenceParameterSets.push(data.subarray(offset, offset + length));
369
- offset += length;
370
- }
371
-
372
- const numOfPictureParameterSets = view.getUint8(offset++);
373
-
374
- // Read PPS
375
- const pictureParameterSets: Uint8Array[] = [];
376
- for (let i = 0; i < numOfPictureParameterSets; i++) {
377
- const length = view.getUint16(offset, false);
378
- offset += 2;
379
-
380
- pictureParameterSets.push(data.subarray(offset, offset + length));
381
- offset += length;
382
- }
383
-
384
- const record: AvcDecoderConfigurationRecord = {
385
- configurationVersion,
386
- avcProfileIndication,
387
- profileCompatibility,
388
- avcLevelIndication,
389
- lengthSizeMinusOne,
390
- sequenceParameterSets,
391
- pictureParameterSets,
392
- chromaFormat: null,
393
- bitDepthLumaMinus8: null,
394
- bitDepthChromaMinus8: null,
395
- sequenceParameterSetExt: null,
396
- };
397
-
398
- // Check if there are extended profile fields
399
- if (
400
- (
401
- avcProfileIndication === 100
402
- || avcProfileIndication === 110
403
- || avcProfileIndication === 122
404
- || avcProfileIndication === 144
405
- )
406
- && offset + 4 <= data.length
407
- ) {
408
- const chromaFormat = view.getUint8(offset++) & 0x03;
409
- const bitDepthLumaMinus8 = view.getUint8(offset++) & 0x07;
410
- const bitDepthChromaMinus8 = view.getUint8(offset++) & 0x07;
411
- const numOfSequenceParameterSetExt = view.getUint8(offset++);
412
-
413
- record.chromaFormat = chromaFormat;
414
- record.bitDepthLumaMinus8 = bitDepthLumaMinus8;
415
- record.bitDepthChromaMinus8 = bitDepthChromaMinus8;
416
-
417
- // Read SPS Ext
418
- const sequenceParameterSetExt: Uint8Array[] = [];
419
- for (let i = 0; i < numOfSequenceParameterSetExt; i++) {
420
- const length = view.getUint16(offset, false);
421
- offset += 2;
422
-
423
- sequenceParameterSetExt.push(data.subarray(offset, offset + length));
424
- offset += length;
425
- }
426
-
427
- record.sequenceParameterSetExt = sequenceParameterSetExt;
428
- }
429
-
430
- return record;
431
- } catch (error) {
432
- console.error('Error deserializing AVC Decoder Configuration Record:', error);
433
- return null;
434
- }
435
- };
436
-
437
- export type AvcSpsInfo = {
438
- profileIdc: number;
439
- constraintFlags: number;
440
- levelIdc: number;
441
- frameMbsOnlyFlag: number;
442
- chromaFormatIdc: number | null;
443
- bitDepthLumaMinus8: number | null;
444
- bitDepthChromaMinus8: number | null;
445
- };
446
-
447
- /** Parses an AVC SPS (Sequence Parameter Set) to extract basic information. */
448
- export const parseAvcSps = (sps: Uint8Array): AvcSpsInfo | null => {
449
- try {
450
- const bitstream = new Bitstream(removeEmulationPreventionBytes(sps));
451
-
452
- bitstream.skipBits(1); // forbidden_zero_bit
453
- bitstream.skipBits(2); // nal_ref_idc
454
- const nalUnitType = bitstream.readBits(5);
455
-
456
- if (nalUnitType !== 7) { // SPS NAL unit type is 7
457
- return null;
458
- }
459
-
460
- const profileIdc = bitstream.readAlignedByte();
461
- const constraintFlags = bitstream.readAlignedByte();
462
- const levelIdc = bitstream.readAlignedByte();
463
-
464
- readExpGolomb(bitstream); // seq_parameter_set_id
465
-
466
- let chromaFormatIdc: number | null = null;
467
- let bitDepthLumaMinus8: number | null = null;
468
- let bitDepthChromaMinus8: number | null = null;
469
-
470
- // Handle high profile chroma_format_idc
471
- if (
472
- profileIdc === 100
473
- || profileIdc === 110
474
- || profileIdc === 122
475
- || profileIdc === 244
476
- || profileIdc === 44
477
- || profileIdc === 83
478
- || profileIdc === 86
479
- || profileIdc === 118
480
- || profileIdc === 128
481
- ) {
482
- chromaFormatIdc = readExpGolomb(bitstream);
483
- if (chromaFormatIdc === 3) {
484
- bitstream.skipBits(1); // separate_colour_plane_flag
485
- }
486
- bitDepthLumaMinus8 = readExpGolomb(bitstream);
487
- bitDepthChromaMinus8 = readExpGolomb(bitstream);
488
- bitstream.skipBits(1); // qpprime_y_zero_transform_bypass_flag
489
- const seqScalingMatrixPresentFlag = bitstream.readBits(1);
490
- if (seqScalingMatrixPresentFlag) {
491
- for (let i = 0; i < (chromaFormatIdc !== 3 ? 8 : 12); i++) {
492
- const seqScalingListPresentFlag = bitstream.readBits(1);
493
- if (seqScalingListPresentFlag) {
494
- const sizeOfScalingList = i < 6 ? 16 : 64;
495
- let lastScale = 8;
496
- let nextScale = 8;
497
- for (let j = 0; j < sizeOfScalingList; j++) {
498
- if (nextScale !== 0) {
499
- const deltaScale = readSignedExpGolomb(bitstream);
500
- nextScale = (lastScale + deltaScale + 256) % 256;
501
- }
502
- lastScale = nextScale === 0 ? lastScale : nextScale;
503
- }
504
- }
505
- }
506
- }
507
- }
508
-
509
- readExpGolomb(bitstream); // log2_max_frame_num_minus4
510
-
511
- const picOrderCntType = readExpGolomb(bitstream);
512
- if (picOrderCntType === 0) {
513
- readExpGolomb(bitstream); // log2_max_pic_order_cnt_lsb_minus4
514
- } else if (picOrderCntType === 1) {
515
- bitstream.skipBits(1); // delta_pic_order_always_zero_flag
516
- readSignedExpGolomb(bitstream); // offset_for_non_ref_pic
517
- readSignedExpGolomb(bitstream); // offset_for_top_to_bottom_field
518
- const numRefFramesInPicOrderCntCycle = readExpGolomb(bitstream);
519
- for (let i = 0; i < numRefFramesInPicOrderCntCycle; i++) {
520
- readSignedExpGolomb(bitstream); // offset_for_ref_frame[i]
521
- }
522
- }
523
-
524
- readExpGolomb(bitstream); // max_num_ref_frames
525
- bitstream.skipBits(1); // gaps_in_frame_num_value_allowed_flag
526
-
527
- readExpGolomb(bitstream); // pic_width_in_mbs_minus1
528
- readExpGolomb(bitstream); // pic_height_in_map_units_minus1
529
-
530
- const frameMbsOnlyFlag = bitstream.readBits(1);
531
-
532
- return {
533
- profileIdc,
534
- constraintFlags,
535
- levelIdc,
536
- frameMbsOnlyFlag,
537
- chromaFormatIdc,
538
- bitDepthLumaMinus8,
539
- bitDepthChromaMinus8,
540
- };
541
- } catch (error) {
542
- console.error('Error parsing AVC SPS:', error);
543
- return null;
544
- }
545
- };
546
-
547
- // Data specified in ISO 14496-15
548
- export type HevcDecoderConfigurationRecord = {
549
- configurationVersion: number;
550
- generalProfileSpace: number;
551
- generalTierFlag: number;
552
- generalProfileIdc: number;
553
- generalProfileCompatibilityFlags: number;
554
- generalConstraintIndicatorFlags: Uint8Array; // 6 bytes long
555
- generalLevelIdc: number;
556
- minSpatialSegmentationIdc: number;
557
- parallelismType: number;
558
- chromaFormatIdc: number;
559
- bitDepthLumaMinus8: number;
560
- bitDepthChromaMinus8: number;
561
- avgFrameRate: number;
562
- constantFrameRate: number;
563
- numTemporalLayers: number;
564
- temporalIdNested: number;
565
- lengthSizeMinusOne: number;
566
- arrays: {
567
- arrayCompleteness: number;
568
- nalUnitType: number;
569
- nalUnits: Uint8Array[];
570
- }[];
571
- };
572
-
573
- export const extractHevcNalUnits = (packetData: Uint8Array, decoderConfig: VideoDecoderConfig) => {
574
- if (decoderConfig.description) {
575
- // Stream is length-prefixed. Let's extract the size of the length prefix from the decoder config
576
-
577
- const bytes = toUint8Array(decoderConfig.description);
578
- const lengthSizeMinusOne = bytes[21]! & 0b11;
579
- const lengthSize = (lengthSizeMinusOne + 1) as 1 | 2 | 3 | 4;
580
-
581
- return findNalUnitsInLengthPrefixed(packetData, lengthSize);
582
- } else {
583
- // Stream is in Annex B format
584
- return findNalUnitsInAnnexB(packetData);
585
- }
586
- };
587
-
588
- export const extractNalUnitTypeForHevc = (data: Uint8Array) => {
589
- return (data[0]! >> 1) & 0x3F;
590
- };
591
-
592
- /** Builds a HevcDecoderConfigurationRecord from an HEVC packet in Annex B format. */
593
- export const extractHevcDecoderConfigurationRecord = (
594
- packetData: Uint8Array,
595
- ) => {
596
- try {
597
- const nalUnits = findNalUnitsInAnnexB(packetData);
598
-
599
- const vpsUnits = nalUnits.filter(unit => extractNalUnitTypeForHevc(unit) === HevcNalUnitType.VPS_NUT);
600
- const spsUnits = nalUnits.filter(unit => extractNalUnitTypeForHevc(unit) === HevcNalUnitType.SPS_NUT);
601
- const ppsUnits = nalUnits.filter(unit => extractNalUnitTypeForHevc(unit) === HevcNalUnitType.PPS_NUT);
602
- const seiUnits = nalUnits.filter(
603
- unit => extractNalUnitTypeForHevc(unit) === HevcNalUnitType.PREFIX_SEI_NUT
604
- || extractNalUnitTypeForHevc(unit) === HevcNalUnitType.SUFFIX_SEI_NUT,
605
- );
606
-
607
- if (spsUnits.length === 0 || ppsUnits.length === 0) return null;
608
-
609
- const sps = spsUnits[0]!;
610
- const bitstream = new Bitstream(removeEmulationPreventionBytes(sps));
611
-
612
- bitstream.skipBits(16); // NAL header
613
-
614
- bitstream.readBits(4); // sps_video_parameter_set_id
615
- const sps_max_sub_layers_minus1 = bitstream.readBits(3);
616
- const sps_temporal_id_nesting_flag = bitstream.readBits(1);
617
-
618
- const {
619
- general_profile_space,
620
- general_tier_flag,
621
- general_profile_idc,
622
- general_profile_compatibility_flags,
623
- general_constraint_indicator_flags,
624
- general_level_idc,
625
- } = parseProfileTierLevel(bitstream, sps_max_sub_layers_minus1);
626
-
627
- readExpGolomb(bitstream); // sps_seq_parameter_set_id
628
-
629
- const chroma_format_idc = readExpGolomb(bitstream);
630
- if (chroma_format_idc === 3) bitstream.skipBits(1); // separate_colour_plane_flag
631
-
632
- readExpGolomb(bitstream); // pic_width_in_luma_samples
633
- readExpGolomb(bitstream); // pic_height_in_luma_samples
634
-
635
- if (bitstream.readBits(1)) { // conformance_window_flag
636
- readExpGolomb(bitstream); // conf_win_left_offset
637
- readExpGolomb(bitstream); // conf_win_right_offset
638
- readExpGolomb(bitstream); // conf_win_top_offset
639
- readExpGolomb(bitstream); // conf_win_bottom_offset
640
- }
641
-
642
- const bit_depth_luma_minus8 = readExpGolomb(bitstream);
643
- const bit_depth_chroma_minus8 = readExpGolomb(bitstream);
644
-
645
- readExpGolomb(bitstream); // log2_max_pic_order_cnt_lsb_minus4
646
-
647
- const sps_sub_layer_ordering_info_present_flag = bitstream.readBits(1);
648
- const maxNum = sps_sub_layer_ordering_info_present_flag ? 0 : sps_max_sub_layers_minus1;
649
- for (let i = maxNum; i <= sps_max_sub_layers_minus1; i++) {
650
- readExpGolomb(bitstream); // sps_max_dec_pic_buffering_minus1[i]
651
- readExpGolomb(bitstream); // sps_max_num_reorder_pics[i]
652
- readExpGolomb(bitstream); // sps_max_latency_increase_plus1[i]
653
- }
654
-
655
- readExpGolomb(bitstream); // log2_min_luma_coding_block_size_minus3
656
- readExpGolomb(bitstream); // log2_diff_max_min_luma_coding_block_size
657
- readExpGolomb(bitstream); // log2_min_luma_transform_block_size_minus2
658
- readExpGolomb(bitstream); // log2_diff_max_min_luma_transform_block_size
659
- readExpGolomb(bitstream); // max_transform_hierarchy_depth_inter
660
- readExpGolomb(bitstream); // max_transform_hierarchy_depth_intra
661
-
662
- if (bitstream.readBits(1)) { // scaling_list_enabled_flag
663
- if (bitstream.readBits(1)) {
664
- skipScalingListData(bitstream);
665
- }
666
- }
667
-
668
- bitstream.skipBits(1); // amp_enabled_flag
669
- bitstream.skipBits(1); // sample_adaptive_offset_enabled_flag
670
-
671
- if (bitstream.readBits(1)) { // pcm_enabled_flag
672
- bitstream.skipBits(4); // pcm_sample_bit_depth_luma_minus1
673
- bitstream.skipBits(4); // pcm_sample_bit_depth_chroma_minus1
674
- readExpGolomb(bitstream); // log2_min_pcm_luma_coding_block_size_minus3
675
- readExpGolomb(bitstream); // log2_diff_max_min_pcm_luma_coding_block_size
676
- bitstream.skipBits(1); // pcm_loop_filter_disabled_flag
677
- }
678
-
679
- const num_short_term_ref_pic_sets = readExpGolomb(bitstream);
680
- skipAllStRefPicSets(bitstream, num_short_term_ref_pic_sets);
681
-
682
- if (bitstream.readBits(1)) { // long_term_ref_pics_present_flag
683
- const num_long_term_ref_pics_sps = readExpGolomb(bitstream);
684
- for (let i = 0; i < num_long_term_ref_pics_sps; i++) {
685
- readExpGolomb(bitstream); // lt_ref_pic_poc_lsb_sps[i]
686
- bitstream.skipBits(1); // used_by_curr_pic_lt_sps_flag[i]
687
- }
688
- }
689
-
690
- bitstream.skipBits(1); // sps_temporal_mvp_enabled_flag
691
- bitstream.skipBits(1); // strong_intra_smoothing_enabled_flag
692
-
693
- let min_spatial_segmentation_idc = 0;
694
- if (bitstream.readBits(1)) { // vui_parameters_present_flag
695
- min_spatial_segmentation_idc = parseVuiForMinSpatialSegmentationIdc(bitstream, sps_max_sub_layers_minus1);
696
- }
697
-
698
- // Parse PPS for parallelismType
699
- let parallelismType = 0;
700
- if (ppsUnits.length > 0) {
701
- const pps = ppsUnits[0]!;
702
- const ppsBitstream = new Bitstream(removeEmulationPreventionBytes(pps));
703
-
704
- ppsBitstream.skipBits(16); // NAL header
705
- readExpGolomb(ppsBitstream); // pps_pic_parameter_set_id
706
- readExpGolomb(ppsBitstream); // pps_seq_parameter_set_id
707
- ppsBitstream.skipBits(1); // dependent_slice_segments_enabled_flag
708
- ppsBitstream.skipBits(1); // output_flag_present_flag
709
- ppsBitstream.skipBits(3); // num_extra_slice_header_bits
710
- ppsBitstream.skipBits(1); // sign_data_hiding_enabled_flag
711
- ppsBitstream.skipBits(1); // cabac_init_present_flag
712
- readExpGolomb(ppsBitstream); // num_ref_idx_l0_default_active_minus1
713
- readExpGolomb(ppsBitstream); // num_ref_idx_l1_default_active_minus1
714
- readSignedExpGolomb(ppsBitstream); // init_qp_minus26
715
- ppsBitstream.skipBits(1); // constrained_intra_pred_flag
716
- ppsBitstream.skipBits(1); // transform_skip_enabled_flag
717
- if (ppsBitstream.readBits(1)) { // cu_qp_delta_enabled_flag
718
- readExpGolomb(ppsBitstream); // diff_cu_qp_delta_depth
719
- }
720
- readSignedExpGolomb(ppsBitstream); // pps_cb_qp_offset
721
- readSignedExpGolomb(ppsBitstream); // pps_cr_qp_offset
722
- ppsBitstream.skipBits(1); // pps_slice_chroma_qp_offsets_present_flag
723
- ppsBitstream.skipBits(1); // weighted_pred_flag
724
- ppsBitstream.skipBits(1); // weighted_bipred_flag
725
- ppsBitstream.skipBits(1); // transquant_bypass_enabled_flag
726
- const tiles_enabled_flag = ppsBitstream.readBits(1);
727
- const entropy_coding_sync_enabled_flag = ppsBitstream.readBits(1);
728
-
729
- if (!tiles_enabled_flag && !entropy_coding_sync_enabled_flag) parallelismType = 0;
730
- else if (tiles_enabled_flag && !entropy_coding_sync_enabled_flag) parallelismType = 2;
731
- else if (!tiles_enabled_flag && entropy_coding_sync_enabled_flag) parallelismType = 3;
732
- else parallelismType = 0;
733
- }
734
-
735
- const arrays = [
736
- ...(vpsUnits.length
737
- ? [
738
- {
739
- arrayCompleteness: 1,
740
- nalUnitType: HevcNalUnitType.VPS_NUT,
741
- nalUnits: vpsUnits,
742
- },
743
- ]
744
- : []),
745
- ...(spsUnits.length
746
- ? [
747
- {
748
- arrayCompleteness: 1,
749
- nalUnitType: HevcNalUnitType.SPS_NUT,
750
- nalUnits: spsUnits,
751
- },
752
- ]
753
- : []),
754
- ...(ppsUnits.length
755
- ? [
756
- {
757
- arrayCompleteness: 1,
758
- nalUnitType: HevcNalUnitType.PPS_NUT,
759
- nalUnits: ppsUnits,
760
- },
761
- ]
762
- : []),
763
- ...(seiUnits.length
764
- ? [
765
- {
766
- arrayCompleteness: 1,
767
- nalUnitType: extractNalUnitTypeForHevc(seiUnits[0]!),
768
- nalUnits: seiUnits,
769
- },
770
- ]
771
- : []),
772
- ];
773
-
774
- const record: HevcDecoderConfigurationRecord = {
775
- configurationVersion: 1,
776
- generalProfileSpace: general_profile_space,
777
- generalTierFlag: general_tier_flag,
778
- generalProfileIdc: general_profile_idc,
779
- generalProfileCompatibilityFlags: general_profile_compatibility_flags,
780
- generalConstraintIndicatorFlags: general_constraint_indicator_flags,
781
- generalLevelIdc: general_level_idc,
782
- minSpatialSegmentationIdc: min_spatial_segmentation_idc,
783
- parallelismType,
784
- chromaFormatIdc: chroma_format_idc,
785
- bitDepthLumaMinus8: bit_depth_luma_minus8,
786
- bitDepthChromaMinus8: bit_depth_chroma_minus8,
787
- avgFrameRate: 0,
788
- constantFrameRate: 0,
789
- numTemporalLayers: sps_max_sub_layers_minus1 + 1,
790
- temporalIdNested: sps_temporal_id_nesting_flag,
791
- lengthSizeMinusOne: 3,
792
- arrays,
793
- };
794
-
795
- return record;
796
- } catch (error) {
797
- console.error('Error building HEVC Decoder Configuration Record:', error);
798
- return null;
799
- }
800
- };
801
-
802
- const parseProfileTierLevel = (
803
- bitstream: Bitstream,
804
- maxNumSubLayersMinus1: number,
805
- ) => {
806
- const general_profile_space = bitstream.readBits(2);
807
- const general_tier_flag = bitstream.readBits(1);
808
- const general_profile_idc = bitstream.readBits(5);
809
-
810
- let general_profile_compatibility_flags = 0;
811
- for (let i = 0; i < 32; i++) {
812
- general_profile_compatibility_flags = (general_profile_compatibility_flags << 1) | bitstream.readBits(1);
813
- }
814
-
815
- const general_constraint_indicator_flags = new Uint8Array(6);
816
- for (let i = 0; i < 6; i++) {
817
- general_constraint_indicator_flags[i] = bitstream.readBits(8);
818
- }
819
-
820
- const general_level_idc = bitstream.readBits(8);
821
-
822
- const sub_layer_profile_present_flag: number[] = [];
823
- const sub_layer_level_present_flag: number[] = [];
824
- for (let i = 0; i < maxNumSubLayersMinus1; i++) {
825
- sub_layer_profile_present_flag.push(bitstream.readBits(1));
826
- sub_layer_level_present_flag.push(bitstream.readBits(1));
827
- }
828
- if (maxNumSubLayersMinus1 > 0) {
829
- for (let i = maxNumSubLayersMinus1; i < 8; i++) {
830
- bitstream.skipBits(2); // reserved_zero_2bits
831
- }
832
- }
833
- for (let i = 0; i < maxNumSubLayersMinus1; i++) {
834
- if (sub_layer_profile_present_flag[i]) bitstream.skipBits(88);
835
- if (sub_layer_level_present_flag[i]) bitstream.skipBits(8);
836
- }
837
-
838
- return {
839
- general_profile_space,
840
- general_tier_flag,
841
- general_profile_idc,
842
- general_profile_compatibility_flags,
843
- general_constraint_indicator_flags,
844
- general_level_idc,
845
- };
846
- };
847
-
848
- const skipScalingListData = (bitstream: Bitstream) => {
849
- for (let sizeId = 0; sizeId < 4; sizeId++) {
850
- for (let matrixId = 0; matrixId < (sizeId === 3 ? 2 : 6); matrixId++) {
851
- const scaling_list_pred_mode_flag = bitstream.readBits(1);
852
- if (!scaling_list_pred_mode_flag) {
853
- readExpGolomb(bitstream); // scaling_list_pred_matrix_id_delta
854
- } else {
855
- const coefNum = Math.min(64, 1 << (4 + (sizeId << 1)));
856
- if (sizeId > 1) {
857
- readSignedExpGolomb(bitstream); // scaling_list_dc_coef_minus8
858
- }
859
- for (let i = 0; i < coefNum; i++) {
860
- readSignedExpGolomb(bitstream); // scaling_list_delta_coef
861
- }
862
- }
863
- }
864
- }
865
- };
866
-
867
- const skipAllStRefPicSets = (bitstream: Bitstream, num_short_term_ref_pic_sets: number) => {
868
- const NumDeltaPocs: number[] = [];
869
- for (let stRpsIdx = 0; stRpsIdx < num_short_term_ref_pic_sets; stRpsIdx++) {
870
- NumDeltaPocs[stRpsIdx] = skipStRefPicSet(bitstream, stRpsIdx, num_short_term_ref_pic_sets, NumDeltaPocs);
871
- }
872
- };
873
-
874
- const skipStRefPicSet = (
875
- bitstream: Bitstream,
876
- stRpsIdx: number,
877
- num_short_term_ref_pic_sets: number,
878
- NumDeltaPocs: number[],
879
- ) => {
880
- let NumDeltaPocsThis = 0;
881
- let inter_ref_pic_set_prediction_flag = 0;
882
- let RefRpsIdx = 0;
883
-
884
- if (stRpsIdx !== 0) {
885
- inter_ref_pic_set_prediction_flag = bitstream.readBits(1);
886
- }
887
- if (inter_ref_pic_set_prediction_flag) {
888
- if (stRpsIdx === num_short_term_ref_pic_sets) {
889
- const delta_idx_minus1 = readExpGolomb(bitstream);
890
- RefRpsIdx = stRpsIdx - (delta_idx_minus1 + 1);
891
- } else {
892
- RefRpsIdx = stRpsIdx - 1;
893
- }
894
- bitstream.readBits(1); // delta_rps_sign
895
- readExpGolomb(bitstream); // abs_delta_rps_minus1
896
-
897
- // The number of iterations is NumDeltaPocs[RefRpsIdx] + 1
898
- const numDelta = NumDeltaPocs[RefRpsIdx] ?? 0;
899
- for (let j = 0; j <= numDelta; j++) {
900
- const used_by_curr_pic_flag = bitstream.readBits(1);
901
- if (!used_by_curr_pic_flag) {
902
- bitstream.readBits(1); // use_delta_flag
903
- }
904
- }
905
- NumDeltaPocsThis = NumDeltaPocs[RefRpsIdx]!;
906
- } else {
907
- const num_negative_pics = readExpGolomb(bitstream);
908
- const num_positive_pics = readExpGolomb(bitstream);
909
-
910
- for (let i = 0; i < num_negative_pics; i++) {
911
- readExpGolomb(bitstream); // delta_poc_s0_minus1[i]
912
- bitstream.readBits(1); // used_by_curr_pic_s0_flag[i]
913
- }
914
- for (let i = 0; i < num_positive_pics; i++) {
915
- readExpGolomb(bitstream); // delta_poc_s1_minus1[i]
916
- bitstream.readBits(1); // used_by_curr_pic_s1_flag[i]
917
- }
918
- NumDeltaPocsThis = num_negative_pics + num_positive_pics;
919
- }
920
- return NumDeltaPocsThis;
921
- };
922
-
923
- const parseVuiForMinSpatialSegmentationIdc = (bitstream: Bitstream, sps_max_sub_layers_minus1: number) => {
924
- if (bitstream.readBits(1)) { // aspect_ratio_info_present_flag
925
- const aspect_ratio_idc = bitstream.readBits(8);
926
- if (aspect_ratio_idc === 255) {
927
- bitstream.readBits(16); // sar_width
928
- bitstream.readBits(16); // sar_height
929
- }
930
- }
931
- if (bitstream.readBits(1)) { // overscan_info_present_flag
932
- bitstream.readBits(1); // overscan_appropriate_flag
933
- }
934
- if (bitstream.readBits(1)) { // video_signal_type_present_flag
935
- bitstream.readBits(3); // video_format
936
- bitstream.readBits(1); // video_full_range_flag
937
- if (bitstream.readBits(1)) {
938
- bitstream.readBits(8); // colour_primaries
939
- bitstream.readBits(8); // transfer_characteristics
940
- bitstream.readBits(8); // matrix_coeffs
941
- }
942
- }
943
- if (bitstream.readBits(1)) { // chroma_loc_info_present_flag
944
- readExpGolomb(bitstream); // chroma_sample_loc_type_top_field
945
- readExpGolomb(bitstream); // chroma_sample_loc_type_bottom_field
946
- }
947
- bitstream.readBits(1); // neutral_chroma_indication_flag
948
- bitstream.readBits(1); // field_seq_flag
949
- bitstream.readBits(1); // frame_field_info_present_flag
950
- if (bitstream.readBits(1)) { // default_display_window_flag
951
- readExpGolomb(bitstream); // def_disp_win_left_offset
952
- readExpGolomb(bitstream); // def_disp_win_right_offset
953
- readExpGolomb(bitstream); // def_disp_win_top_offset
954
- readExpGolomb(bitstream); // def_disp_win_bottom_offset
955
- }
956
- if (bitstream.readBits(1)) { // vui_timing_info_present_flag
957
- bitstream.readBits(32); // vui_num_units_in_tick
958
- bitstream.readBits(32); // vui_time_scale
959
- if (bitstream.readBits(1)) { // vui_poc_proportional_to_timing_flag
960
- readExpGolomb(bitstream); // vui_num_ticks_poc_diff_one_minus1
961
- }
962
- if (bitstream.readBits(1)) {
963
- skipHrdParameters(bitstream, true, sps_max_sub_layers_minus1);
964
- }
965
- }
966
- if (bitstream.readBits(1)) { // bitstream_restriction_flag
967
- bitstream.readBits(1); // tiles_fixed_structure_flag
968
- bitstream.readBits(1); // motion_vectors_over_pic_boundaries_flag
969
- bitstream.readBits(1); // restricted_ref_pic_lists_flag
970
- const min_spatial_segmentation_idc = readExpGolomb(bitstream);
971
- // skip the rest
972
- readExpGolomb(bitstream); // max_bytes_per_pic_denom
973
- readExpGolomb(bitstream); // max_bits_per_min_cu_denom
974
- readExpGolomb(bitstream); // log2_max_mv_length_horizontal
975
- readExpGolomb(bitstream); // log2_max_mv_length_vertical
976
- return min_spatial_segmentation_idc;
977
- }
978
- return 0;
979
- };
980
-
981
- const skipHrdParameters = (
982
- bitstream: Bitstream,
983
- commonInfPresentFlag: boolean,
984
- maxNumSubLayersMinus1: number,
985
- ) => {
986
- let nal_hrd_parameters_present_flag = false;
987
- let vcl_hrd_parameters_present_flag = false;
988
- let sub_pic_hrd_params_present_flag = false;
989
-
990
- if (commonInfPresentFlag) {
991
- nal_hrd_parameters_present_flag = bitstream.readBits(1) === 1;
992
- vcl_hrd_parameters_present_flag = bitstream.readBits(1) === 1;
993
- if (nal_hrd_parameters_present_flag || vcl_hrd_parameters_present_flag) {
994
- sub_pic_hrd_params_present_flag = bitstream.readBits(1) === 1;
995
- if (sub_pic_hrd_params_present_flag) {
996
- bitstream.readBits(8); // tick_divisor_minus2
997
- bitstream.readBits(5); // du_cpb_removal_delay_increment_length_minus1
998
- bitstream.readBits(1); // sub_pic_cpb_params_in_pic_timing_sei_flag
999
- bitstream.readBits(5); // dpb_output_delay_du_length_minus1
1000
- }
1001
- bitstream.readBits(4); // bit_rate_scale
1002
- bitstream.readBits(4); // cpb_size_scale
1003
- if (sub_pic_hrd_params_present_flag) {
1004
- bitstream.readBits(4); // cpb_size_du_scale
1005
- }
1006
- bitstream.readBits(5); // initial_cpb_removal_delay_length_minus1
1007
- bitstream.readBits(5); // au_cpb_removal_delay_length_minus1
1008
- bitstream.readBits(5); // dpb_output_delay_length_minus1
1009
- }
1010
- }
1011
-
1012
- for (let i = 0; i <= maxNumSubLayersMinus1; i++) {
1013
- const fixed_pic_rate_general_flag = bitstream.readBits(1) === 1;
1014
- let fixed_pic_rate_within_cvs_flag = true; // Default assumption if general is true
1015
- if (!fixed_pic_rate_general_flag) {
1016
- fixed_pic_rate_within_cvs_flag = bitstream.readBits(1) === 1;
1017
- }
1018
-
1019
- let low_delay_hrd_flag = false; // Default assumption
1020
- if (fixed_pic_rate_within_cvs_flag) {
1021
- readExpGolomb(bitstream); // elemental_duration_in_tc_minus1[i]
1022
- } else {
1023
- low_delay_hrd_flag = bitstream.readBits(1) === 1;
1024
- }
1025
-
1026
- let CpbCnt = 1; // Default if low_delay is true
1027
- if (!low_delay_hrd_flag) {
1028
- const cpb_cnt_minus1 = readExpGolomb(bitstream); // cpb_cnt_minus1[i]
1029
- CpbCnt = cpb_cnt_minus1 + 1;
1030
- }
1031
-
1032
- if (nal_hrd_parameters_present_flag) {
1033
- skipSubLayerHrdParameters(bitstream, CpbCnt, sub_pic_hrd_params_present_flag);
1034
- }
1035
- if (vcl_hrd_parameters_present_flag) {
1036
- skipSubLayerHrdParameters(bitstream, CpbCnt, sub_pic_hrd_params_present_flag);
1037
- }
1038
- }
1039
- };
1040
-
1041
- const skipSubLayerHrdParameters = (
1042
- bitstream: Bitstream,
1043
- CpbCnt: number,
1044
- sub_pic_hrd_params_present_flag: boolean,
1045
- ) => {
1046
- for (let i = 0; i < CpbCnt; i++) {
1047
- readExpGolomb(bitstream); // bit_rate_value_minus1[i]
1048
- readExpGolomb(bitstream); // cpb_size_value_minus1[i]
1049
- if (sub_pic_hrd_params_present_flag) {
1050
- readExpGolomb(bitstream); // cpb_size_du_value_minus1[i]
1051
- readExpGolomb(bitstream); // bit_rate_du_value_minus1[i]
1052
- }
1053
- bitstream.readBits(1); // cbr_flag[i]
1054
- }
1055
- };
1056
-
1057
- /** Serializes an HevcDecoderConfigurationRecord into the format specified in Section 8.3.3.1 of ISO 14496-15. */
1058
- export const serializeHevcDecoderConfigurationRecord = (record: HevcDecoderConfigurationRecord) => {
1059
- const bytes: number[] = [];
1060
-
1061
- bytes.push(record.configurationVersion);
1062
-
1063
- bytes.push(
1064
- ((record.generalProfileSpace & 0x3) << 6)
1065
- | ((record.generalTierFlag & 0x1) << 5)
1066
- | (record.generalProfileIdc & 0x1F),
1067
- );
1068
-
1069
- bytes.push((record.generalProfileCompatibilityFlags >>> 24) & 0xFF);
1070
- bytes.push((record.generalProfileCompatibilityFlags >>> 16) & 0xFF);
1071
- bytes.push((record.generalProfileCompatibilityFlags >>> 8) & 0xFF);
1072
- bytes.push(record.generalProfileCompatibilityFlags & 0xFF);
1073
-
1074
- bytes.push(...record.generalConstraintIndicatorFlags);
1075
-
1076
- bytes.push(record.generalLevelIdc & 0xFF);
1077
-
1078
- bytes.push(0xF0 | ((record.minSpatialSegmentationIdc >> 8) & 0x0F)); // Reserved + high nibble
1079
- bytes.push(record.minSpatialSegmentationIdc & 0xFF); // Low byte
1080
-
1081
- bytes.push(0xFC | (record.parallelismType & 0x03));
1082
-
1083
- bytes.push(0xFC | (record.chromaFormatIdc & 0x03));
1084
-
1085
- bytes.push(0xF8 | (record.bitDepthLumaMinus8 & 0x07));
1086
-
1087
- bytes.push(0xF8 | (record.bitDepthChromaMinus8 & 0x07));
1088
-
1089
- bytes.push((record.avgFrameRate >> 8) & 0xFF); // High byte
1090
- bytes.push(record.avgFrameRate & 0xFF); // Low byte
1091
-
1092
- bytes.push(
1093
- ((record.constantFrameRate & 0x03) << 6)
1094
- | ((record.numTemporalLayers & 0x07) << 3)
1095
- | ((record.temporalIdNested & 0x01) << 2)
1096
- | (record.lengthSizeMinusOne & 0x03),
1097
- );
1098
-
1099
- bytes.push(record.arrays.length & 0xFF);
1100
-
1101
- for (const arr of record.arrays) {
1102
- bytes.push(
1103
- ((arr.arrayCompleteness & 0x01) << 7)
1104
- | (0 << 6)
1105
- | (arr.nalUnitType & 0x3F),
1106
- );
1107
-
1108
- bytes.push((arr.nalUnits.length >> 8) & 0xFF); // High byte
1109
- bytes.push(arr.nalUnits.length & 0xFF); // Low byte
1110
-
1111
- for (const nal of arr.nalUnits) {
1112
- bytes.push((nal.length >> 8) & 0xFF); // High byte
1113
- bytes.push(nal.length & 0xFF); // Low byte
1114
-
1115
- for (let i = 0; i < nal.length; i++) {
1116
- bytes.push(nal[i]!);
1117
- }
1118
- }
1119
- }
1120
-
1121
- return new Uint8Array(bytes);
1122
- };
1123
-
1124
- export type Vp9CodecInfo = {
1125
- profile: number;
1126
- level: number;
1127
- bitDepth: number;
1128
- chromaSubsampling: number;
1129
- videoFullRangeFlag: number;
1130
- colourPrimaries: number;
1131
- transferCharacteristics: number;
1132
- matrixCoefficients: number;
1133
- };
1134
-
1135
- export const extractVp9CodecInfoFromPacket = (
1136
- packet: Uint8Array,
1137
- ): Vp9CodecInfo | null => {
1138
- // eslint-disable-next-line @stylistic/max-len
1139
- // https://storage.googleapis.com/downloads.webmproject.org/docs/vp9/vp9-bitstream-specification-v0.7-20170222-draft.pdf
1140
- // http://downloads.webmproject.org/docs/vp9/vp9-bitstream_superframe-and-uncompressed-header_v1.0.pdf
1141
-
1142
- const bitstream = new Bitstream(packet);
1143
-
1144
- // Frame marker (0b10)
1145
- const frameMarker = bitstream.readBits(2);
1146
- if (frameMarker !== 2) {
1147
- return null;
1148
- }
1149
-
1150
- // Profile
1151
- const profileLowBit = bitstream.readBits(1);
1152
- const profileHighBit = bitstream.readBits(1);
1153
- const profile = (profileHighBit << 1) + profileLowBit;
1154
-
1155
- // Skip reserved bit for profile 3
1156
- if (profile === 3) {
1157
- bitstream.skipBits(1);
1158
- }
1159
-
1160
- // show_existing_frame
1161
- const showExistingFrame = bitstream.readBits(1);
1162
-
1163
- if (showExistingFrame === 1) {
1164
- return null;
1165
- }
1166
-
1167
- // frame_type (0 = key frame)
1168
- const frameType = bitstream.readBits(1);
1169
-
1170
- if (frameType !== 0) {
1171
- return null;
1172
- }
1173
-
1174
- // Skip show_frame and error_resilient_mode
1175
- bitstream.skipBits(2);
1176
-
1177
- // Sync code (0x498342)
1178
- const syncCode = bitstream.readBits(24);
1179
- if (syncCode !== 0x498342) {
1180
- return null;
1181
- }
1182
-
1183
- // Color config
1184
- let bitDepth = 8;
1185
- if (profile >= 2) {
1186
- const tenOrTwelveBit = bitstream.readBits(1);
1187
- bitDepth = tenOrTwelveBit ? 12 : 10;
1188
- }
1189
-
1190
- // Color space
1191
- const colorSpace = bitstream.readBits(3);
1192
-
1193
- let chromaSubsampling = 0;
1194
- let videoFullRangeFlag = 0;
1195
-
1196
- if (colorSpace !== 7) { // 7 is CS_RGB
1197
- const colorRange = bitstream.readBits(1);
1198
- videoFullRangeFlag = colorRange;
1199
-
1200
- if (profile === 1 || profile === 3) {
1201
- const subsamplingX = bitstream.readBits(1);
1202
- const subsamplingY = bitstream.readBits(1);
1203
-
1204
- // 0 = 4:2:0 vertical
1205
- // 1 = 4:2:0 colocated
1206
- // 2 = 4:2:2
1207
- // 3 = 4:4:4
1208
- chromaSubsampling = !subsamplingX && !subsamplingY
1209
- ? 3 // 0,0 = 4:4:4
1210
- : subsamplingX && !subsamplingY
1211
- ? 2 // 1,0 = 4:2:2
1212
- : 1; // 1,1 = 4:2:0 colocated (default)
1213
-
1214
- // Skip reserved bit
1215
- bitstream.skipBits(1);
1216
- } else {
1217
- // For profile 0 and 2, always 4:2:0
1218
- chromaSubsampling = 1; // Using colocated as default
1219
- }
1220
- } else {
1221
- // RGB is always 4:4:4
1222
- chromaSubsampling = 3;
1223
- videoFullRangeFlag = 1;
1224
- }
1225
-
1226
- // Parse frame size
1227
- const widthMinusOne = bitstream.readBits(16);
1228
- const heightMinusOne = bitstream.readBits(16);
1229
-
1230
- const width = widthMinusOne + 1;
1231
- const height = heightMinusOne + 1;
1232
-
1233
- // Calculate level based on dimensions
1234
- const pictureSize = width * height;
1235
- let level = last(VP9_LEVEL_TABLE)!.level; // Default to highest level
1236
- for (const entry of VP9_LEVEL_TABLE) {
1237
- if (pictureSize <= entry.maxPictureSize) {
1238
- level = entry.level;
1239
- break;
1240
- }
1241
- }
1242
-
1243
- // Map color_space to standard values
1244
- const matrixCoefficients = colorSpace === 7
1245
- ? 0
1246
- : colorSpace === 2
1247
- ? 1
1248
- : colorSpace === 1
1249
- ? 6
1250
- : 2;
1251
-
1252
- const colourPrimaries = colorSpace === 2
1253
- ? 1
1254
- : colorSpace === 1
1255
- ? 6
1256
- : 2;
1257
-
1258
- const transferCharacteristics = colorSpace === 2
1259
- ? 1
1260
- : colorSpace === 1
1261
- ? 6
1262
- : 2;
1263
-
1264
- return {
1265
- profile,
1266
- level,
1267
- bitDepth,
1268
- chromaSubsampling,
1269
- videoFullRangeFlag,
1270
- colourPrimaries,
1271
- transferCharacteristics,
1272
- matrixCoefficients,
1273
- };
1274
- };
1275
-
1276
- export type Av1CodecInfo = {
1277
- profile: number;
1278
- level: number;
1279
- tier: number;
1280
- bitDepth: number;
1281
- monochrome: number;
1282
- chromaSubsamplingX: number;
1283
- chromaSubsamplingY: number;
1284
- chromaSamplePosition: number;
1285
- };
1286
-
1287
- /** Iterates over all OBUs in an AV1 packet bistream. */
1288
- export const iterateAv1PacketObus = function* (packet: Uint8Array) {
1289
- // https://aomediacodec.github.io/av1-spec/av1-spec.pdf
1290
-
1291
- const bitstream = new Bitstream(packet);
1292
-
1293
- const readLeb128 = (): number | null => {
1294
- let value = 0;
1295
-
1296
- for (let i = 0; i < 8; i++) {
1297
- const byte = bitstream.readAlignedByte();
1298
-
1299
- value |= ((byte & 0x7f) << (i * 7));
1300
-
1301
- if (!(byte & 0x80)) {
1302
- break;
1303
- }
1304
-
1305
- // Spec requirement
1306
- if (i === 7 && (byte & 0x80)) {
1307
- return null;
1308
- }
1309
- }
1310
-
1311
- // Spec requirement
1312
- if (value >= 2 ** 32 - 1) {
1313
- return null;
1314
- }
1315
-
1316
- return value;
1317
- };
1318
-
1319
- while (bitstream.getBitsLeft() >= 8) {
1320
- // Parse OBU header
1321
- bitstream.skipBits(1);
1322
- const obuType = bitstream.readBits(4);
1323
- const obuExtension = bitstream.readBits(1);
1324
- const obuHasSizeField = bitstream.readBits(1);
1325
- bitstream.skipBits(1);
1326
-
1327
- // Skip extension header if present
1328
- if (obuExtension) {
1329
- bitstream.skipBits(8);
1330
- }
1331
-
1332
- // Read OBU size if present
1333
- let obuSize: number;
1334
- if (obuHasSizeField) {
1335
- const obuSizeValue = readLeb128();
1336
- if (obuSizeValue === null) return; // It was invalid
1337
- obuSize = obuSizeValue;
1338
- } else {
1339
- // Calculate remaining bits and convert to bytes, rounding down
1340
- obuSize = Math.floor(bitstream.getBitsLeft() / 8);
1341
- }
1342
-
1343
- assert(bitstream.pos % 8 === 0);
1344
-
1345
- yield {
1346
- type: obuType,
1347
- data: packet.subarray(bitstream.pos / 8, bitstream.pos / 8 + obuSize),
1348
- };
1349
-
1350
- // Move to next OBU
1351
- bitstream.skipBits(obuSize * 8);
1352
- }
1353
- };
1354
-
1355
- /**
1356
- * When AV1 codec information is not provided by the container, we can still try to extract the information by digging
1357
- * into the AV1 bitstream.
1358
- */
1359
- export const extractAv1CodecInfoFromPacket = (
1360
- packet: Uint8Array,
1361
- ): Av1CodecInfo | null => {
1362
- // https://aomediacodec.github.io/av1-spec/av1-spec.pdf
1363
-
1364
- for (const { type, data } of iterateAv1PacketObus(packet)) {
1365
- if (type !== 1) {
1366
- continue; // 1 == OBU_SEQUENCE_HEADER
1367
- }
1368
-
1369
- const bitstream = new Bitstream(data);
1370
-
1371
- // Read sequence header fields
1372
- const seqProfile = bitstream.readBits(3);
1373
-
1374
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
1375
- const stillPicture = bitstream.readBits(1);
1376
-
1377
- const reducedStillPictureHeader = bitstream.readBits(1);
1378
-
1379
- let seqLevel = 0;
1380
- let seqTier = 0;
1381
- let bufferDelayLengthMinus1 = 0;
1382
-
1383
- if (reducedStillPictureHeader) {
1384
- seqLevel = bitstream.readBits(5);
1385
- } else {
1386
- // Parse timing_info_present_flag
1387
- const timingInfoPresentFlag = bitstream.readBits(1);
1388
-
1389
- if (timingInfoPresentFlag) {
1390
- // Skip timing info (num_units_in_display_tick, time_scale, equal_picture_interval)
1391
- bitstream.skipBits(32); // num_units_in_display_tick
1392
- bitstream.skipBits(32); // time_scale
1393
- const equalPictureInterval = bitstream.readBits(1);
1394
-
1395
- if (equalPictureInterval) {
1396
- // Skip num_ticks_per_picture_minus_1 (uvlc)
1397
- // Since this is variable length, we'd need to implement uvlc reading
1398
- // For now, we'll return null as this is rare
1399
- return null;
1400
- }
1401
- }
1402
-
1403
- // Parse decoder_model_info_present_flag
1404
- const decoderModelInfoPresentFlag = bitstream.readBits(1);
1405
-
1406
- if (decoderModelInfoPresentFlag) {
1407
- // Store buffer_delay_length_minus_1 instead of just skipping
1408
- bufferDelayLengthMinus1 = bitstream.readBits(5);
1409
- bitstream.skipBits(32); // num_units_in_decoding_tick
1410
- bitstream.skipBits(5); // buffer_removal_time_length_minus_1
1411
- bitstream.skipBits(5); // frame_presentation_time_length_minus_1
1412
- }
1413
-
1414
- // Parse operating_points_cnt_minus_1
1415
- const operatingPointsCntMinus1 = bitstream.readBits(5);
1416
-
1417
- // For each operating point
1418
- for (let i = 0; i <= operatingPointsCntMinus1; i++) {
1419
- // operating_point_idc[i]
1420
- bitstream.skipBits(12);
1421
-
1422
- // seq_level_idx[i]
1423
- const seqLevelIdx = bitstream.readBits(5);
1424
-
1425
- if (i === 0) {
1426
- seqLevel = seqLevelIdx;
1427
- }
1428
-
1429
- if (seqLevelIdx > 7) {
1430
- // seq_tier[i]
1431
- const seqTierTemp = bitstream.readBits(1);
1432
- if (i === 0) {
1433
- seqTier = seqTierTemp;
1434
- }
1435
- }
1436
-
1437
- if (decoderModelInfoPresentFlag) {
1438
- // decoder_model_present_for_this_op[i]
1439
- const decoderModelPresentForThisOp = bitstream.readBits(1);
1440
-
1441
- if (decoderModelPresentForThisOp) {
1442
- const n = bufferDelayLengthMinus1 + 1;
1443
- bitstream.skipBits(n); // decoder_buffer_delay[op]
1444
- bitstream.skipBits(n); // encoder_buffer_delay[op]
1445
- bitstream.skipBits(1); // low_delay_mode_flag[op]
1446
- }
1447
- }
1448
-
1449
- // initial_display_delay_present_flag
1450
- const initialDisplayDelayPresentFlag = bitstream.readBits(1);
1451
-
1452
- if (initialDisplayDelayPresentFlag) {
1453
- // initial_display_delay_minus_1[i]
1454
- bitstream.skipBits(4);
1455
- }
1456
- }
1457
- }
1458
-
1459
- const highBitdepth = bitstream.readBits(1);
1460
-
1461
- let bitDepth = 8;
1462
- if (seqProfile === 2 && highBitdepth) {
1463
- const twelveBit = bitstream.readBits(1);
1464
- bitDepth = twelveBit ? 12 : 10;
1465
- } else if (seqProfile <= 2) {
1466
- bitDepth = highBitdepth ? 10 : 8;
1467
- }
1468
-
1469
- let monochrome = 0;
1470
- if (seqProfile !== 1) {
1471
- monochrome = bitstream.readBits(1);
1472
- }
1473
-
1474
- let chromaSubsamplingX = 1;
1475
- let chromaSubsamplingY = 1;
1476
- let chromaSamplePosition = 0;
1477
-
1478
- if (!monochrome) {
1479
- if (seqProfile === 0) {
1480
- chromaSubsamplingX = 1;
1481
- chromaSubsamplingY = 1;
1482
- } else if (seqProfile === 1) {
1483
- chromaSubsamplingX = 0;
1484
- chromaSubsamplingY = 0;
1485
- } else {
1486
- if (bitDepth === 12) {
1487
- chromaSubsamplingX = bitstream.readBits(1);
1488
- if (chromaSubsamplingX) {
1489
- chromaSubsamplingY = bitstream.readBits(1);
1490
- }
1491
- }
1492
- }
1493
-
1494
- if (chromaSubsamplingX && chromaSubsamplingY) {
1495
- chromaSamplePosition = bitstream.readBits(2);
1496
- }
1497
- }
1498
-
1499
- return {
1500
- profile: seqProfile,
1501
- level: seqLevel,
1502
- tier: seqTier,
1503
- bitDepth,
1504
- monochrome,
1505
- chromaSubsamplingX,
1506
- chromaSubsamplingY,
1507
- chromaSamplePosition,
1508
- };
1509
- }
1510
-
1511
- return null;
1512
- };
1513
-
1514
- export const parseOpusIdentificationHeader = (bytes: Uint8Array) => {
1515
- const view = toDataView(bytes);
1516
-
1517
- const outputChannelCount = view.getUint8(9);
1518
- const preSkip = view.getUint16(10, true);
1519
- const inputSampleRate = view.getUint32(12, true);
1520
- const outputGain = view.getInt16(16, true);
1521
- const channelMappingFamily = view.getUint8(18);
1522
-
1523
- let channelMappingTable: Uint8Array | null = null;
1524
- if (channelMappingFamily) {
1525
- channelMappingTable = bytes.subarray(19, 19 + 2 + outputChannelCount);
1526
- }
1527
-
1528
- return {
1529
- outputChannelCount,
1530
- preSkip,
1531
- inputSampleRate,
1532
- outputGain,
1533
- channelMappingFamily,
1534
- channelMappingTable,
1535
- };
1536
- };
1537
-
1538
- // From https://datatracker.ietf.org/doc/html/rfc6716, in 48 kHz samples
1539
- const OPUS_FRAME_DURATION_TABLE = [
1540
- 480, 960, 1920, 2880,
1541
- 480, 960, 1920, 2880,
1542
- 480, 960, 1920, 2880,
1543
- 480, 960,
1544
- 480, 960,
1545
- 120, 240, 480, 960,
1546
- 120, 240, 480, 960,
1547
- 120, 240, 480, 960,
1548
- 120, 240, 480, 960,
1549
- ];
1550
-
1551
- export const parseOpusTocByte = (packet: Uint8Array) => {
1552
- const config = packet[0]! >> 3;
1553
-
1554
- return {
1555
- durationInSamples: OPUS_FRAME_DURATION_TABLE[config]!,
1556
- };
1557
- };
1558
-
1559
- // Based on vorbis_parser.c from FFmpeg.
1560
- export const parseModesFromVorbisSetupPacket = (setupHeader: Uint8Array) => {
1561
- // Verify that this is a Setup header.
1562
- if (setupHeader.length < 7) {
1563
- throw new Error('Setup header is too short.');
1564
- }
1565
- if (setupHeader[0] !== 5) {
1566
- throw new Error('Wrong packet type in Setup header.');
1567
- }
1568
- const signature = String.fromCharCode(...setupHeader.slice(1, 7));
1569
- if (signature !== 'vorbis') {
1570
- throw new Error('Invalid packet signature in Setup header.');
1571
- }
1572
-
1573
- // Reverse the entire buffer.
1574
- const bufSize = setupHeader.length;
1575
- const revBuffer = new Uint8Array(bufSize);
1576
- for (let i = 0; i < bufSize; i++) {
1577
- revBuffer[i] = setupHeader[bufSize - 1 - i]!;
1578
- }
1579
-
1580
- // Initialize a Bitstream on the reversed buffer.
1581
- const bitstream = new Bitstream(revBuffer);
1582
-
1583
- // --- Find the framing bit.
1584
- // In FFmpeg code, we scan until get_bits1() returns 1.
1585
- let gotFramingBit = 0;
1586
- while (bitstream.getBitsLeft() > 97) {
1587
- if (bitstream.readBits(1) === 1) {
1588
- gotFramingBit = bitstream.pos;
1589
- break;
1590
- }
1591
- }
1592
- if (gotFramingBit === 0) {
1593
- throw new Error('Invalid Setup header: framing bit not found.');
1594
- }
1595
-
1596
- // --- Search backwards for a valid mode header.
1597
- // We try to “guess” the number of modes by reading a fixed pattern.
1598
- let modeCount = 0;
1599
- let gotModeHeader = false;
1600
- let lastModeCount = 0;
1601
- while (bitstream.getBitsLeft() >= 97) {
1602
- const tempPos = bitstream.pos;
1603
- const a = bitstream.readBits(8);
1604
- const b = bitstream.readBits(16);
1605
- const c = bitstream.readBits(16);
1606
- // If a > 63 or b or c nonzero, assume we’ve gone too far.
1607
- if (a > 63 || b !== 0 || c !== 0) {
1608
- bitstream.pos = tempPos;
1609
- break;
1610
- }
1611
- bitstream.skipBits(1);
1612
- modeCount++;
1613
- if (modeCount > 64) {
1614
- break;
1615
- }
1616
- const bsClone = bitstream.clone();
1617
- const candidate = bsClone.readBits(6) + 1;
1618
- if (candidate === modeCount) {
1619
- gotModeHeader = true;
1620
- lastModeCount = modeCount;
1621
- }
1622
- }
1623
- if (!gotModeHeader) {
1624
- throw new Error('Invalid Setup header: mode header not found.');
1625
- }
1626
- if (lastModeCount > 63) {
1627
- throw new Error(`Unsupported mode count: ${lastModeCount}.`);
1628
- }
1629
- const finalModeCount = lastModeCount;
1630
-
1631
- // --- Reinitialize the bitstream.
1632
- bitstream.pos = 0;
1633
- // Skip the bits up to the found framing bit.
1634
- bitstream.skipBits(gotFramingBit);
1635
-
1636
- // --- Now read, for each mode (in reverse order), 40 bits then one bit.
1637
- // That one bit is the mode blockflag.
1638
- const modeBlockflags = Array(finalModeCount).fill(0) as number[];
1639
- for (let i = finalModeCount - 1; i >= 0; i--) {
1640
- bitstream.skipBits(40);
1641
- modeBlockflags[i] = bitstream.readBits(1);
1642
- }
1643
-
1644
- return { modeBlockflags };
1645
- };
1646
-
1647
- /** Determines a packet's type (key or delta) by digging into the packet bitstream. */
1648
- export const determineVideoPacketType = (
1649
- codec: VideoCodec,
1650
- decoderConfig: VideoDecoderConfig,
1651
- packetData: Uint8Array,
1652
- ): PacketType | null => {
1653
- switch (codec) {
1654
- case 'avc': {
1655
- const nalUnits = extractAvcNalUnits(packetData, decoderConfig);
1656
- const isKeyframe = nalUnits.some(x => extractNalUnitTypeForAvc(x) === AvcNalUnitType.IDR);
1657
-
1658
- return isKeyframe ? 'key' : 'delta';
1659
- };
1660
-
1661
- case 'hevc': {
1662
- const nalUnits = extractHevcNalUnits(packetData, decoderConfig);
1663
- const isKeyframe = nalUnits.some((x) => {
1664
- const type = extractNalUnitTypeForHevc(x);
1665
- return HevcNalUnitType.BLA_W_LP <= type && type <= HevcNalUnitType.RSV_IRAP_VCL23;
1666
- });
1667
-
1668
- return isKeyframe ? 'key' : 'delta';
1669
- };
1670
-
1671
- case 'vp8': {
1672
- // VP8, once again, by far the easiest to deal with.
1673
- const frameType = packetData[0]! & 0b1;
1674
- return frameType === 0 ? 'key' : 'delta';
1675
- };
1676
-
1677
- case 'vp9': {
1678
- const bitstream = new Bitstream(packetData);
1679
-
1680
- if (bitstream.readBits(2) !== 2) {
1681
- return null;
1682
- };
1683
-
1684
- const profileLowBit = bitstream.readBits(1);
1685
- const profileHighBit = bitstream.readBits(1);
1686
- const profile = (profileHighBit << 1) + profileLowBit;
1687
-
1688
- // Skip reserved bit for profile 3
1689
- if (profile === 3) {
1690
- bitstream.skipBits(1);
1691
- }
1692
-
1693
- const showExistingFrame = bitstream.readBits(1);
1694
- if (showExistingFrame) {
1695
- return null;
1696
- }
1697
-
1698
- const frameType = bitstream.readBits(1);
1699
- return frameType === 0 ? 'key' : 'delta';
1700
- };
1701
-
1702
- case 'av1': {
1703
- let reducedStillPictureHeader = false;
1704
-
1705
- for (const { type, data } of iterateAv1PacketObus(packetData)) {
1706
- if (type === 1) { // OBU_SEQUENCE_HEADER
1707
- const bitstream = new Bitstream(data);
1708
-
1709
- bitstream.skipBits(4);
1710
- reducedStillPictureHeader = !!bitstream.readBits(1);
1711
- } else if (
1712
- type === 3 // OBU_FRAME_HEADER
1713
- || type === 6 // OBU_FRAME
1714
- || type === 7 // OBU_REDUNDANT_FRAME_HEADER
1715
- ) {
1716
- if (reducedStillPictureHeader) {
1717
- return 'key';
1718
- }
1719
-
1720
- const bitstream = new Bitstream(data);
1721
- const showExistingFrame = bitstream.readBits(1);
1722
- if (showExistingFrame) {
1723
- return null;
1724
- }
1725
-
1726
- const frameType = bitstream.readBits(2);
1727
- return frameType === 0 ? 'key' : 'delta';
1728
- }
1729
- }
1730
-
1731
- return null;
1732
- };
1733
-
1734
- default: {
1735
- assertNever(codec);
1736
- assert(false);
1737
- };
1738
- }
1739
- };
1740
-
1741
- export enum FlacBlockType {
1742
- STREAMINFO = 0,
1743
- VORBIS_COMMENT = 4,
1744
- PICTURE = 6,
1745
- }
1746
-
1747
- export const readVorbisComments = (bytes: Uint8Array, metadataTags: MetadataTags) => {
1748
- // https://datatracker.ietf.org/doc/html/rfc7845#section-5.2
1749
-
1750
- const commentView = toDataView(bytes);
1751
- let commentPos = 0;
1752
-
1753
- const vendorStringLength = commentView.getUint32(commentPos, true);
1754
- commentPos += 4;
1755
-
1756
- const vendorString = textDecoder.decode(
1757
- bytes.subarray(commentPos, commentPos + vendorStringLength),
1758
- );
1759
- commentPos += vendorStringLength;
1760
-
1761
- if (vendorStringLength > 0) {
1762
- // Expose the vendor string in the raw metadata
1763
- metadataTags.raw ??= {};
1764
- metadataTags.raw['vendor'] ??= vendorString;
1765
- }
1766
-
1767
- const listLength = commentView.getUint32(commentPos, true);
1768
- commentPos += 4;
1769
-
1770
- // Loop over all metadata tags
1771
- for (let i = 0; i < listLength; i++) {
1772
- const stringLength = commentView.getUint32(commentPos, true);
1773
- commentPos += 4;
1774
-
1775
- const string = textDecoder.decode(
1776
- bytes.subarray(commentPos, commentPos + stringLength),
1777
- );
1778
- commentPos += stringLength;
1779
-
1780
- const separatorIndex = string.indexOf('=');
1781
- if (separatorIndex === -1) {
1782
- continue;
1783
- }
1784
-
1785
- const key = string.slice(0, separatorIndex).toUpperCase();
1786
- const value = string.slice(separatorIndex + 1);
1787
-
1788
- metadataTags.raw ??= {};
1789
- metadataTags.raw[key] ??= value;
1790
-
1791
- switch (key) {
1792
- case 'TITLE': {
1793
- metadataTags.title ??= value;
1794
- }; break;
1795
-
1796
- case 'DESCRIPTION': {
1797
- metadataTags.description ??= value;
1798
- }; break;
1799
-
1800
- case 'ARTIST': {
1801
- metadataTags.artist ??= value;
1802
- }; break;
1803
-
1804
- case 'ALBUM': {
1805
- metadataTags.album ??= value;
1806
- }; break;
1807
-
1808
- case 'ALBUMARTIST': {
1809
- metadataTags.albumArtist ??= value;
1810
- }; break;
1811
-
1812
- case 'COMMENT': {
1813
- metadataTags.comment ??= value;
1814
- }; break;
1815
-
1816
- case 'LYRICS': {
1817
- metadataTags.lyrics ??= value;
1818
- }; break;
1819
-
1820
- case 'TRACKNUMBER': {
1821
- const parts = value.split('/');
1822
- const trackNum = Number.parseInt(parts[0]!, 10);
1823
- const tracksTotal = parts[1] && Number.parseInt(parts[1], 10);
1824
-
1825
- if (Number.isInteger(trackNum) && trackNum > 0) {
1826
- metadataTags.trackNumber ??= trackNum;
1827
- }
1828
- if (tracksTotal && Number.isInteger(tracksTotal) && tracksTotal > 0) {
1829
- metadataTags.tracksTotal ??= tracksTotal;
1830
- }
1831
- }; break;
1832
-
1833
- case 'TRACKTOTAL': {
1834
- const tracksTotal = Number.parseInt(value, 10);
1835
- if (Number.isInteger(tracksTotal) && tracksTotal > 0) {
1836
- metadataTags.tracksTotal ??= tracksTotal;
1837
- }
1838
- }; break;
1839
-
1840
- case 'DISCNUMBER': {
1841
- const parts = value.split('/');
1842
- const discNum = Number.parseInt(parts[0]!, 10);
1843
- const discsTotal = parts[1] && Number.parseInt(parts[1], 10);
1844
-
1845
- if (Number.isInteger(discNum) && discNum > 0) {
1846
- metadataTags.discNumber ??= discNum;
1847
- }
1848
- if (discsTotal && Number.isInteger(discsTotal) && discsTotal > 0) {
1849
- metadataTags.discsTotal ??= discsTotal;
1850
- }
1851
- }; break;
1852
-
1853
- case 'DISCTOTAL': {
1854
- const discsTotal = Number.parseInt(value, 10);
1855
- if (Number.isInteger(discsTotal) && discsTotal > 0) {
1856
- metadataTags.discsTotal ??= discsTotal;
1857
- }
1858
- }; break;
1859
-
1860
- case 'DATE': {
1861
- const date = new Date(value);
1862
- if (!Number.isNaN(date.getTime())) {
1863
- metadataTags.date ??= date;
1864
- }
1865
- }; break;
1866
-
1867
- case 'GENRE': {
1868
- metadataTags.genre ??= value;
1869
- }; break;
1870
-
1871
- case 'METADATA_BLOCK_PICTURE': {
1872
- // https://datatracker.ietf.org/doc/rfc9639/ Section 8.8
1873
- const decoded = base64ToBytes(value);
1874
-
1875
- const view = toDataView(decoded);
1876
- const pictureType = view.getUint32(0, false);
1877
- const mediaTypeLength = view.getUint32(4, false);
1878
- const mediaType = String.fromCharCode(...decoded.subarray(8, 8 + mediaTypeLength)); // ASCII
1879
- const descriptionLength = view.getUint32(8 + mediaTypeLength, false);
1880
- const description = textDecoder.decode(decoded.subarray(
1881
- 12 + mediaTypeLength,
1882
- 12 + mediaTypeLength + descriptionLength,
1883
- ));
1884
- const dataLength = view.getUint32(mediaTypeLength + descriptionLength + 28);
1885
- const data = decoded.subarray(
1886
- mediaTypeLength + descriptionLength + 32,
1887
- mediaTypeLength + descriptionLength + 32 + dataLength,
1888
- );
1889
-
1890
- metadataTags.images ??= [];
1891
- metadataTags.images.push({
1892
- data,
1893
- mimeType: mediaType,
1894
- kind: pictureType === 3 ? 'coverFront' : pictureType === 4 ? 'coverBack' : 'unknown',
1895
- name: undefined,
1896
- description: description || undefined,
1897
- });
1898
- }; break;
1899
- }
1900
- }
1901
- };
1902
-
1903
- export const createVorbisComments = (headerBytes: Uint8Array, tags: MetadataTags, writeImages: boolean) => {
1904
- // https://datatracker.ietf.org/doc/html/rfc7845#section-5.2
1905
-
1906
- const commentHeaderParts: Uint8Array[] = [
1907
- headerBytes,
1908
- ];
1909
-
1910
- const vendorString = 'Mediabunny';
1911
- const encodedVendorString = textEncoder.encode(vendorString);
1912
-
1913
- let currentBuffer = new Uint8Array(4 + encodedVendorString.length);
1914
- let currentView = new DataView(currentBuffer.buffer);
1915
- currentView.setUint32(0, encodedVendorString.length, true);
1916
- currentBuffer.set(encodedVendorString, 4);
1917
-
1918
- commentHeaderParts.push(currentBuffer);
1919
-
1920
- const writtenTags = new Set<string>();
1921
- const addCommentTag = (key: string, value: string) => {
1922
- const joined = `${key}=${value}`;
1923
- const encoded = textEncoder.encode(joined);
1924
-
1925
- currentBuffer = new Uint8Array(4 + encoded.length);
1926
- currentView = new DataView(currentBuffer.buffer);
1927
-
1928
- currentView.setUint32(0, encoded.length, true);
1929
- currentBuffer.set(encoded, 4);
1930
-
1931
- commentHeaderParts.push(currentBuffer);
1932
- writtenTags.add(key);
1933
- };
1934
-
1935
- for (const { key, value } of keyValueIterator(tags)) {
1936
- switch (key) {
1937
- case 'title': {
1938
- addCommentTag('TITLE', value);
1939
- }; break;
1940
-
1941
- case 'description': {
1942
- addCommentTag('DESCRIPTION', value);
1943
- }; break;
1944
-
1945
- case 'artist': {
1946
- addCommentTag('ARTIST', value);
1947
- }; break;
1948
-
1949
- case 'album': {
1950
- addCommentTag('ALBUM', value);
1951
- }; break;
1952
-
1953
- case 'albumArtist': {
1954
- addCommentTag('ALBUMARTIST', value);
1955
- }; break;
1956
-
1957
- case 'genre': {
1958
- addCommentTag('GENRE', value);
1959
- }; break;
1960
-
1961
- case 'date': {
1962
- const rawVersion = tags.raw?.['DATE'] ?? tags.raw?.['date'];
1963
- if (rawVersion && typeof rawVersion === 'string') {
1964
- addCommentTag('DATE', rawVersion);
1965
- } else {
1966
- addCommentTag('DATE', value.toISOString().slice(0, 10));
1967
- }
1968
- }; break;
1969
-
1970
- case 'comment': {
1971
- addCommentTag('COMMENT', value);
1972
- }; break;
1973
-
1974
- case 'lyrics': {
1975
- addCommentTag('LYRICS', value);
1976
- }; break;
1977
-
1978
- case 'trackNumber': {
1979
- addCommentTag('TRACKNUMBER', value.toString());
1980
- }; break;
1981
-
1982
- case 'tracksTotal': {
1983
- addCommentTag('TRACKTOTAL', value.toString());
1984
- }; break;
1985
-
1986
- case 'discNumber': {
1987
- addCommentTag('DISCNUMBER', value.toString());
1988
- }; break;
1989
-
1990
- case 'discsTotal': {
1991
- addCommentTag('DISCTOTAL', value.toString());
1992
- }; break;
1993
-
1994
- case 'images': {
1995
- // For example, in .flac, we put the pictures in a different section,
1996
- // not in the Vorbis comment header.
1997
- if (!writeImages) {
1998
- break;
1999
- }
2000
- for (const image of value) {
2001
- // https://datatracker.ietf.org/doc/rfc9639/ Section 8.8
2002
- const pictureType = image.kind === 'coverFront' ? 3 : image.kind === 'coverBack' ? 4 : 0;
2003
- const encodedMediaType = new Uint8Array(image.mimeType.length);
2004
-
2005
- for (let i = 0; i < image.mimeType.length; i++) {
2006
- encodedMediaType[i] = image.mimeType.charCodeAt(i);
2007
- }
2008
-
2009
- const encodedDescription = textEncoder.encode(image.description ?? '');
2010
-
2011
- const buffer = new Uint8Array(
2012
- 4 // Picture type
2013
- + 4 // MIME type length
2014
- + encodedMediaType.length // MIME type
2015
- + 4 // Description length
2016
- + encodedDescription.length // Description
2017
- + 16 // Width, height, color depth, number of colors
2018
- + 4 // Picture data length
2019
- + image.data.length, // Picture data
2020
- );
2021
- const view = toDataView(buffer);
2022
-
2023
- view.setUint32(0, pictureType, false);
2024
- view.setUint32(4, encodedMediaType.length, false);
2025
- buffer.set(encodedMediaType, 8);
2026
- view.setUint32(8 + encodedMediaType.length, encodedDescription.length, false);
2027
- buffer.set(encodedDescription, 12 + encodedMediaType.length);
2028
- // Skip a bunch of fields (width, height, color depth, number of colors)
2029
- view.setUint32(
2030
- 28 + encodedMediaType.length + encodedDescription.length, image.data.length, false,
2031
- );
2032
- buffer.set(
2033
- image.data,
2034
- 32 + encodedMediaType.length + encodedDescription.length,
2035
- );
2036
-
2037
- const encoded = bytesToBase64(buffer);
2038
- addCommentTag('METADATA_BLOCK_PICTURE', encoded);
2039
- }
2040
- }; break;
2041
-
2042
- case 'raw': {
2043
- // Handled later
2044
- }; break;
2045
-
2046
- default: assertNever(key);
2047
- }
2048
- }
2049
-
2050
- if (tags.raw) {
2051
- for (const key in tags.raw) {
2052
- const value = tags.raw[key] ?? tags.raw[key.toLowerCase()];
2053
- if (key === 'vendor' || value == null || writtenTags.has(key)) {
2054
- continue;
2055
- }
2056
-
2057
- if (typeof value === 'string') {
2058
- addCommentTag(key, value);
2059
- }
2060
- }
2061
- }
2062
-
2063
- const listLengthBuffer = new Uint8Array(4);
2064
- toDataView(listLengthBuffer).setUint32(0, writtenTags.size, true);
2065
- commentHeaderParts.splice(2, 0, listLengthBuffer); // Insert after the header and vendor section
2066
-
2067
- // Merge all comment header parts into a single buffer
2068
- const commentHeaderLength = commentHeaderParts.reduce((a, b) => a + b.length, 0);
2069
- const commentHeader = new Uint8Array(commentHeaderLength);
2070
-
2071
- let pos = 0;
2072
- for (const part of commentHeaderParts) {
2073
- commentHeader.set(part, pos);
2074
- pos += part.length;
2075
- }
2076
-
2077
- return commentHeader;
2078
- };