@remotion/media-parser 4.0.191 → 4.0.192

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 (40) hide show
  1. package/dist/boxes/iso-base-media/mdhd.d.ts +14 -0
  2. package/dist/boxes/iso-base-media/mdhd.js +33 -0
  3. package/dist/boxes/iso-base-media/process-box.js +13 -0
  4. package/dist/boxes/iso-base-media/stsd/samples.js +1 -0
  5. package/dist/boxes/webm/parse-webm-header.js +1 -1
  6. package/dist/boxes/webm/segments/track-entry.d.ts +10 -0
  7. package/dist/boxes/webm/segments/track-entry.js +23 -7
  8. package/dist/boxes/webm/segments.d.ts +2 -2
  9. package/dist/boxes/webm/segments.js +6 -0
  10. package/dist/buffer-iterator.js +2 -0
  11. package/dist/get-codec.d.ts +4 -0
  12. package/dist/get-codec.js +22 -0
  13. package/dist/get-fps.d.ts +7 -0
  14. package/dist/get-fps.js +84 -9
  15. package/dist/get-video-codec.d.ts +4 -0
  16. package/dist/get-video-codec.js +76 -0
  17. package/dist/has-all-info.d.ts +1 -1
  18. package/dist/has-all-info.js +4 -0
  19. package/dist/options.d.ts +7 -3
  20. package/dist/parse-media.js +4 -0
  21. package/dist/parse-result.d.ts +2 -1
  22. package/package.json +2 -2
  23. package/src/boxes/iso-base-media/mdhd.ts +56 -0
  24. package/src/boxes/iso-base-media/process-box.ts +15 -0
  25. package/src/boxes/iso-base-media/stsd/samples.ts +1 -0
  26. package/src/boxes/webm/parse-webm-header.ts +1 -1
  27. package/src/boxes/webm/segments/track-entry.ts +37 -10
  28. package/src/boxes/webm/segments.ts +15 -1
  29. package/src/buffer-iterator.ts +2 -0
  30. package/src/get-fps.ts +127 -9
  31. package/src/get-video-codec.ts +100 -0
  32. package/src/has-all-info.ts +7 -2
  33. package/src/options.ts +28 -3
  34. package/src/parse-media.ts +6 -1
  35. package/src/parse-result.ts +3 -1
  36. package/src/test/matroska.test.ts +5 -5
  37. package/src/test/parse-webm.test.ts +2 -0
  38. package/src/test/stream-local.test.ts +16 -5
  39. package/src/test/stream-remote.test.ts +41 -0
  40. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,14 @@
1
+ import type { BufferIterator } from '../../buffer-iterator';
2
+ export interface MdhdBox {
3
+ type: 'mdhd-box';
4
+ version: number;
5
+ timescale: number;
6
+ duration: number;
7
+ language: number;
8
+ quality: number;
9
+ }
10
+ export declare const parseMdhd: ({ data, size, fileOffset, }: {
11
+ data: BufferIterator;
12
+ size: number;
13
+ fileOffset: number;
14
+ }) => MdhdBox;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseMdhd = void 0;
4
+ const parseMdhd = ({ data, size, fileOffset, }) => {
5
+ const version = data.getUint8();
6
+ if (version !== 0) {
7
+ throw new Error(`Unsupported MDHD version ${version}`);
8
+ }
9
+ // flags, we discard them
10
+ data.discard(3);
11
+ // creation time
12
+ data.discard(4);
13
+ // modification time
14
+ data.discard(4);
15
+ const timescale = data.getUint32();
16
+ const duration = data.getUint32();
17
+ const language = data.getUint16();
18
+ // quality
19
+ const quality = data.getUint16();
20
+ const remaining = size - (data.counter.getOffset() - fileOffset);
21
+ if (remaining !== 0) {
22
+ throw new Error(`Expected remaining bytes to be 0, got ${remaining}`);
23
+ }
24
+ return {
25
+ type: 'mdhd-box',
26
+ duration,
27
+ timescale,
28
+ version,
29
+ language,
30
+ quality,
31
+ };
32
+ };
33
+ exports.parseMdhd = parseMdhd;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseBoxes = void 0;
4
4
  const ftyp_1 = require("./ftyp");
5
+ const mdhd_1 = require("./mdhd");
5
6
  const moov_1 = require("./moov/moov");
6
7
  const mvhd_1 = require("./mvhd");
7
8
  const mebx_1 = require("./stsd/mebx");
@@ -119,6 +120,18 @@ const processBox = ({ iterator, allowIncompleteBoxes, }) => {
119
120
  size: boxSize,
120
121
  };
121
122
  }
123
+ if (boxType === 'mdhd') {
124
+ const box = (0, mdhd_1.parseMdhd)({
125
+ data: iterator,
126
+ size: boxSize,
127
+ fileOffset,
128
+ });
129
+ return {
130
+ type: 'complete',
131
+ box,
132
+ size: boxSize,
133
+ };
134
+ }
122
135
  const bytesRemainingInBox = boxSize - (iterator.counter.getOffset() - fileOffset);
123
136
  const children = getChildren({
124
137
  boxType,
@@ -30,6 +30,7 @@ const videoTags = [
30
30
  'v410',
31
31
  'v210',
32
32
  'hvc1',
33
+ 'ap4h',
33
34
  ];
34
35
  // https://developer.apple.com/documentation/quicktime-file-format/sound_sample_descriptions
35
36
  const audioTags = [
@@ -5,7 +5,7 @@ const segments_1 = require("./segments");
5
5
  // Parsing according to https://darkcoding.net/software/reading-mediarecorders-webm-opus-output/
6
6
  const parseWebm = (counter) => {
7
7
  counter.discard(4);
8
- const length = counter.getEBML();
8
+ const length = counter.getVint();
9
9
  if (length !== 31) {
10
10
  throw new Error(`Expected header length 31, got ${length}`);
11
11
  }
@@ -69,3 +69,13 @@ export type ColorSegment = {
69
69
  type: 'color-segment';
70
70
  };
71
71
  export declare const parseColorSegment: (iterator: BufferIterator) => ColorSegment;
72
+ export type TitleSegment = {
73
+ type: 'title-segment';
74
+ title: string;
75
+ };
76
+ export declare const parseTitleSegment: (iterator: BufferIterator) => TitleSegment;
77
+ export type InterlacedSegment = {
78
+ type: 'interlaced-segment';
79
+ interlaced: boolean;
80
+ };
81
+ export declare const parseInterlacedSegment: (iterator: BufferIterator) => InterlacedSegment;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseColorSegment = exports.parseMaxBlockAdditionId = exports.parseAlphaModeSegment = exports.parseHeightSegment = exports.parseWidthSegment = exports.parseVideoSegment = exports.parseDefaultDurationSegment = exports.parseTrackTypeSegment = exports.parseCodecSegment = exports.parseLanguageSegment = exports.parseFlagLacing = exports.parseTrackUID = exports.parseTrackNumber = exports.parseTrackEntry = void 0;
3
+ exports.parseInterlacedSegment = exports.parseTitleSegment = exports.parseColorSegment = exports.parseMaxBlockAdditionId = exports.parseAlphaModeSegment = exports.parseHeightSegment = exports.parseWidthSegment = exports.parseVideoSegment = exports.parseDefaultDurationSegment = exports.parseTrackTypeSegment = exports.parseCodecSegment = exports.parseLanguageSegment = exports.parseFlagLacing = exports.parseTrackUID = exports.parseTrackNumber = exports.parseTrackEntry = void 0;
4
4
  const parse_children_1 = require("./parse-children");
5
5
  const parseTrackEntry = (iterator) => {
6
6
  const offset = iterator.counter.getOffset();
@@ -25,10 +25,6 @@ const parseTrackNumber = (iterator) => {
25
25
  exports.parseTrackNumber = parseTrackNumber;
26
26
  const parseTrackUID = (iterator) => {
27
27
  const length = iterator.getVint();
28
- // Observation: AV1 has 8 bytes, WebM has 7
29
- if (length !== 8 && length !== 7) {
30
- throw new Error('Expected track number to be 8 byte');
31
- }
32
28
  const bytes = iterator.getSlice(length);
33
29
  const asString = [...bytes]
34
30
  .map((b) => b.toString(16).padStart(2, '0'))
@@ -109,11 +105,10 @@ const parseDefaultDurationSegment = (iterator) => {
109
105
  };
110
106
  exports.parseDefaultDurationSegment = parseDefaultDurationSegment;
111
107
  const parseVideoSegment = (iterator) => {
112
- const offset = iterator.counter.getOffset();
113
108
  const length = iterator.getVint();
114
109
  return {
115
110
  type: 'video-segment',
116
- children: (0, parse_children_1.expectChildren)(iterator, length - (iterator.counter.getOffset() - offset)),
111
+ children: (0, parse_children_1.expectChildren)(iterator, length),
117
112
  };
118
113
  };
119
114
  exports.parseVideoSegment = parseVideoSegment;
@@ -173,3 +168,24 @@ const parseColorSegment = (iterator) => {
173
168
  };
174
169
  };
175
170
  exports.parseColorSegment = parseColorSegment;
171
+ const parseTitleSegment = (iterator) => {
172
+ const length = iterator.getVint();
173
+ const title = iterator.getByteString(length);
174
+ return {
175
+ type: 'title-segment',
176
+ title,
177
+ };
178
+ };
179
+ exports.parseTitleSegment = parseTitleSegment;
180
+ const parseInterlacedSegment = (iterator) => {
181
+ const length = iterator.getVint();
182
+ if (length !== 1) {
183
+ throw new Error('Expected interlaced segment to be 1 byte');
184
+ }
185
+ const interlaced = iterator.getUint8();
186
+ return {
187
+ type: 'interlaced-segment',
188
+ interlaced: Boolean(interlaced),
189
+ };
190
+ };
191
+ exports.parseInterlacedSegment = parseInterlacedSegment;
@@ -7,10 +7,10 @@ import { type SeekSegment } from './segments/seek';
7
7
  import type { SeekHeadSegment } from './segments/seek-head';
8
8
  import { type SeekPositionSegment } from './segments/seek-position';
9
9
  import type { TimestampScaleSegment } from './segments/timestamp-scale';
10
- import type { AlphaModeSegment, CodecSegment, ColorSegment, DefaultDurationSegment, FlagLacingSegment, HeightSegment, LanguageSegment, MaxBlockAdditionId, TrackEntrySegment, TrackNumberSegment, TrackTypeSegment, TrackUIDSegment, VideoSegment, WidthSegment } from './segments/track-entry';
10
+ import type { AlphaModeSegment, CodecSegment, ColorSegment, DefaultDurationSegment, FlagLacingSegment, HeightSegment, InterlacedSegment, LanguageSegment, MaxBlockAdditionId, TitleSegment, TrackEntrySegment, TrackNumberSegment, TrackTypeSegment, TrackUIDSegment, VideoSegment, WidthSegment } from './segments/track-entry';
11
11
  import type { TracksSegment } from './segments/tracks';
12
12
  import type { UnknownSegment } from './segments/unknown';
13
13
  import type { VoidSegment } from './segments/void';
14
14
  import type { WritingAppSegment } from './segments/writing';
15
- export type MatroskaSegment = MainSegment | UnknownSegment | SeekHeadSegment | SeekSegment | SeekPositionSegment | VoidSegment | InfoSegment | TimestampScaleSegment | MuxingAppSegment | WritingAppSegment | DurationSegment | TracksSegment | TrackEntrySegment | TrackNumberSegment | TrackUIDSegment | FlagLacingSegment | LanguageSegment | CodecSegment | TrackTypeSegment | DefaultDurationSegment | VideoSegment | WidthSegment | HeightSegment | AlphaModeSegment | MaxBlockAdditionId | ColorSegment;
15
+ export type MatroskaSegment = MainSegment | UnknownSegment | SeekHeadSegment | SeekSegment | SeekPositionSegment | VoidSegment | InfoSegment | TimestampScaleSegment | MuxingAppSegment | WritingAppSegment | DurationSegment | TracksSegment | TrackEntrySegment | TrackNumberSegment | TrackUIDSegment | FlagLacingSegment | LanguageSegment | CodecSegment | TrackTypeSegment | DefaultDurationSegment | VideoSegment | WidthSegment | HeightSegment | AlphaModeSegment | MaxBlockAdditionId | ColorSegment | TitleSegment | InterlacedSegment;
16
16
  export declare const expectSegment: (iterator: BufferIterator) => MatroskaSegment;
@@ -94,9 +94,15 @@ const expectSegment = (iterator) => {
94
94
  if (segmentId === '0xba') {
95
95
  return (0, track_entry_1.parseHeightSegment)(iterator);
96
96
  }
97
+ if (segmentId === '0x9a') {
98
+ return (0, track_entry_1.parseInterlacedSegment)(iterator);
99
+ }
97
100
  if (segmentId === '0x53c0') {
98
101
  return (0, track_entry_1.parseAlphaModeSegment)(iterator);
99
102
  }
103
+ if (segmentId === '0x7ba9') {
104
+ return (0, track_entry_1.parseTitleSegment)(iterator);
105
+ }
100
106
  const length = iterator.getVint();
101
107
  const bytesRemaining = iterator.byteLength() - iterator.counter.getOffset();
102
108
  const toDiscard = Math.min(bytesRemaining, length > 0 ? length : bytesRemaining);
@@ -139,6 +139,7 @@ const getArrayBufferIterator = (initialData) => {
139
139
  '0xe0',
140
140
  '0xb0',
141
141
  '0xba',
142
+ '0x9a',
142
143
  ];
143
144
  if (knownIdsWithOneLength.includes(firstOneString)) {
144
145
  return firstOneString;
@@ -155,6 +156,7 @@ const getArrayBufferIterator = (initialData) => {
155
156
  '0x4489',
156
157
  '0x55ee',
157
158
  '0x55b0',
159
+ '0x7ba9',
158
160
  ];
159
161
  const firstTwoString = `${firstOneString}${Array.from(new Uint8Array(firstTwo))
160
162
  .map((b) => {
@@ -0,0 +1,4 @@
1
+ import type { KnownVideoCodecs } from './options';
2
+ import type { AnySegment } from './parse-result';
3
+ export declare const hasVideoCodec: (boxes: AnySegment[]) => boolean;
4
+ export declare const getVideoCodec: (boxes: AnySegment[]) => KnownVideoCodecs | null;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getVideoCodec = exports.hasVideoCodec = void 0;
4
+ const hasVideoCodec = (boxes) => {
5
+ try {
6
+ return boxes.some((b) => b.type === 'ftyp-box');
7
+ }
8
+ catch (err) {
9
+ return false;
10
+ }
11
+ };
12
+ exports.hasVideoCodec = hasVideoCodec;
13
+ const getVideoCodec = (boxes) => {
14
+ const ftypBox = boxes.find((b) => b.type === 'ftyp-box');
15
+ if (ftypBox && ftypBox.type === 'ftyp-box') {
16
+ if (ftypBox.compatibleBrands.find((b) => b === 'avc1')) {
17
+ return 'h264';
18
+ }
19
+ }
20
+ return null;
21
+ };
22
+ exports.getVideoCodec = getVideoCodec;
package/dist/get-fps.d.ts CHANGED
@@ -1,3 +1,10 @@
1
1
  import type { AnySegment } from './parse-result';
2
+ type TimescaleAndDuration = {
3
+ timescale: number;
4
+ duration: number;
5
+ };
6
+ export declare const trakBoxContainsVideo: (trakBox: AnySegment) => boolean;
7
+ export declare const getTimescaleAndDuration: (boxes: AnySegment[]) => TimescaleAndDuration | null;
2
8
  export declare const getFps: (segments: AnySegment[]) => number | null;
3
9
  export declare const hasFps: (boxes: AnySegment[]) => boolean;
10
+ export {};
package/dist/get-fps.js CHANGED
@@ -1,16 +1,89 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hasFps = exports.getFps = void 0;
4
- const calculateFps = (sttsBox, timeScale) => {
5
- let sum = 0;
3
+ exports.hasFps = exports.getFps = exports.getTimescaleAndDuration = exports.trakBoxContainsVideo = void 0;
4
+ const calculateFps = ({ sttsBox, timeScale, durationInSamples, }) => {
6
5
  let totalSamples = 0;
7
6
  for (const sample of sttsBox.sampleDistribution) {
8
- sum += sample.sampleCount * sample.sampleDelta;
9
7
  totalSamples += sample.sampleCount;
10
8
  }
11
- return timeScale / (sum / totalSamples);
9
+ const durationInSeconds = durationInSamples / timeScale;
10
+ const fps = totalSamples / durationInSeconds;
11
+ return fps;
12
12
  };
13
+ const trakBoxContainsVideo = (trakBox) => {
14
+ if (trakBox.type !== 'trak-box') {
15
+ return false;
16
+ }
17
+ const { children } = trakBox;
18
+ const mediaBoxes = children.filter((c) => c.type === 'regular-box' && c.boxType === 'mdia');
19
+ if (!mediaBoxes || mediaBoxes.length === 0) {
20
+ return false;
21
+ }
22
+ const firstMediaBox = mediaBoxes[0];
23
+ if (firstMediaBox.type !== 'regular-box' ||
24
+ firstMediaBox.boxType !== 'mdia') {
25
+ return false;
26
+ }
27
+ const minf = firstMediaBox.children.find((c) => c.type === 'regular-box' && c.boxType === 'minf');
28
+ if (!minf || minf.type !== 'regular-box' || minf.boxType !== 'minf') {
29
+ return false;
30
+ }
31
+ const stbl = minf.children.find((c) => c.type === 'regular-box' && c.boxType === 'stbl');
32
+ if (!stbl || stbl.type !== 'regular-box' || stbl.boxType !== 'stbl') {
33
+ return false;
34
+ }
35
+ const stsd = stbl.children.find((c) => c.type === 'stsd-box');
36
+ if (!stsd || stsd.type !== 'stsd-box') {
37
+ return false;
38
+ }
39
+ const videoSample = stsd.samples.find((s) => s.type === 'video');
40
+ if (!videoSample || videoSample.type !== 'video') {
41
+ return false;
42
+ }
43
+ return true;
44
+ };
45
+ exports.trakBoxContainsVideo = trakBoxContainsVideo;
46
+ const getTimescaleAndDuration = (boxes) => {
47
+ const moovBox = boxes.find((s) => s.type === 'moov-box');
48
+ if (!moovBox || moovBox.type !== 'moov-box') {
49
+ return null;
50
+ }
51
+ const { children } = moovBox;
52
+ const trackBoxes = children.filter((c) => c.type === 'trak-box');
53
+ if (!trackBoxes || trackBoxes.length === 0) {
54
+ return null;
55
+ }
56
+ const trackBox = trackBoxes.find(exports.trakBoxContainsVideo);
57
+ if (!trackBox || trackBox.type !== 'trak-box') {
58
+ return null;
59
+ }
60
+ const trackBoxChildren = trackBox.children;
61
+ if (!trackBoxChildren || trackBoxChildren.length === 0) {
62
+ return null;
63
+ }
64
+ const mdiaBox = trackBoxChildren.find((c) => c.type === 'regular-box' && c.boxType === 'mdia');
65
+ if (!mdiaBox ||
66
+ mdiaBox.type !== 'regular-box' ||
67
+ mdiaBox.boxType !== 'mdia') {
68
+ return null;
69
+ }
70
+ const mdhdBox = mdiaBox === null || mdiaBox === void 0 ? void 0 : mdiaBox.children.find((c) => c.type === 'mdhd-box');
71
+ if (mdhdBox && mdhdBox.type === 'mdhd-box') {
72
+ return { timescale: mdhdBox.timescale, duration: mdhdBox.duration };
73
+ }
74
+ const mvhdBox = moovBox.children.find((c) => c.type === 'mvhd-box');
75
+ if (!mvhdBox || mvhdBox.type !== 'mvhd-box') {
76
+ return null;
77
+ }
78
+ const { timeScale, durationInUnits } = mvhdBox;
79
+ return { timescale: timeScale, duration: durationInUnits };
80
+ };
81
+ exports.getTimescaleAndDuration = getTimescaleAndDuration;
13
82
  const getFps = (segments) => {
83
+ const timescaleAndDuration = (0, exports.getTimescaleAndDuration)(segments);
84
+ if (!timescaleAndDuration) {
85
+ return null;
86
+ }
14
87
  const moovBox = segments.find((s) => s.type === 'moov-box');
15
88
  if (!moovBox || moovBox.type !== 'moov-box') {
16
89
  return null;
@@ -19,14 +92,12 @@ const getFps = (segments) => {
19
92
  if (!mvhdBox || mvhdBox.type !== 'mvhd-box') {
20
93
  return null;
21
94
  }
22
- const { timeScale } = mvhdBox;
23
95
  const { children } = moovBox;
24
96
  const trackBoxes = children.filter((c) => c.type === 'trak-box');
25
97
  if (!trackBoxes || trackBoxes.length === 0) {
26
98
  return null;
27
99
  }
28
- // TODO: What if the video track is not the first track?
29
- const trackBox = trackBoxes[0];
100
+ const trackBox = trackBoxes.find(exports.trakBoxContainsVideo);
30
101
  if (!trackBox || trackBox.type !== 'trak-box') {
31
102
  return null;
32
103
  }
@@ -56,7 +127,11 @@ const getFps = (segments) => {
56
127
  if (!sttsBox || sttsBox.type !== 'stts-box') {
57
128
  return null;
58
129
  }
59
- return calculateFps(sttsBox, timeScale);
130
+ return calculateFps({
131
+ sttsBox,
132
+ timeScale: timescaleAndDuration.timescale,
133
+ durationInSamples: timescaleAndDuration.duration,
134
+ });
60
135
  };
61
136
  exports.getFps = getFps;
62
137
  const hasFps = (boxes) => {
@@ -0,0 +1,4 @@
1
+ import type { KnownVideoCodecs } from './options';
2
+ import type { AnySegment } from './parse-result';
3
+ export declare const hasVideoCodec: (boxes: AnySegment[]) => boolean;
4
+ export declare const getVideoCodec: (boxes: AnySegment[]) => KnownVideoCodecs | null;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getVideoCodec = exports.hasVideoCodec = void 0;
4
+ /* eslint-disable max-depth */
5
+ const get_fps_1 = require("./get-fps");
6
+ const hasVideoCodec = (boxes) => {
7
+ try {
8
+ return (0, exports.getVideoCodec)(boxes) !== null;
9
+ }
10
+ catch (e) {
11
+ return false;
12
+ }
13
+ };
14
+ exports.hasVideoCodec = hasVideoCodec;
15
+ const getVideoCodec = (boxes) => {
16
+ const moovBox = boxes.find((b) => b.type === 'moov-box');
17
+ if (moovBox && moovBox.type === 'moov-box') {
18
+ const trakBox = moovBox.children.find((b) => b.type === 'trak-box' && (0, get_fps_1.trakBoxContainsVideo)(b));
19
+ if (trakBox && trakBox.type === 'trak-box') {
20
+ const mdiaBox = trakBox.children.find((b) => b.type === 'regular-box' && b.boxType === 'mdia');
21
+ if (mdiaBox &&
22
+ mdiaBox.type === 'regular-box' &&
23
+ mdiaBox.boxType === 'mdia') {
24
+ const minfBox = mdiaBox === null || mdiaBox === void 0 ? void 0 : mdiaBox.children.find((b) => b.type === 'regular-box' && b.boxType === 'minf');
25
+ if (minfBox &&
26
+ minfBox.type === 'regular-box' &&
27
+ minfBox.boxType === 'minf') {
28
+ const stblBox = minfBox === null || minfBox === void 0 ? void 0 : minfBox.children.find((b) => b.type === 'regular-box' && b.boxType === 'stbl');
29
+ if (stblBox && stblBox.type === 'regular-box') {
30
+ const stsdBox = stblBox === null || stblBox === void 0 ? void 0 : stblBox.children.find((b) => b.type === 'stsd-box');
31
+ if (stsdBox && stsdBox.type === 'stsd-box') {
32
+ const videoSample = stsdBox.samples.find((s) => s.type === 'video');
33
+ if (videoSample && videoSample.type === 'video') {
34
+ if (videoSample.format === 'hvc1') {
35
+ return 'h265';
36
+ }
37
+ if (videoSample.format === 'avc1') {
38
+ return 'h264';
39
+ }
40
+ if (videoSample.format === 'ap4h') {
41
+ return 'prores';
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }
50
+ const mainSegment = boxes.find((b) => b.type === 'main-segment');
51
+ if (!mainSegment || mainSegment.type !== 'main-segment') {
52
+ return null;
53
+ }
54
+ const tracksSegment = mainSegment.children.find((b) => b.type === 'tracks-segment');
55
+ if (!tracksSegment || tracksSegment.type !== 'tracks-segment') {
56
+ return null;
57
+ }
58
+ for (const track of tracksSegment.children) {
59
+ if (track.type === 'track-entry-segment') {
60
+ const trackType = track.children.find((b) => b.type === 'codec-segment');
61
+ if (trackType && trackType.type === 'codec-segment') {
62
+ if (trackType.codec === 'V_VP8') {
63
+ return 'vp8';
64
+ }
65
+ if (trackType.codec === 'V_VP9') {
66
+ return 'vp9';
67
+ }
68
+ if (trackType.codec === 'V_AV1') {
69
+ return 'av1';
70
+ }
71
+ }
72
+ }
73
+ }
74
+ return null;
75
+ };
76
+ exports.getVideoCodec = getVideoCodec;
@@ -1,3 +1,3 @@
1
1
  import type { Options } from './options';
2
2
  import type { ParseResult } from './parse-result';
3
- export declare const hasAllInfo: (options: Options<boolean, boolean, boolean, boolean>, parseResult: ParseResult) => boolean;
3
+ export declare const hasAllInfo: (options: Options<boolean, boolean, boolean, boolean, boolean>, parseResult: ParseResult) => boolean;
@@ -4,6 +4,7 @@ exports.hasAllInfo = void 0;
4
4
  const get_dimensions_1 = require("./get-dimensions");
5
5
  const get_duration_1 = require("./get-duration");
6
6
  const get_fps_1 = require("./get-fps");
7
+ const get_video_codec_1 = require("./get-video-codec");
7
8
  const hasAllInfo = (options, parseResult) => {
8
9
  const keys = Object.entries(options)
9
10
  .filter(([, value]) => value)
@@ -21,6 +22,9 @@ const hasAllInfo = (options, parseResult) => {
21
22
  if (key === 'fps') {
22
23
  return (0, get_fps_1.hasFps)(parseResult.segments) !== null;
23
24
  }
25
+ if (key === 'videoCodec') {
26
+ return (0, get_video_codec_1.hasVideoCodec)(parseResult.segments) !== null;
27
+ }
24
28
  throw new Error(`Unknown key: ${key}`);
25
29
  });
26
30
  };
package/dist/options.d.ts CHANGED
@@ -1,13 +1,15 @@
1
1
  import type { Dimensions } from './get-dimensions';
2
2
  import type { AnySegment } from './parse-result';
3
3
  import type { ReaderInterface } from './reader';
4
- export type Options<EnableDimensions extends boolean, EnableDuration extends boolean, EnableBoxes extends boolean, EnableFps extends boolean> = {
4
+ export type KnownVideoCodecs = 'h264' | 'h265' | 'vp8' | 'vp9' | 'av1' | 'prores';
5
+ export type Options<EnableDimensions extends boolean, EnableDuration extends boolean, EnableBoxes extends boolean, EnableFps extends boolean, EnableVideoCodec extends boolean> = {
5
6
  dimensions?: EnableDimensions;
6
7
  durationInSeconds?: EnableDuration;
7
8
  boxes?: EnableBoxes;
8
9
  fps?: EnableFps;
10
+ videoCodec?: EnableVideoCodec;
9
11
  };
10
- export type Metadata<EnableDimensions extends boolean, EnableDuration extends boolean, EnableBoxes extends boolean, EnableFps extends boolean> = (EnableDimensions extends true ? {
12
+ export type Metadata<EnableDimensions extends boolean, EnableDuration extends boolean, EnableBoxes extends boolean, EnableFps extends boolean, EnableVideoCodec extends boolean> = (EnableDimensions extends true ? {
11
13
  dimensions: Dimensions;
12
14
  } : {}) & (EnableDuration extends true ? {
13
15
  durationInSeconds: number | null;
@@ -15,5 +17,7 @@ export type Metadata<EnableDimensions extends boolean, EnableDuration extends bo
15
17
  boxes: AnySegment[];
16
18
  } : {}) & (EnableFps extends true ? {
17
19
  fps: number | null;
20
+ } : {}) & (EnableVideoCodec extends true ? {
21
+ videoCodec: KnownVideoCodecs | null;
18
22
  } : {});
19
- export type ParseMedia = <EnableDimensions extends boolean, EnableDuration extends boolean, EnableBoxes extends boolean, EnableFps extends boolean>(src: string, options: Options<EnableDimensions, EnableDuration, EnableBoxes, EnableFps>, readerInterface?: ReaderInterface) => Promise<Metadata<EnableDimensions, EnableDuration, EnableBoxes, EnableFps>>;
23
+ export type ParseMedia = <EnableDimensions extends boolean, EnableDuration extends boolean, EnableBoxes extends boolean, EnableFps extends boolean, EnableVideoCodec extends boolean>(src: string, options: Options<EnableDimensions, EnableDuration, EnableBoxes, EnableFps, EnableVideoCodec>, readerInterface?: ReaderInterface) => Promise<Metadata<EnableDimensions, EnableDuration, EnableBoxes, EnableFps, EnableVideoCodec>>;
@@ -6,6 +6,7 @@ const from_web_1 = require("./from-web");
6
6
  const get_dimensions_1 = require("./get-dimensions");
7
7
  const get_duration_1 = require("./get-duration");
8
8
  const get_fps_1 = require("./get-fps");
9
+ const get_video_codec_1 = require("./get-video-codec");
9
10
  const has_all_info_1 = require("./has-all-info");
10
11
  const parse_video_1 = require("./parse-video");
11
12
  const parseMedia = async (src, options, readerInterface = from_web_1.webReader) => {
@@ -36,6 +37,9 @@ const parseMedia = async (src, options, readerInterface = from_web_1.webReader)
36
37
  if (options.fps) {
37
38
  returnValue.fps = (0, get_fps_1.getFps)(parseResult.segments);
38
39
  }
40
+ if (options.videoCodec) {
41
+ returnValue.videoCodec = (0, get_video_codec_1.getVideoCodec)(parseResult.segments);
42
+ }
39
43
  if (options.boxes) {
40
44
  returnValue.boxes = parseResult.segments;
41
45
  }
@@ -1,5 +1,6 @@
1
1
  import type { BaseBox } from './boxes/iso-base-media/base-type';
2
2
  import type { FtypBox } from './boxes/iso-base-media/ftyp';
3
+ import type { MdhdBox } from './boxes/iso-base-media/mdhd';
3
4
  import type { MoovBox } from './boxes/iso-base-media/moov/moov';
4
5
  import type { MvhdBox } from './boxes/iso-base-media/mvhd';
5
6
  import type { KeysBox } from './boxes/iso-base-media/stsd/keys';
@@ -16,7 +17,7 @@ interface RegularBox extends BaseBox {
16
17
  offset: number;
17
18
  type: 'regular-box';
18
19
  }
19
- export type IsoBaseMediaBox = RegularBox | FtypBox | MvhdBox | TkhdBox | StsdBox | MebxBox | KeysBox | MoovBox | TrakBox | SttsBox;
20
+ export type IsoBaseMediaBox = RegularBox | FtypBox | MvhdBox | TkhdBox | StsdBox | MebxBox | KeysBox | MoovBox | TrakBox | SttsBox | MdhdBox;
20
21
  export type AnySegment = MatroskaSegment | IsoBaseMediaBox;
21
22
  export type ParseResult = {
22
23
  status: 'done';
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.191",
6
+ "version": "4.0.192",
7
7
  "main": "dist/index.js",
8
8
  "sideEffects": false,
9
9
  "devDependencies": {
10
- "@remotion/renderer": "4.0.191"
10
+ "@remotion/renderer": "4.0.192"
11
11
  },
12
12
  "publishConfig": {
13
13
  "access": "public"
@@ -0,0 +1,56 @@
1
+ import type {BufferIterator} from '../../buffer-iterator';
2
+
3
+ export interface MdhdBox {
4
+ type: 'mdhd-box';
5
+ version: number;
6
+ timescale: number;
7
+ duration: number;
8
+ language: number;
9
+ quality: number;
10
+ }
11
+
12
+ export const parseMdhd = ({
13
+ data,
14
+ size,
15
+ fileOffset,
16
+ }: {
17
+ data: BufferIterator;
18
+ size: number;
19
+ fileOffset: number;
20
+ }): MdhdBox => {
21
+ const version = data.getUint8();
22
+ if (version !== 0) {
23
+ throw new Error(`Unsupported MDHD version ${version}`);
24
+ }
25
+
26
+ // flags, we discard them
27
+ data.discard(3);
28
+
29
+ // creation time
30
+ data.discard(4);
31
+
32
+ // modification time
33
+ data.discard(4);
34
+
35
+ const timescale = data.getUint32();
36
+ const duration = data.getUint32();
37
+
38
+ const language = data.getUint16();
39
+
40
+ // quality
41
+ const quality = data.getUint16();
42
+
43
+ const remaining = size - (data.counter.getOffset() - fileOffset);
44
+ if (remaining !== 0) {
45
+ throw new Error(`Expected remaining bytes to be 0, got ${remaining}`);
46
+ }
47
+
48
+ return {
49
+ type: 'mdhd-box',
50
+ duration,
51
+ timescale,
52
+ version,
53
+ language,
54
+ quality,
55
+ };
56
+ };
@@ -2,6 +2,7 @@ import type {BufferIterator} from '../../buffer-iterator';
2
2
  import type {IsoBaseMediaBox, ParseResult} from '../../parse-result';
3
3
  import type {BoxAndNext} from '../../parse-video';
4
4
  import {parseFtyp} from './ftyp';
5
+ import {parseMdhd} from './mdhd';
5
6
  import {parseMoov} from './moov/moov';
6
7
  import {parseMvhd} from './mvhd';
7
8
  import {parseMebx} from './stsd/mebx';
@@ -162,6 +163,20 @@ const processBox = ({
162
163
  };
163
164
  }
164
165
 
166
+ if (boxType === 'mdhd') {
167
+ const box = parseMdhd({
168
+ data: iterator,
169
+ size: boxSize,
170
+ fileOffset,
171
+ });
172
+
173
+ return {
174
+ type: 'complete',
175
+ box,
176
+ size: boxSize,
177
+ };
178
+ }
179
+
165
180
  const bytesRemainingInBox =
166
181
  boxSize - (iterator.counter.getOffset() - fileOffset);
167
182