@remotion/media-parser 4.0.191 → 4.0.193

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 (66) hide show
  1. package/boxes.json +1 -0
  2. package/dist/boxes/iso-base-media/esds/esds-descriptors.d.ts +21 -0
  3. package/dist/boxes/iso-base-media/esds/esds-descriptors.js +62 -0
  4. package/dist/boxes/iso-base-media/esds/esds.d.ts +15 -0
  5. package/dist/boxes/iso-base-media/esds/esds.js +27 -0
  6. package/dist/boxes/iso-base-media/ftype.d.ts +9 -0
  7. package/dist/boxes/iso-base-media/ftype.js +31 -0
  8. package/dist/boxes/iso-base-media/mdhd.d.ts +14 -0
  9. package/dist/boxes/iso-base-media/mdhd.js +33 -0
  10. package/dist/boxes/iso-base-media/process-box.js +30 -0
  11. package/dist/boxes/iso-base-media/stsd/samples.d.ts +2 -0
  12. package/dist/boxes/iso-base-media/stsd/samples.js +28 -8
  13. package/dist/boxes/webm/parse-webm-header.js +4 -4
  14. package/dist/boxes/webm/segments/track-entry.d.ts +30 -0
  15. package/dist/boxes/webm/segments/track-entry.js +59 -8
  16. package/dist/boxes/webm/segments.d.ts +2 -2
  17. package/dist/boxes/webm/segments.js +18 -0
  18. package/dist/buffer-iterator.d.ts +2 -1
  19. package/dist/buffer-iterator.js +29 -8
  20. package/dist/from-node.js +6 -2
  21. package/dist/from-web.js +6 -1
  22. package/dist/get-audio-codec.d.ts +4 -0
  23. package/dist/get-audio-codec.js +106 -0
  24. package/dist/get-dimensions.js +6 -2
  25. package/dist/get-fps.d.ts +8 -0
  26. package/dist/get-fps.js +117 -9
  27. package/dist/get-video-codec.d.ts +4 -0
  28. package/dist/get-video-codec.js +79 -0
  29. package/dist/get-video-metadata.d.ts +2 -0
  30. package/dist/get-video-metadata.js +44 -0
  31. package/dist/has-all-info.d.ts +1 -1
  32. package/dist/has-all-info.js +8 -0
  33. package/dist/options.d.ts +11 -3
  34. package/dist/parse-media.js +27 -6
  35. package/dist/parse-result.d.ts +3 -1
  36. package/dist/read-and-increment-offset.d.ts +28 -0
  37. package/dist/read-and-increment-offset.js +177 -0
  38. package/dist/reader.d.ts +5 -1
  39. package/package.json +2 -2
  40. package/src/boxes/iso-base-media/esds/esds-descriptors.ts +104 -0
  41. package/src/boxes/iso-base-media/esds/esds.ts +49 -0
  42. package/src/boxes/iso-base-media/mdhd.ts +56 -0
  43. package/src/boxes/iso-base-media/process-box.ts +35 -0
  44. package/src/boxes/iso-base-media/stsd/samples.ts +36 -8
  45. package/src/boxes/webm/parse-webm-header.ts +4 -4
  46. package/src/boxes/webm/segments/track-entry.ts +103 -11
  47. package/src/boxes/webm/segments.ts +43 -1
  48. package/src/buffer-iterator.ts +36 -10
  49. package/src/from-node.ts +6 -4
  50. package/src/from-web.ts +8 -1
  51. package/src/get-audio-codec.ts +143 -0
  52. package/src/get-dimensions.ts +11 -4
  53. package/src/get-fps.ts +175 -9
  54. package/src/get-video-codec.ts +104 -0
  55. package/src/has-all-info.ts +19 -2
  56. package/src/options.ts +43 -3
  57. package/src/parse-media.ts +35 -7
  58. package/src/parse-result.ts +5 -1
  59. package/src/reader.ts +5 -1
  60. package/src/test/matroska.test.ts +6 -7
  61. package/src/test/parse-esds.test.ts +75 -0
  62. package/src/test/parse-webm.test.ts +2 -0
  63. package/src/test/stream-local.test.ts +93 -5
  64. package/src/test/stream-remote.test.ts +41 -0
  65. package/src/test/stsd.test.ts +52 -5
  66. package/tsconfig.tsbuildinfo +1 -1
package/src/get-fps.ts CHANGED
@@ -1,18 +1,183 @@
1
1
  import type {SttsBox} from './boxes/iso-base-media/stts/stts';
2
2
  import type {AnySegment} from './parse-result';
3
3
 
4
- const calculateFps = (sttsBox: SttsBox, timeScale: number) => {
5
- let sum = 0;
4
+ const calculateFps = ({
5
+ sttsBox,
6
+ timeScale,
7
+ durationInSamples,
8
+ }: {
9
+ sttsBox: SttsBox;
10
+ timeScale: number;
11
+ durationInSamples: number;
12
+ }) => {
6
13
  let totalSamples = 0;
14
+
7
15
  for (const sample of sttsBox.sampleDistribution) {
8
- sum += sample.sampleCount * sample.sampleDelta;
9
16
  totalSamples += sample.sampleCount;
10
17
  }
11
18
 
12
- return timeScale / (sum / totalSamples);
19
+ const durationInSeconds = durationInSamples / timeScale;
20
+ const fps = totalSamples / durationInSeconds;
21
+
22
+ return fps;
23
+ };
24
+
25
+ type TimescaleAndDuration = {
26
+ timescale: number;
27
+ duration: number;
28
+ };
29
+
30
+ export const trakBoxContainsAudio = (trakBox: AnySegment): boolean => {
31
+ if (trakBox.type !== 'trak-box') {
32
+ return false;
33
+ }
34
+
35
+ const {children} = trakBox;
36
+ const mediaBoxes = children.filter(
37
+ (c) => c.type === 'regular-box' && c.boxType === 'mdia',
38
+ );
39
+ if (!mediaBoxes || mediaBoxes.length === 0) {
40
+ return false;
41
+ }
42
+
43
+ const firstMediaBox = mediaBoxes[0];
44
+ if (
45
+ firstMediaBox.type !== 'regular-box' ||
46
+ firstMediaBox.boxType !== 'mdia'
47
+ ) {
48
+ return false;
49
+ }
50
+
51
+ const minf = firstMediaBox.children.find(
52
+ (c) => c.type === 'regular-box' && c.boxType === 'minf',
53
+ );
54
+ if (!minf || minf.type !== 'regular-box' || minf.boxType !== 'minf') {
55
+ return false;
56
+ }
57
+
58
+ const stbl = minf.children.find(
59
+ (c) => c.type === 'regular-box' && c.boxType === 'stbl',
60
+ );
61
+ if (!stbl || stbl.type !== 'regular-box' || stbl.boxType !== 'stbl') {
62
+ return false;
63
+ }
64
+
65
+ const stsd = stbl.children.find((c) => c.type === 'stsd-box');
66
+ if (!stsd || stsd.type !== 'stsd-box') {
67
+ return false;
68
+ }
69
+
70
+ const videoSample = stsd.samples.find((s) => s.type === 'audio');
71
+ if (!videoSample || videoSample.type !== 'audio') {
72
+ return false;
73
+ }
74
+
75
+ return true;
76
+ };
77
+
78
+ export const trakBoxContainsVideo = (trakBox: AnySegment): boolean => {
79
+ if (trakBox.type !== 'trak-box') {
80
+ return false;
81
+ }
82
+
83
+ const {children} = trakBox;
84
+ const mediaBoxes = children.filter(
85
+ (c) => c.type === 'regular-box' && c.boxType === 'mdia',
86
+ );
87
+ if (!mediaBoxes || mediaBoxes.length === 0) {
88
+ return false;
89
+ }
90
+
91
+ const firstMediaBox = mediaBoxes[0];
92
+ if (
93
+ firstMediaBox.type !== 'regular-box' ||
94
+ firstMediaBox.boxType !== 'mdia'
95
+ ) {
96
+ return false;
97
+ }
98
+
99
+ const minf = firstMediaBox.children.find(
100
+ (c) => c.type === 'regular-box' && c.boxType === 'minf',
101
+ );
102
+ if (!minf || minf.type !== 'regular-box' || minf.boxType !== 'minf') {
103
+ return false;
104
+ }
105
+
106
+ const stbl = minf.children.find(
107
+ (c) => c.type === 'regular-box' && c.boxType === 'stbl',
108
+ );
109
+ if (!stbl || stbl.type !== 'regular-box' || stbl.boxType !== 'stbl') {
110
+ return false;
111
+ }
112
+
113
+ const stsd = stbl.children.find((c) => c.type === 'stsd-box');
114
+ if (!stsd || stsd.type !== 'stsd-box') {
115
+ return false;
116
+ }
117
+
118
+ const videoSample = stsd.samples.find((s) => s.type === 'video');
119
+ if (!videoSample || videoSample.type !== 'video') {
120
+ return false;
121
+ }
122
+
123
+ return true;
124
+ };
125
+
126
+ export const getTimescaleAndDuration = (
127
+ boxes: AnySegment[],
128
+ ): TimescaleAndDuration | null => {
129
+ const moovBox = boxes.find((s) => s.type === 'moov-box');
130
+ if (!moovBox || moovBox.type !== 'moov-box') {
131
+ return null;
132
+ }
133
+
134
+ const {children} = moovBox;
135
+ const trackBoxes = children.filter((c) => c.type === 'trak-box');
136
+ if (!trackBoxes || trackBoxes.length === 0) {
137
+ return null;
138
+ }
139
+
140
+ const trackBox = trackBoxes.find(trakBoxContainsVideo);
141
+ if (!trackBox || trackBox.type !== 'trak-box') {
142
+ return null;
143
+ }
144
+
145
+ const trackBoxChildren = trackBox.children;
146
+ if (!trackBoxChildren || trackBoxChildren.length === 0) {
147
+ return null;
148
+ }
149
+
150
+ const mdiaBox = trackBoxChildren.find(
151
+ (c) => c.type === 'regular-box' && c.boxType === 'mdia',
152
+ );
153
+ if (
154
+ !mdiaBox ||
155
+ mdiaBox.type !== 'regular-box' ||
156
+ mdiaBox.boxType !== 'mdia'
157
+ ) {
158
+ return null;
159
+ }
160
+
161
+ const mdhdBox = mdiaBox?.children.find((c) => c.type === 'mdhd-box');
162
+ if (mdhdBox && mdhdBox.type === 'mdhd-box') {
163
+ return {timescale: mdhdBox.timescale, duration: mdhdBox.duration};
164
+ }
165
+
166
+ const mvhdBox = moovBox.children.find((c) => c.type === 'mvhd-box');
167
+ if (!mvhdBox || mvhdBox.type !== 'mvhd-box') {
168
+ return null;
169
+ }
170
+
171
+ const {timeScale, durationInUnits} = mvhdBox;
172
+ return {timescale: timeScale, duration: durationInUnits};
13
173
  };
14
174
 
15
175
  export const getFps = (segments: AnySegment[]) => {
176
+ const timescaleAndDuration = getTimescaleAndDuration(segments);
177
+ if (!timescaleAndDuration) {
178
+ return null;
179
+ }
180
+
16
181
  const moovBox = segments.find((s) => s.type === 'moov-box');
17
182
  if (!moovBox || moovBox.type !== 'moov-box') {
18
183
  return null;
@@ -23,16 +188,13 @@ export const getFps = (segments: AnySegment[]) => {
23
188
  return null;
24
189
  }
25
190
 
26
- const {timeScale} = mvhdBox;
27
-
28
191
  const {children} = moovBox;
29
192
  const trackBoxes = children.filter((c) => c.type === 'trak-box');
30
193
  if (!trackBoxes || trackBoxes.length === 0) {
31
194
  return null;
32
195
  }
33
196
 
34
- // TODO: What if the video track is not the first track?
35
- const trackBox = trackBoxes[0];
197
+ const trackBox = trackBoxes.find(trakBoxContainsVideo);
36
198
  if (!trackBox || trackBox.type !== 'trak-box') {
37
199
  return null;
38
200
  }
@@ -80,7 +242,11 @@ export const getFps = (segments: AnySegment[]) => {
80
242
  return null;
81
243
  }
82
244
 
83
- return calculateFps(sttsBox, timeScale);
245
+ return calculateFps({
246
+ sttsBox,
247
+ timeScale: timescaleAndDuration.timescale,
248
+ durationInSamples: timescaleAndDuration.duration,
249
+ });
84
250
  };
85
251
 
86
252
  export const hasFps = (boxes: AnySegment[]): boolean => {
@@ -0,0 +1,104 @@
1
+ /* eslint-disable max-depth */
2
+ import {trakBoxContainsVideo} from './get-fps';
3
+ import type {KnownVideoCodecs} from './options';
4
+ import type {AnySegment} from './parse-result';
5
+
6
+ export const hasVideoCodec = (boxes: AnySegment[]): boolean => {
7
+ try {
8
+ return getVideoCodec(boxes) !== null;
9
+ } catch (e) {
10
+ return false;
11
+ }
12
+ };
13
+
14
+ export const getVideoCodec = (boxes: AnySegment[]): KnownVideoCodecs | null => {
15
+ const moovBox = boxes.find((b) => b.type === 'moov-box');
16
+ if (moovBox && moovBox.type === 'moov-box') {
17
+ const trakBox = moovBox.children.find(
18
+ (b) => b.type === 'trak-box' && trakBoxContainsVideo(b),
19
+ );
20
+ if (trakBox && trakBox.type === 'trak-box') {
21
+ const mdiaBox = trakBox.children.find(
22
+ (b) => b.type === 'regular-box' && b.boxType === 'mdia',
23
+ );
24
+ if (
25
+ mdiaBox &&
26
+ mdiaBox.type === 'regular-box' &&
27
+ mdiaBox.boxType === 'mdia'
28
+ ) {
29
+ const minfBox = mdiaBox?.children.find(
30
+ (b) => b.type === 'regular-box' && b.boxType === 'minf',
31
+ );
32
+ if (
33
+ minfBox &&
34
+ minfBox.type === 'regular-box' &&
35
+ minfBox.boxType === 'minf'
36
+ ) {
37
+ const stblBox = minfBox?.children.find(
38
+ (b) => b.type === 'regular-box' && b.boxType === 'stbl',
39
+ );
40
+ if (stblBox && stblBox.type === 'regular-box') {
41
+ const stsdBox = stblBox?.children.find(
42
+ (b) => b.type === 'stsd-box',
43
+ );
44
+ if (stsdBox && stsdBox.type === 'stsd-box') {
45
+ const videoSample = stsdBox.samples.find(
46
+ (s) => s.type === 'video',
47
+ );
48
+ if (videoSample && videoSample.type === 'video') {
49
+ if (videoSample.format === 'hvc1') {
50
+ return 'h265';
51
+ }
52
+
53
+ if (videoSample.format === 'avc1') {
54
+ return 'h264';
55
+ }
56
+
57
+ if (videoSample.format === 'ap4h') {
58
+ return 'prores';
59
+ }
60
+ }
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ const mainSegment = boxes.find((b) => b.type === 'main-segment');
69
+ if (!mainSegment || mainSegment.type !== 'main-segment') {
70
+ return null;
71
+ }
72
+
73
+ const tracksSegment = mainSegment.children.find(
74
+ (b) => b.type === 'tracks-segment',
75
+ );
76
+ if (!tracksSegment || tracksSegment.type !== 'tracks-segment') {
77
+ return null;
78
+ }
79
+
80
+ for (const track of tracksSegment.children) {
81
+ if (track.type === 'track-entry-segment') {
82
+ const trackType = track.children.find((b) => b.type === 'codec-segment');
83
+ if (trackType && trackType.type === 'codec-segment') {
84
+ if (trackType.codec === 'V_VP8') {
85
+ return 'vp8';
86
+ }
87
+
88
+ if (trackType.codec === 'V_VP9') {
89
+ return 'vp9';
90
+ }
91
+
92
+ if (trackType.codec === 'V_AV1') {
93
+ return 'av1';
94
+ }
95
+
96
+ if (trackType.codec === 'V_MPEG4/ISO/AVC') {
97
+ return 'h264';
98
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ return null;
104
+ };
@@ -1,16 +1,25 @@
1
+ import {hasAudioCodec} from './get-audio-codec';
1
2
  import {hasDimensions} from './get-dimensions';
2
3
  import {hasDuration} from './get-duration';
3
4
  import {hasFps} from './get-fps';
5
+ import {hasVideoCodec} from './get-video-codec';
4
6
  import type {Options} from './options';
5
7
  import type {ParseResult} from './parse-result';
6
8
 
7
9
  export const hasAllInfo = (
8
- options: Options<boolean, boolean, boolean, boolean>,
10
+ options: Options<boolean, boolean, boolean, boolean, boolean, boolean>,
9
11
  parseResult: ParseResult,
10
12
  ) => {
11
13
  const keys = Object.entries(options)
12
14
  .filter(([, value]) => value)
13
- .map(([key]) => key) as (keyof Options<true, true, true, true>)[];
15
+ .map(([key]) => key) as (keyof Options<
16
+ true,
17
+ true,
18
+ true,
19
+ true,
20
+ true,
21
+ true
22
+ >)[];
14
23
 
15
24
  return keys.every((key) => {
16
25
  if (key === 'boxes') {
@@ -29,6 +38,14 @@ export const hasAllInfo = (
29
38
  return hasFps(parseResult.segments) !== null;
30
39
  }
31
40
 
41
+ if (key === 'videoCodec') {
42
+ return hasVideoCodec(parseResult.segments) !== null;
43
+ }
44
+
45
+ if (key === 'audioCodec') {
46
+ return hasAudioCodec(parseResult.segments) !== null;
47
+ }
48
+
32
49
  throw new Error(`Unknown key: ${key satisfies never}`);
33
50
  });
34
51
  };
package/src/options.ts CHANGED
@@ -2,16 +2,36 @@ import type {Dimensions} from './get-dimensions';
2
2
  import type {AnySegment} from './parse-result';
3
3
  import type {ReaderInterface} from './reader';
4
4
 
5
+ export type KnownVideoCodecs =
6
+ | 'h264'
7
+ | 'h265'
8
+ | 'vp8'
9
+ | 'vp9'
10
+ | 'av1'
11
+ | 'prores';
12
+
13
+ export type KnownAudioCodecs =
14
+ | 'aac'
15
+ | 'mp3'
16
+ | 'aiff'
17
+ | 'opus'
18
+ | 'pcm'
19
+ | 'unknown';
20
+
5
21
  export type Options<
6
22
  EnableDimensions extends boolean,
7
23
  EnableDuration extends boolean,
8
24
  EnableBoxes extends boolean,
9
25
  EnableFps extends boolean,
26
+ EnableVideoCodec extends boolean,
27
+ EnableAudioCodec extends boolean,
10
28
  > = {
11
29
  dimensions?: EnableDimensions;
12
30
  durationInSeconds?: EnableDuration;
13
31
  boxes?: EnableBoxes;
14
32
  fps?: EnableFps;
33
+ videoCodec?: EnableVideoCodec;
34
+ audioCodec?: EnableAudioCodec;
15
35
  };
16
36
 
17
37
  export type Metadata<
@@ -19,20 +39,40 @@ export type Metadata<
19
39
  EnableDuration extends boolean,
20
40
  EnableBoxes extends boolean,
21
41
  EnableFps extends boolean,
42
+ EnableVideoCodec extends boolean,
43
+ EnableAudioCodec extends boolean,
22
44
  > = (EnableDimensions extends true ? {dimensions: Dimensions} : {}) &
23
45
  (EnableDuration extends true ? {durationInSeconds: number | null} : {}) &
24
46
  (EnableBoxes extends true ? {boxes: AnySegment[]} : {}) &
25
- (EnableFps extends true ? {fps: number | null} : {});
47
+ (EnableFps extends true ? {fps: number | null} : {}) &
48
+ (EnableVideoCodec extends true ? {videoCodec: KnownVideoCodecs | null} : {}) &
49
+ (EnableAudioCodec extends true ? {audioCodec: KnownAudioCodecs | null} : {});
26
50
 
27
51
  export type ParseMedia = <
28
52
  EnableDimensions extends boolean,
29
53
  EnableDuration extends boolean,
30
54
  EnableBoxes extends boolean,
31
55
  EnableFps extends boolean,
56
+ EnableVideoCodec extends boolean,
57
+ EnableAudioCodec extends boolean,
32
58
  >(
33
59
  src: string,
34
- options: Options<EnableDimensions, EnableDuration, EnableBoxes, EnableFps>,
60
+ options: Options<
61
+ EnableDimensions,
62
+ EnableDuration,
63
+ EnableBoxes,
64
+ EnableFps,
65
+ EnableVideoCodec,
66
+ EnableAudioCodec
67
+ >,
35
68
  readerInterface?: ReaderInterface,
36
69
  ) => Promise<
37
- Metadata<EnableDimensions, EnableDuration, EnableBoxes, EnableFps>
70
+ Metadata<
71
+ EnableDimensions,
72
+ EnableDuration,
73
+ EnableBoxes,
74
+ EnableFps,
75
+ EnableVideoCodec,
76
+ EnableAudioCodec
77
+ >
38
78
  >;
@@ -1,10 +1,14 @@
1
+ import type {BufferIterator} from './buffer-iterator';
1
2
  import {getArrayBufferIterator} from './buffer-iterator';
2
3
  import {webReader} from './from-web';
4
+ import {getAudioCodec} from './get-audio-codec';
3
5
  import {getDimensions} from './get-dimensions';
4
6
  import {getDuration} from './get-duration';
5
7
  import {getFps} from './get-fps';
8
+ import {getVideoCodec} from './get-video-codec';
6
9
  import {hasAllInfo} from './has-all-info';
7
10
  import type {Metadata, ParseMedia} from './options';
11
+ import type {ParseResult} from './parse-result';
8
12
  import {parseVideo} from './parse-video';
9
13
 
10
14
  export const parseMedia: ParseMedia = async (
@@ -12,21 +16,33 @@ export const parseMedia: ParseMedia = async (
12
16
  options,
13
17
  readerInterface = webReader,
14
18
  ) => {
15
- const reader = await readerInterface.read(src, null);
19
+ const {reader, contentLength} = await readerInterface.read(src, null);
16
20
 
17
- const returnValue = {} as Metadata<true, true, true, true>;
21
+ const returnValue = {} as Metadata<true, true, true, true, true, true>;
18
22
 
19
- const iterator = getArrayBufferIterator(new Uint8Array([]));
20
- let parseResult = parseVideo(iterator);
23
+ let iterator: BufferIterator | null = null;
24
+ let parseResult: ParseResult | null = null;
21
25
 
22
- while (parseResult.status === 'incomplete') {
26
+ while (parseResult === null || parseResult.status === 'incomplete') {
23
27
  const result = await reader.read();
24
28
  if (result.done) {
25
29
  break;
26
30
  }
27
31
 
28
- iterator.addData(result.value);
29
- parseResult = parseResult.continueParsing();
32
+ if (iterator) {
33
+ iterator.addData(result.value);
34
+ } else {
35
+ iterator = getArrayBufferIterator(
36
+ result.value,
37
+ contentLength ?? undefined,
38
+ );
39
+ }
40
+
41
+ if (parseResult) {
42
+ parseResult = parseResult.continueParsing();
43
+ } else {
44
+ parseResult = parseVideo(iterator);
45
+ }
30
46
 
31
47
  if (hasAllInfo(options, parseResult)) {
32
48
  if (!reader.closed) {
@@ -37,6 +53,10 @@ export const parseMedia: ParseMedia = async (
37
53
  }
38
54
  }
39
55
 
56
+ if (!parseResult) {
57
+ throw new Error('Could not parse video');
58
+ }
59
+
40
60
  if (options.dimensions) {
41
61
  returnValue.dimensions = getDimensions(parseResult.segments);
42
62
  }
@@ -49,6 +69,14 @@ export const parseMedia: ParseMedia = async (
49
69
  returnValue.fps = getFps(parseResult.segments);
50
70
  }
51
71
 
72
+ if (options.videoCodec) {
73
+ returnValue.videoCodec = getVideoCodec(parseResult.segments);
74
+ }
75
+
76
+ if (options.audioCodec) {
77
+ returnValue.audioCodec = getAudioCodec(parseResult.segments);
78
+ }
79
+
52
80
  if (options.boxes) {
53
81
  returnValue.boxes = parseResult.segments;
54
82
  }
@@ -1,5 +1,7 @@
1
1
  import type {BaseBox} from './boxes/iso-base-media/base-type';
2
+ import type {EsdsBox} from './boxes/iso-base-media/esds/esds';
2
3
  import type {FtypBox} from './boxes/iso-base-media/ftyp';
4
+ import type {MdhdBox} from './boxes/iso-base-media/mdhd';
3
5
  import type {MoovBox} from './boxes/iso-base-media/moov/moov';
4
6
  import type {MvhdBox} from './boxes/iso-base-media/mvhd';
5
7
  import type {KeysBox} from './boxes/iso-base-media/stsd/keys';
@@ -28,7 +30,9 @@ export type IsoBaseMediaBox =
28
30
  | KeysBox
29
31
  | MoovBox
30
32
  | TrakBox
31
- | SttsBox;
33
+ | SttsBox
34
+ | MdhdBox
35
+ | EsdsBox;
32
36
 
33
37
  export type AnySegment = MatroskaSegment | IsoBaseMediaBox;
34
38
 
package/src/reader.ts CHANGED
@@ -1,7 +1,11 @@
1
+ type ReadResult = {
2
+ reader: ReadableStreamDefaultReader<Uint8Array>;
3
+ contentLength: number | null;
4
+ };
1
5
  type ReadContent = (
2
6
  src: string,
3
7
  range: [number, number] | null,
4
- ) => Promise<ReadableStreamDefaultReader<Uint8Array>>;
8
+ ) => Promise<ReadResult>;
5
9
  type GetLength = (src: string) => Promise<number>;
6
10
 
7
11
  export type ReaderInterface = {
@@ -140,19 +140,18 @@ test('Should get duration of AV1 video', async () => {
140
140
  ],
141
141
  },
142
142
  {
143
- id: '0x0163a294',
144
- type: 'unknown-segment',
143
+ codecPrivateData: [
144
+ 129, 8, 12, 0, 10, 14, 0, 0, 0, 66, 171, 191, 195, 118, 0,
145
+ 8, 8, 8, 8, 32,
146
+ ],
147
+ type: 'codec-private-segment',
145
148
  },
146
149
  ],
147
150
  },
148
- {
149
- id: '0x0c000a0e',
150
- type: 'unknown-segment',
151
- },
152
151
  ],
153
152
  },
154
153
  {
155
- id: '0x',
154
+ id: '0x1254c367',
156
155
  type: 'unknown-segment',
157
156
  },
158
157
  ],
@@ -0,0 +1,75 @@
1
+ import {expect, test} from 'bun:test';
2
+ import {parseEsds} from '../boxes/iso-base-media/esds/esds';
3
+ import {getArrayBufferIterator} from '../buffer-iterator';
4
+
5
+ test('Parse ESDS box', () => {
6
+ const buf = new Uint8Array([
7
+ // mock header
8
+ 0, 0, 0, 0, 0, 0, 0, 0,
9
+ // actual box
10
+ 0, 0, 0, 0, 3, 128, 128, 128, 27, 0, 2, 0, 4, 128, 128, 128, 13, 107, 21, 0,
11
+ 0, 0, 0, 4, 226, 0, 0, 4, 226, 0, 6, 128, 128, 128, 1, 2,
12
+ ]);
13
+
14
+ const iter = getArrayBufferIterator(buf);
15
+ iter.counter.increment(8);
16
+
17
+ expect(
18
+ parseEsds({
19
+ data: iter,
20
+ fileOffset: 8,
21
+ size: buf.length - 8,
22
+ }),
23
+ ).toEqual({
24
+ type: 'esds-box',
25
+ version: 0,
26
+ tag: 3,
27
+ sizeOfInstance: 27,
28
+ esId: 2,
29
+ descriptors: [
30
+ {
31
+ type: 'decoder-config-descriptor',
32
+ objectTypeIndication: 'mp3',
33
+ },
34
+ {
35
+ type: 'sl-config-descriptor',
36
+ },
37
+ ],
38
+ });
39
+ });
40
+
41
+ test('Parse two ESDS', () => {
42
+ const buf = new Uint8Array([
43
+ // mock header
44
+ 0, 0, 0, 0, 0, 0, 0, 0,
45
+ // actual box
46
+ 0, 0, 0, 0, 3, 25, 0, 0, 0, 4, 17, 64, 21, 0, 24, 0, 0, 4, 226, 0, 0, 4,
47
+ 226, 0, 5, 2, 17, 144, 6, 1, 2, 0, 0, 0, 24, 115, 116, 116, 115,
48
+ ]);
49
+
50
+ const iter = getArrayBufferIterator(buf);
51
+ iter.counter.increment(8);
52
+
53
+ expect(
54
+ parseEsds({
55
+ data: iter,
56
+ fileOffset: 8,
57
+ size: buf.length - 8,
58
+ }),
59
+ ).toEqual({
60
+ type: 'esds-box',
61
+ version: 0,
62
+ tag: 3,
63
+ sizeOfInstance: 25,
64
+ esId: 0,
65
+ descriptors: [
66
+ {
67
+ objectTypeIndication: 'aac',
68
+ type: 'decoder-config-descriptor',
69
+ },
70
+ {
71
+ type: 'sl-config-descriptor',
72
+ },
73
+ ],
74
+ });
75
+ });
@@ -8,8 +8,10 @@ test('should be able to parse a WebM', async () => {
8
8
  RenderInternals.exampleVideos.transparentWebm,
9
9
  {
10
10
  durationInSeconds: true,
11
+ videoCodec: true,
11
12
  },
12
13
  nodeReader,
13
14
  );
14
15
  expect(webm.durationInSeconds).toBe(5);
16
+ expect(webm.videoCodec).toBe('vp8');
15
17
  });