@remotion/media-parser 4.0.200 → 4.0.201

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 (111) hide show
  1. package/dist/av1-codec-string.d.ts +5 -0
  2. package/dist/av1-codec-string.js +18 -1
  3. package/dist/bitstream/av1.d.ts +2 -0
  4. package/dist/bitstream/av1.js +12 -0
  5. package/dist/boxes/iso-base-media/avcc-hvcc.d.ts +20 -0
  6. package/dist/boxes/iso-base-media/avcc-hvcc.js +73 -0
  7. package/dist/boxes/iso-base-media/avcc.d.ts +18 -0
  8. package/dist/boxes/iso-base-media/avcc.js +27 -0
  9. package/dist/boxes/iso-base-media/esds-descriptors.d.ts +21 -0
  10. package/dist/boxes/iso-base-media/esds-descriptors.js +62 -0
  11. package/dist/boxes/iso-base-media/esds.d.ts +15 -0
  12. package/dist/boxes/iso-base-media/esds.js +27 -0
  13. package/dist/boxes/iso-base-media/mdat/mdat.js +2 -1
  14. package/dist/boxes/iso-base-media/moov/moov.js +1 -0
  15. package/dist/boxes/iso-base-media/process-box.d.ts +4 -2
  16. package/dist/boxes/iso-base-media/process-box.js +56 -40
  17. package/dist/boxes/iso-base-media/stsd/mebx.d.ts +2 -1
  18. package/dist/boxes/iso-base-media/stsd/mebx.js +2 -1
  19. package/dist/boxes/iso-base-media/stsd/samples.js +3 -0
  20. package/dist/boxes/iso-base-media/stsd/stco.d.ts +3 -2
  21. package/dist/boxes/iso-base-media/stsd/stco.js +2 -2
  22. package/dist/boxes/iso-base-media/trak/trak.js +1 -0
  23. package/dist/boxes/webm/bitstream/av1.js +10 -1
  24. package/dist/boxes/webm/ebml.d.ts +1 -1
  25. package/dist/boxes/webm/make-header.d.ts +8 -1
  26. package/dist/boxes/webm/make-header.js +65 -30
  27. package/dist/boxes/webm/parse-ebml.d.ts +7 -0
  28. package/dist/boxes/webm/parse-ebml.js +66 -0
  29. package/dist/boxes/webm/parse-webm-header.js +8 -9
  30. package/dist/boxes/webm/segments/all-segments.d.ts +258 -1
  31. package/dist/boxes/webm/segments/all-segments.js +126 -2
  32. package/dist/boxes/webm/segments/seek-position.js +1 -1
  33. package/dist/boxes/webm/segments/seek.d.ts +1 -1
  34. package/dist/boxes/webm/segments/seek.js +8 -2
  35. package/dist/boxes/webm/segments/timestamp-scale.js +1 -1
  36. package/dist/boxes/webm/segments/track-entry.d.ts +5 -1
  37. package/dist/boxes/webm/segments/track-entry.js +19 -20
  38. package/dist/boxes/webm/segments.d.ts +2 -2
  39. package/dist/boxes/webm/segments.js +30 -25
  40. package/dist/boxes/webm/traversal.d.ts +1 -0
  41. package/dist/boxes/webm/traversal.js +12 -1
  42. package/dist/buffer-iterator.d.ts +9 -6
  43. package/dist/buffer-iterator.js +83 -7
  44. package/dist/from-fetch.js +13 -3
  45. package/dist/from-input-type-file.d.ts +2 -0
  46. package/dist/from-input-type-file.js +37 -0
  47. package/dist/from-node.js +9 -2
  48. package/dist/from-web-file.js +6 -1
  49. package/dist/from-web.js +15 -6
  50. package/dist/get-audio-codec.d.ts +1 -1
  51. package/dist/get-codec.d.ts +4 -0
  52. package/dist/get-codec.js +22 -0
  53. package/dist/get-sample-positions.js +1 -1
  54. package/dist/has-all-info.js +1 -1
  55. package/dist/options.d.ts +3 -2
  56. package/dist/parse-media.js +13 -9
  57. package/dist/parse-video.js +16 -0
  58. package/dist/parser-state.d.ts +4 -3
  59. package/dist/parser-state.js +15 -3
  60. package/dist/reader.d.ts +1 -1
  61. package/dist/web-file.d.ts +2 -0
  62. package/dist/web-file.js +37 -0
  63. package/package.json +2 -2
  64. package/src/boxes/iso-base-media/mdat/mdat.ts +2 -1
  65. package/src/boxes/iso-base-media/moov/moov.ts +1 -0
  66. package/src/boxes/iso-base-media/process-box.ts +70 -40
  67. package/src/boxes/iso-base-media/stsd/mebx.ts +3 -0
  68. package/src/boxes/iso-base-media/stsd/samples.ts +3 -0
  69. package/src/boxes/iso-base-media/stsd/stco.ts +5 -3
  70. package/src/boxes/iso-base-media/trak/trak.ts +1 -0
  71. package/src/boxes/webm/make-header.ts +122 -32
  72. package/src/boxes/webm/parse-ebml.ts +93 -0
  73. package/src/boxes/webm/parse-webm-header.ts +8 -12
  74. package/src/boxes/webm/segments/all-segments.ts +222 -1
  75. package/src/boxes/webm/segments/seek-position.ts +1 -1
  76. package/src/boxes/webm/segments/seek.ts +12 -2
  77. package/src/boxes/webm/segments/timestamp-scale.ts +1 -1
  78. package/src/boxes/webm/segments/track-entry.ts +31 -26
  79. package/src/boxes/webm/segments.ts +37 -32
  80. package/src/boxes/webm/traversal.ts +13 -0
  81. package/src/buffer-iterator.ts +102 -9
  82. package/src/from-fetch.ts +22 -3
  83. package/src/from-node.ts +18 -4
  84. package/src/from-web-file.ts +11 -1
  85. package/src/get-sample-positions.ts +1 -1
  86. package/src/has-all-info.ts +1 -1
  87. package/src/options.ts +3 -2
  88. package/src/parse-media.ts +14 -8
  89. package/src/parse-video.ts +17 -0
  90. package/src/parser-state.ts +22 -5
  91. package/src/reader.ts +1 -0
  92. package/src/test/create-matroska.test.ts +36 -2
  93. package/src/test/matroska.test.ts +69 -27
  94. package/src/test/parse-stco.test.ts +2 -0
  95. package/src/test/stream-local.test.ts +23 -9
  96. package/src/test/stream-remote.test.ts +23 -19
  97. package/src/test/stsd.test.ts +2 -0
  98. package/tsconfig.tsbuildinfo +1 -1
  99. package/dist/boxes/iso-base-media/ftype.d.ts +0 -9
  100. package/dist/boxes/iso-base-media/ftype.js +0 -31
  101. package/dist/get-video-metadata.d.ts +0 -2
  102. package/dist/get-video-metadata.js +0 -44
  103. package/dist/read-and-increment-offset.d.ts +0 -28
  104. package/dist/read-and-increment-offset.js +0 -177
  105. package/dist/understand-vorbis.d.ts +0 -1
  106. package/dist/understand-vorbis.js +0 -12
  107. package/src/boxes/webm/segments/unknown.ts +0 -19
  108. /package/dist/{boxes/webm/bitstream/av1/frame.d.ts → get-samples.d.ts} +0 -0
  109. /package/dist/{boxes/webm/bitstream/av1/frame.js → get-samples.js} +0 -0
  110. /package/dist/{boxes/webm/bitstream/h264/get-h264-descriptor.d.ts → sample-aspect-ratio.d.ts} +0 -0
  111. /package/dist/{boxes/webm/bitstream/h264/get-h264-descriptor.js → sample-aspect-ratio.js} +0 -0
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.inputTypeFileReader = void 0;
4
+ exports.inputTypeFileReader = {
5
+ read: (file, range) => {
6
+ if (typeof file === 'string') {
7
+ throw new Error('`inputTypeFileReader` only supports `File` objects');
8
+ }
9
+ if (range !== null) {
10
+ throw new Error('`inputTypeFileReader` does not support `range`');
11
+ }
12
+ const part = range === null
13
+ ? file
14
+ : typeof range === 'number'
15
+ ? file.slice(range)
16
+ : file.slice(range[0], range[1]);
17
+ const reader = new FileReader();
18
+ reader.readAsArrayBuffer(file);
19
+ return new Promise((resolve, reject) => {
20
+ reader.onload = () => {
21
+ resolve({
22
+ reader: part.stream().getReader(),
23
+ contentLength: file.size,
24
+ });
25
+ };
26
+ reader.onerror = (error) => {
27
+ reject(error);
28
+ };
29
+ });
30
+ },
31
+ getLength: (src) => {
32
+ if (typeof src === 'string') {
33
+ throw new Error('`inputTypeFileReader` only supports `File` objects');
34
+ }
35
+ return Promise.resolve(src.size);
36
+ },
37
+ };
package/package.json CHANGED
@@ -3,11 +3,11 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/media-parser"
4
4
  },
5
5
  "name": "@remotion/media-parser",
6
- "version": "4.0.200",
6
+ "version": "4.0.201",
7
7
  "main": "dist/index.js",
8
8
  "sideEffects": false,
9
9
  "devDependencies": {
10
- "@remotion/renderer": "4.0.200"
10
+ "@remotion/renderer": "4.0.201"
11
11
  },
12
12
  "publishConfig": {
13
13
  "access": "public"
@@ -26,7 +26,7 @@ export const parseMdat = async ({
26
26
  }): Promise<MdatBox> => {
27
27
  const alreadyHas = hasTracks(existingBoxes);
28
28
  if (!alreadyHas) {
29
- data.discard(size - 8);
29
+ data.discard(size - (data.counter.getOffset() - fileOffset));
30
30
  return Promise.resolve({
31
31
  type: 'mdat-box',
32
32
  boxSize: size,
@@ -116,6 +116,7 @@ export const parseMdat = async ({
116
116
  }
117
117
 
118
118
  const remaining = size - (data.counter.getOffset() - fileOffset);
119
+ data.removeBytesRead();
119
120
  if (remaining === 0) {
120
121
  break;
121
122
  }
@@ -27,6 +27,7 @@ export const parseMoov = async ({
27
27
  initialBoxes: [],
28
28
  options,
29
29
  continueMdat: false,
30
+ littleEndian: false,
30
31
  });
31
32
 
32
33
  if (children.status === 'incomplete') {
@@ -37,11 +37,13 @@ const getChildren = async ({
37
37
  iterator,
38
38
  bytesRemainingInBox,
39
39
  options,
40
+ littleEndian,
40
41
  }: {
41
42
  boxType: string;
42
43
  iterator: BufferIterator;
43
44
  bytesRemainingInBox: number;
44
45
  options: ParserContext;
46
+ littleEndian: boolean;
45
47
  }) => {
46
48
  const parseChildren =
47
49
  boxType === 'mdia' ||
@@ -59,6 +61,7 @@ const getChildren = async ({
59
61
  initialBoxes: [],
60
62
  options,
61
63
  continueMdat: false,
64
+ littleEndian,
62
65
  });
63
66
 
64
67
  if (parsed.status === 'incomplete') {
@@ -121,17 +124,37 @@ export const processBox = async ({
121
124
  allowIncompleteBoxes,
122
125
  parsedBoxes,
123
126
  options,
127
+ littleEndian,
124
128
  }: {
125
129
  iterator: BufferIterator;
126
130
  allowIncompleteBoxes: boolean;
127
131
  parsedBoxes: AnySegment[];
128
132
  options: ParserContext;
133
+ littleEndian: boolean;
129
134
  }): Promise<BoxAndNext> => {
130
135
  const fileOffset = iterator.counter.getOffset();
131
136
  const bytesRemaining = iterator.bytesRemaining();
132
137
 
133
- const boxSize = iterator.getFourByteNumber();
134
- if (boxSize === 0) {
138
+ const boxSizeRaw = iterator.getFourByteNumber(littleEndian);
139
+
140
+ // If `boxSize === 1`, the 8 bytes after the box type are the size of the box.
141
+ if (
142
+ (boxSizeRaw === 1 && iterator.bytesRemaining() < 12) ||
143
+ iterator.bytesRemaining() < 4
144
+ ) {
145
+ iterator.counter.decrement(iterator.counter.getOffset() - fileOffset);
146
+ if (allowIncompleteBoxes) {
147
+ return {
148
+ type: 'incomplete',
149
+ };
150
+ }
151
+
152
+ throw new Error(
153
+ `Expected box size of ${bytesRemaining}, got ${boxSizeRaw}. Incomplete boxes are not allowed.`,
154
+ );
155
+ }
156
+
157
+ if (boxSizeRaw === 0) {
135
158
  return {
136
159
  type: 'complete',
137
160
  box: {
@@ -143,42 +166,41 @@ export const processBox = async ({
143
166
  };
144
167
  }
145
168
 
169
+ const boxType = iterator.getByteString(4);
170
+
171
+ const boxSize =
172
+ boxSizeRaw === 1 ? iterator.getEightByteNumber(littleEndian) : boxSizeRaw;
173
+
146
174
  if (bytesRemaining < boxSize) {
147
- if (bytesRemaining >= 4) {
148
- const type = iterator.getByteString(4);
149
- iterator.counter.decrement(4);
150
-
151
- if (type === 'mdat') {
152
- const shouldSkip = options.canSkipVideoData || !hasTracks(parsedBoxes);
153
-
154
- if (shouldSkip) {
155
- const skipTo = fileOffset + boxSize;
156
- const bytesToSkip = skipTo - iterator.counter.getOffset();
157
-
158
- // If there is a huge mdat chunk, we can skip it because we don't need it for the metadata
159
- if (bytesToSkip > 1_000_000) {
160
- return {
161
- type: 'complete',
162
- box: {
163
- type: 'mdat-box',
164
- boxSize,
165
- fileOffset,
166
- samplesProcessed: false,
167
- },
168
- size: boxSize,
169
- skipTo: fileOffset + boxSize,
170
- };
171
- }
172
- } else {
173
- iterator.discard(4);
174
- return parseMdatPartially({
175
- iterator,
176
- boxSize,
177
- fileOffset,
178
- parsedBoxes,
179
- options,
180
- });
175
+ if (boxType === 'mdat') {
176
+ const shouldSkip = options.canSkipVideoData || !hasTracks(parsedBoxes);
177
+
178
+ if (shouldSkip) {
179
+ const skipTo = fileOffset + boxSize;
180
+ const bytesToSkip = skipTo - iterator.counter.getOffset();
181
+
182
+ // If there is a huge mdat chunk, we can skip it because we don't need it for the metadata
183
+ if (bytesToSkip > 1_000_000) {
184
+ return {
185
+ type: 'complete',
186
+ box: {
187
+ type: 'mdat-box',
188
+ boxSize,
189
+ fileOffset,
190
+ samplesProcessed: false,
191
+ },
192
+ size: boxSize,
193
+ skipTo: fileOffset + boxSize,
194
+ };
181
195
  }
196
+ } else {
197
+ return parseMdatPartially({
198
+ iterator,
199
+ boxSize,
200
+ fileOffset,
201
+ parsedBoxes,
202
+ options,
203
+ });
182
204
  }
183
205
  }
184
206
 
@@ -194,8 +216,6 @@ export const processBox = async ({
194
216
  );
195
217
  }
196
218
 
197
- const boxType = iterator.getByteString(4);
198
-
199
219
  if (boxType === 'ftyp') {
200
220
  const box = parseFtyp({iterator, size: boxSize, offset: fileOffset});
201
221
  return {
@@ -271,11 +291,12 @@ export const processBox = async ({
271
291
  };
272
292
  }
273
293
 
274
- if (boxType === 'stco') {
294
+ if (boxType === 'stco' || boxType === 'co64') {
275
295
  const box = parseStco({
276
296
  iterator,
277
297
  offset: fileOffset,
278
298
  size: boxSize,
299
+ mode64Bit: boxType === 'co64',
279
300
  });
280
301
 
281
302
  return {
@@ -352,6 +373,7 @@ export const processBox = async ({
352
373
  offset: fileOffset,
353
374
  size: boxSize,
354
375
  options,
376
+ littleEndian,
355
377
  });
356
378
 
357
379
  return {
@@ -525,6 +547,7 @@ export const processBox = async ({
525
547
  iterator,
526
548
  bytesRemainingInBox,
527
549
  options,
550
+ littleEndian,
528
551
  });
529
552
 
530
553
  return {
@@ -548,6 +571,7 @@ export const parseBoxes = async ({
548
571
  initialBoxes,
549
572
  options,
550
573
  continueMdat,
574
+ littleEndian,
551
575
  }: {
552
576
  iterator: BufferIterator;
553
577
  maxBytes: number;
@@ -555,6 +579,7 @@ export const parseBoxes = async ({
555
579
  initialBoxes: IsoBaseMediaBox[];
556
580
  options: ParserContext;
557
581
  continueMdat: false | PartialMdatBox;
582
+ littleEndian: boolean;
558
583
  }): Promise<ParseResult> => {
559
584
  let boxes: IsoBaseMediaBox[] = initialBoxes;
560
585
  const initialOffset = iterator.counter.getOffset();
@@ -577,6 +602,7 @@ export const parseBoxes = async ({
577
602
  allowIncompleteBoxes,
578
603
  parsedBoxes: initialBoxes,
579
604
  options,
605
+ littleEndian,
580
606
  });
581
607
 
582
608
  if (result.type === 'incomplete') {
@@ -595,6 +621,7 @@ export const parseBoxes = async ({
595
621
  initialBoxes: boxes,
596
622
  options,
597
623
  continueMdat: false,
624
+ littleEndian,
598
625
  });
599
626
  },
600
627
  skipTo: null,
@@ -614,6 +641,7 @@ export const parseBoxes = async ({
614
641
  initialBoxes: boxes,
615
642
  options,
616
643
  continueMdat: result,
644
+ littleEndian,
617
645
  }),
618
646
  );
619
647
  },
@@ -641,13 +669,14 @@ export const parseBoxes = async ({
641
669
  initialBoxes: boxes,
642
670
  options,
643
671
  continueMdat: false,
672
+ littleEndian,
644
673
  });
645
674
  },
646
675
  skipTo: result.skipTo,
647
676
  };
648
677
  }
649
678
 
650
- iterator.discardFirstBytes();
679
+ iterator.removeBytesRead();
651
680
  }
652
681
 
653
682
  const mdatState = hasSkippedMdatProcessing(boxes);
@@ -663,6 +692,7 @@ export const parseBoxes = async ({
663
692
  initialBoxes: boxes,
664
693
  options,
665
694
  continueMdat: false,
695
+ littleEndian,
666
696
  });
667
697
  },
668
698
  skipTo: mdatState.fileOffset,
@@ -16,11 +16,13 @@ export const parseMebx = async ({
16
16
  offset,
17
17
  size,
18
18
  options,
19
+ littleEndian,
19
20
  }: {
20
21
  iterator: BufferIterator;
21
22
  offset: number;
22
23
  size: number;
23
24
  options: ParserContext;
25
+ littleEndian: boolean;
24
26
  }): Promise<MebxBox> => {
25
27
  // reserved, 6 bit
26
28
  iterator.discard(6);
@@ -34,6 +36,7 @@ export const parseMebx = async ({
34
36
  initialBoxes: [],
35
37
  options,
36
38
  continueMdat: false,
39
+ littleEndian,
37
40
  });
38
41
 
39
42
  if (children.status === 'incomplete') {
@@ -179,6 +179,7 @@ export const processSample = async ({
179
179
  initialBoxes: [],
180
180
  options,
181
181
  continueMdat: false,
182
+ littleEndian: false,
182
183
  });
183
184
 
184
185
  if (children.status === 'incomplete') {
@@ -232,6 +233,7 @@ export const processSample = async ({
232
233
  initialBoxes: [],
233
234
  options,
234
235
  continueMdat: false,
236
+ littleEndian: false,
235
237
  });
236
238
 
237
239
  if (children.status === 'incomplete') {
@@ -288,6 +290,7 @@ export const processSample = async ({
288
290
  initialBoxes: [],
289
291
  options,
290
292
  continueMdat: false,
293
+ littleEndian: false,
291
294
  });
292
295
 
293
296
  if (children.status === 'incomplete') {
@@ -6,17 +6,19 @@ export interface StcoBox extends BaseBox {
6
6
  version: number;
7
7
  flags: number[];
8
8
  entryCount: number;
9
- entries: number[];
9
+ entries: (number | bigint)[];
10
10
  }
11
11
 
12
12
  export const parseStco = ({
13
13
  iterator,
14
14
  offset,
15
15
  size,
16
+ mode64Bit,
16
17
  }: {
17
18
  iterator: BufferIterator;
18
19
  offset: number;
19
20
  size: number;
21
+ mode64Bit: boolean;
20
22
  }): StcoBox => {
21
23
  const version = iterator.getUint8();
22
24
  if (version !== 0) {
@@ -26,14 +28,14 @@ export const parseStco = ({
26
28
  const flags = iterator.getSlice(3);
27
29
  const entryCount = iterator.getUint32();
28
30
 
29
- const entries: number[] = [];
31
+ const entries: (number | bigint)[] = [];
30
32
  for (let i = 0; i < entryCount; i++) {
31
33
  const bytesRemaining = size - (iterator.counter.getOffset() - offset);
32
34
  if (bytesRemaining < 4) {
33
35
  break;
34
36
  }
35
37
 
36
- entries.push(iterator.getUint32());
38
+ entries.push(mode64Bit ? iterator.getUint64() : iterator.getUint32());
37
39
  }
38
40
 
39
41
  iterator.discard(size - (iterator.counter.getOffset() - offset));
@@ -27,6 +27,7 @@ export const parseTrak = async ({
27
27
  initialBoxes: [],
28
28
  options,
29
29
  continueMdat: false,
30
+ littleEndian: false,
30
31
  });
31
32
 
32
33
  if (children.status === 'incomplete') {
@@ -1,48 +1,138 @@
1
1
  import {getVariableInt} from './ebml';
2
- import {matroskaElements} from './segments/all-segments';
2
+ import type {
3
+ Ebml,
4
+ EbmlWithChildren,
5
+ EbmlWithHexString,
6
+ EbmlWithString,
7
+ EbmlWithUint8,
8
+ EmblTypes,
9
+ HeaderStructure,
10
+ matroskaElements,
11
+ } from './segments/all-segments';
12
+ import {getIdForName} from './segments/all-segments';
3
13
 
4
14
  export const webmPattern = new Uint8Array([0x1a, 0x45, 0xdf, 0xa3]);
5
15
 
6
16
  const matroskaToHex = (
7
17
  matrId: (typeof matroskaElements)[keyof typeof matroskaElements],
8
18
  ) => {
9
- const numbers: number[] = [];
19
+ const numbers: Uint8Array = new Uint8Array((matrId.length - 2) / 2);
20
+
10
21
  for (let i = 2; i < matrId.length; i += 2) {
11
- const hex = matrId.substr(i, 2);
12
- numbers.push(parseInt(hex, 16));
22
+ const hex = matrId.substring(i, i + 2);
23
+ numbers[(i - 2) / 2] = parseInt(hex, 16);
13
24
  }
14
25
 
15
26
  return numbers;
16
27
  };
17
28
 
18
- export const makeMatroskaHeader = () => {
19
- const size = 0x23;
20
-
21
- const array = new Uint8Array([
22
- ...webmPattern,
23
- ...getVariableInt(size),
24
- ...matroskaToHex(matroskaElements.EBMLVersion),
25
- ...getVariableInt(1),
26
- 1,
27
- ...matroskaToHex(matroskaElements.EBMLReadVersion),
28
- ...getVariableInt(1),
29
- 1,
30
- ...matroskaToHex(matroskaElements.EBMLMaxIDLength),
31
- ...getVariableInt(1),
32
- 4,
33
- ...matroskaToHex(matroskaElements.EBMLMaxSizeLength),
34
- ...getVariableInt(1),
35
- 8,
36
- ...matroskaToHex(matroskaElements.DocType),
37
- ...getVariableInt(8),
38
- ...new TextEncoder().encode('matroska'),
39
- ...matroskaToHex(matroskaElements.DocTypeVersion),
40
- ...getVariableInt(1),
41
- 4,
42
- ...matroskaToHex(matroskaElements.DocTypeReadVersion),
43
- ...getVariableInt(1),
44
- 2,
29
+ type Numbers = '0' | '1' | '2' | '3' | '4' | '5' | '6';
30
+
31
+ type ChildFields<Struct extends HeaderStructure> = {
32
+ [key in keyof Struct &
33
+ Numbers as Struct[key]['name']]: EmblTypes[Struct[key]['type']];
34
+ };
35
+
36
+ type SerializeValue<Struct extends Ebml> = Struct extends EbmlWithChildren
37
+ ? ChildFields<Struct['children']>
38
+ : Struct extends EbmlWithString
39
+ ? string
40
+ : Struct extends EbmlWithUint8
41
+ ? number
42
+ : Struct extends EbmlWithHexString
43
+ ? string
44
+ : undefined;
45
+
46
+ function putUintDynamic(number: number) {
47
+ if (number < 0) {
48
+ throw new Error(
49
+ 'This function is designed for non-negative integers only.',
50
+ );
51
+ }
52
+
53
+ // Calculate the minimum number of bytes needed to store the integer
54
+ const length = Math.ceil(Math.log2(number + 1) / 8);
55
+ const bytes = new Uint8Array(length);
56
+
57
+ for (let i = 0; i < length; i++) {
58
+ // Extract each byte from the number
59
+ bytes[length - 1 - i] = (number >> (8 * i)) & 0xff;
60
+ }
61
+
62
+ return bytes;
63
+ }
64
+
65
+ const makeFromHeaderStructure = <Struct extends Ebml>(
66
+ struct: Struct,
67
+ fields: SerializeValue<Struct>,
68
+ ): Uint8Array => {
69
+ const arrays: Uint8Array[] = [];
70
+
71
+ if (struct.type === 'children') {
72
+ for (const item of struct.children) {
73
+ arrays.push(
74
+ makeMatroskaHeader(
75
+ item,
76
+ // @ts-expect-error
77
+ fields[item.name],
78
+ ),
79
+ );
80
+ }
81
+
82
+ return combineUint8Arrays(arrays);
83
+ }
84
+
85
+ if (struct.type === 'string') {
86
+ return new TextEncoder().encode(fields as string);
87
+ }
88
+
89
+ if (struct.type === 'uint') {
90
+ return putUintDynamic(fields as number);
91
+ }
92
+
93
+ if (struct.type === 'hex-string') {
94
+ const hex = (fields as string).substring(2);
95
+ const arr = new Uint8Array(hex.length / 2);
96
+ for (let i = 0; i < hex.length; i += 2) {
97
+ const byte = parseInt(hex.substring(i, i + 2), 16);
98
+ arr[i / 2] = byte;
99
+ }
100
+
101
+ return arr;
102
+ }
103
+
104
+ if (struct.type === 'void') {
105
+ throw new Error('Serializing Void is not implemented');
106
+ }
107
+
108
+ throw new Error('Unexpected type');
109
+ };
110
+
111
+ export const makeMatroskaHeader = <Struct extends Ebml>(
112
+ struct: Struct,
113
+ fields: SerializeValue<Struct>,
114
+ ) => {
115
+ const value = makeFromHeaderStructure(struct, fields);
116
+
117
+ return combineUint8Arrays([
118
+ matroskaToHex(getIdForName(struct.name)),
119
+ getVariableInt(value.length),
120
+ value,
45
121
  ]);
122
+ };
123
+
124
+ const combineUint8Arrays = (arrays: Uint8Array[]) => {
125
+ let totalLength = 0;
126
+ for (const array of arrays) {
127
+ totalLength += array.length;
128
+ }
129
+
130
+ const result = new Uint8Array(totalLength);
131
+ let offset = 0;
132
+ for (const array of arrays) {
133
+ result.set(array, offset);
134
+ offset += array.length;
135
+ }
46
136
 
47
- return array;
137
+ return result;
48
138
  };
@@ -0,0 +1,93 @@
1
+ import type {BufferIterator} from '../../buffer-iterator';
2
+ import type {PossibleEbml} from './segments/all-segments';
3
+ import {ebmlMap, type Ebml, type EbmlParsed} from './segments/all-segments';
4
+
5
+ type Prettify<T> = {
6
+ [K in keyof T]: T[K];
7
+ } & {};
8
+
9
+ export const parseEbml = (iterator: BufferIterator): Prettify<PossibleEbml> => {
10
+ const hex = iterator.getMatroskaSegmentId();
11
+ if (hex === null) {
12
+ throw new Error(
13
+ 'Not enough bytes left to parse EBML - this should not happen',
14
+ );
15
+ }
16
+
17
+ const hasInMap = ebmlMap[hex as keyof typeof ebmlMap];
18
+ if (!hasInMap) {
19
+ throw new Error(
20
+ `Don't know how to parse EBML hex ID ${JSON.stringify(hex)}`,
21
+ );
22
+ }
23
+
24
+ const size = iterator.getVint();
25
+ if (size === null) {
26
+ throw new Error(
27
+ 'Not enough bytes left to parse EBML - this should not happen',
28
+ );
29
+ }
30
+
31
+ if (hasInMap.type === 'uint') {
32
+ const value = iterator.getUint(size);
33
+
34
+ return {type: hasInMap.name, value, hex};
35
+ }
36
+
37
+ if (hasInMap.type === 'string') {
38
+ const value = iterator.getByteString(size);
39
+
40
+ return {
41
+ type: hasInMap.name,
42
+ value,
43
+ hex,
44
+ };
45
+ }
46
+
47
+ if (hasInMap.type === 'float') {
48
+ const value = size === 4 ? iterator.getFloat32() : iterator.getFloat64();
49
+
50
+ return {
51
+ type: hasInMap.name,
52
+ value,
53
+ hex,
54
+ };
55
+ }
56
+
57
+ if (hasInMap.type === 'void') {
58
+ iterator.discard(size);
59
+
60
+ return {
61
+ type: hasInMap.name,
62
+ value: undefined,
63
+ hex,
64
+ };
65
+ }
66
+
67
+ if (hasInMap.type === 'children') {
68
+ const children: EbmlParsed<Ebml>[] = [];
69
+ const startOffset = iterator.counter.getOffset();
70
+
71
+ // eslint-disable-next-line no-constant-condition
72
+ while (true) {
73
+ const value = parseEbml(iterator);
74
+ children.push(value);
75
+ const offsetNow = iterator.counter.getOffset();
76
+
77
+ if (offsetNow - startOffset > size) {
78
+ throw new Error(
79
+ `Offset ${offsetNow - startOffset} is larger than the length of the hex ${size}`,
80
+ );
81
+ }
82
+
83
+ if (offsetNow - startOffset === size) {
84
+ break;
85
+ }
86
+ }
87
+
88
+ return {type: hasInMap.name, value: children as EbmlParsed<Ebml>[], hex};
89
+ }
90
+
91
+ // @ts-expect-error
92
+ throw new Error(`Unknown segment type ${hasInMap.type}`);
93
+ };