@remotion/media-parser 4.0.267 → 4.0.269

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.
@@ -5,15 +5,16 @@ const get_frame_length_1 = require("./get-frame-length");
5
5
  const samples_per_mpeg_file_1 = require("./samples-per-mpeg-file");
6
6
  const getDurationFromMp3 = (state) => {
7
7
  const mp3Info = state.mp3Info.getMp3Info();
8
- if (!mp3Info) {
9
- throw new Error('No mp3 info');
8
+ const mp3CbrInfo = state.mp3Info.getCbrMp3Info();
9
+ if (!mp3Info || !mp3CbrInfo) {
10
+ return null;
10
11
  }
11
12
  const samplesPerFrame = (0, samples_per_mpeg_file_1.getSamplesPerMpegFrame)({
12
13
  layer: mp3Info.layer,
13
14
  mpegVersion: mp3Info.mpegVersion,
14
15
  });
15
16
  const frameLengthInBytes = (0, get_frame_length_1.getMpegFrameLength)({
16
- bitrateKbit: mp3Info.bitrateKbit,
17
+ bitrateKbit: mp3CbrInfo.bitrateKbit,
17
18
  padding: false,
18
19
  samplesPerFrame,
19
20
  samplingFrequency: mp3Info.sampleRate,
@@ -2,6 +2,7 @@
2
2
  // spec: http://www.mp3-tech.org/programmer/frame_header.html
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.parseMpegHeader = void 0;
5
+ const log_1 = require("../../log");
5
6
  const register_track_1 = require("../../register-track");
6
7
  const get_frame_length_1 = require("./get-frame-length");
7
8
  const samples_per_mpeg_file_1 = require("./samples-per-mpeg-file");
@@ -219,35 +220,45 @@ const parseMpegHeader = async ({ state, }) => {
219
220
  const offsetNow = iterator.counter.getOffset();
220
221
  iterator.counter.decrement(offsetNow - initialOffset);
221
222
  const data = iterator.getSlice(frameLength);
223
+ let isInfoTag = false;
222
224
  if (state.callbacks.tracks.getTracks().length === 0) {
223
- state.mp3Info.setMp3Info({
225
+ const info = {
224
226
  layer,
225
227
  mpegVersion,
226
228
  sampleRate,
227
- bitrateKbit,
228
229
  startOfMpegStream: initialOffset,
229
- });
230
- await (0, register_track_1.registerAudioTrack)({
231
- container: 'mp3',
232
- state,
233
- track: {
234
- type: 'audio',
235
- codec: 'mp3',
236
- codecPrivate: null,
237
- codecWithoutConfig: 'mp3',
238
- description: undefined,
239
- numberOfChannels,
240
- sampleRate,
241
- timescale: 1000000,
242
- trackId: 0,
243
- trakBox: null,
244
- },
245
- });
246
- state.callbacks.tracks.setIsDone(state.logLevel);
247
- }
248
- const mp3Info = state.mp3Info.getMp3Info();
249
- if (!mp3Info) {
250
- throw new Error('No MP3 info by now');
230
+ };
231
+ const asText = new TextDecoder().decode(data);
232
+ const isVbr = asText.includes('Xing') || asText.includes('VBRI');
233
+ isInfoTag = isVbr || asText.includes('Info');
234
+ if (isVbr) {
235
+ log_1.Log.verbose(state.logLevel, 'MP3 has variable bit rate. Requiring whole file to be read');
236
+ }
237
+ else {
238
+ state.mp3Info.setCbrMp3Info({
239
+ bitrateKbit,
240
+ });
241
+ }
242
+ if (!isInfoTag) {
243
+ state.mp3Info.setMp3Info(info);
244
+ await (0, register_track_1.registerAudioTrack)({
245
+ container: 'mp3',
246
+ state,
247
+ track: {
248
+ type: 'audio',
249
+ codec: 'mp3',
250
+ codecPrivate: null,
251
+ codecWithoutConfig: 'mp3',
252
+ description: undefined,
253
+ numberOfChannels,
254
+ sampleRate,
255
+ timescale: 1000000,
256
+ trackId: 0,
257
+ trakBox: null,
258
+ },
259
+ });
260
+ state.callbacks.tracks.setIsDone(state.logLevel);
261
+ }
251
262
  }
252
263
  const avgLength = (0, get_frame_length_1.getAverageMpegFrameLength)({
253
264
  bitrateKbit,
@@ -255,21 +266,27 @@ const parseMpegHeader = async ({ state, }) => {
255
266
  samplesPerFrame,
256
267
  samplingFrequency: sampleRate,
257
268
  });
258
- const nthFrame = Math.round((initialOffset - mp3Info.startOfMpegStream) / avgLength);
259
- const durationInSeconds = samplesPerFrame / sampleRate;
260
- const timeInSeconds = (nthFrame * samplesPerFrame) / sampleRate;
261
- const timestamp = Math.round(timeInSeconds * 1000000);
262
- const duration = Math.round(durationInSeconds * 1000000);
263
- await state.callbacks.onAudioSample(0, {
264
- data,
265
- cts: timestamp,
266
- dts: timestamp,
267
- duration,
268
- offset: initialOffset,
269
- timescale: 1000000,
270
- timestamp,
271
- trackId: 0,
272
- type: 'key',
273
- });
269
+ if (!isInfoTag) {
270
+ const mp3Info = state.mp3Info.getMp3Info();
271
+ if (!mp3Info) {
272
+ throw new Error('No MP3 info');
273
+ }
274
+ const nthFrame = Math.round((initialOffset - mp3Info.startOfMpegStream) / avgLength);
275
+ const durationInSeconds = samplesPerFrame / sampleRate;
276
+ const timeInSeconds = (nthFrame * samplesPerFrame) / sampleRate;
277
+ const timestamp = Math.round(timeInSeconds * 1000000);
278
+ const duration = Math.round(durationInSeconds * 1000000);
279
+ await state.callbacks.onAudioSample(0, {
280
+ data,
281
+ cts: timestamp,
282
+ dts: timestamp,
283
+ duration,
284
+ offset: initialOffset,
285
+ timescale: 1000000,
286
+ timestamp,
287
+ trackId: 0,
288
+ type: 'key',
289
+ });
290
+ }
274
291
  };
275
292
  exports.parseMpegHeader = parseMpegHeader;
@@ -1,3 +1,10 @@
1
1
  import type { Track } from '../../get-tracks';
2
- import type { MainSegment } from './segments/all-segments';
3
- export declare const getTracksFromMatroska: (segment: MainSegment, timescale: number) => Track[];
2
+ import type { ParserState } from '../../state/parser-state';
3
+ export type ResolvedAndUnresolvedTracks = {
4
+ resolved: Track[];
5
+ missingInfo: Track[];
6
+ };
7
+ export declare const getTracksFromMatroska: ({ state, }: {
8
+ state: ParserState;
9
+ }) => ResolvedAndUnresolvedTracks;
10
+ export declare const matroskaHasTracks: (state: ParserState) => boolean;
@@ -1,14 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getTracksFromMatroska = void 0;
3
+ exports.matroskaHasTracks = exports.getTracksFromMatroska = void 0;
4
+ const codec_string_1 = require("../avc/codec-string");
4
5
  const make_track_1 = require("./make-track");
5
6
  const traversal_1 = require("./traversal");
6
- const getTracksFromMatroska = (segment, timescale) => {
7
- const tracksSegment = (0, traversal_1.getTracksSegment)(segment);
7
+ const getTracksFromMatroska = ({ state, }) => {
8
+ const webmState = state.webm;
9
+ const structure = state.getMatroskaStructure();
10
+ const mainSegment = (0, traversal_1.getMainSegment)(structure.boxes);
11
+ if (!mainSegment) {
12
+ throw new Error('No main segment');
13
+ }
14
+ const tracksSegment = (0, traversal_1.getTracksSegment)(mainSegment);
8
15
  if (!tracksSegment) {
9
16
  throw new Error('No tracks segment');
10
17
  }
11
- const tracks = [];
18
+ const resolvedTracks = [];
19
+ const missingInfo = [];
12
20
  for (const trackEntrySegment of tracksSegment.value) {
13
21
  if (trackEntrySegment.type === 'Crc32') {
14
22
  continue;
@@ -18,12 +26,39 @@ const getTracksFromMatroska = (segment, timescale) => {
18
26
  }
19
27
  const track = (0, make_track_1.getTrack)({
20
28
  track: trackEntrySegment,
21
- timescale,
29
+ timescale: webmState.getTimescale(),
22
30
  });
23
- if (track) {
24
- tracks.push(track);
31
+ if (!track) {
32
+ continue;
33
+ }
34
+ if (track.codec === make_track_1.NO_CODEC_PRIVATE_SHOULD_BE_DERIVED_FROM_SPS) {
35
+ const avc = webmState.getAvcProfileForTrackNumber(track.trackId);
36
+ if (avc) {
37
+ resolvedTracks.push({
38
+ ...track,
39
+ codec: (0, codec_string_1.getCodecStringFromSpsAndPps)(avc),
40
+ });
41
+ }
42
+ else {
43
+ missingInfo.push(track);
44
+ }
45
+ }
46
+ else {
47
+ resolvedTracks.push(track);
25
48
  }
26
49
  }
27
- return tracks;
50
+ return { missingInfo, resolved: resolvedTracks };
28
51
  };
29
52
  exports.getTracksFromMatroska = getTracksFromMatroska;
53
+ const matroskaHasTracks = (state) => {
54
+ const structure = state.getMatroskaStructure();
55
+ const mainSegment = (0, traversal_1.getMainSegment)(structure.boxes);
56
+ if (!mainSegment) {
57
+ return false;
58
+ }
59
+ return ((0, traversal_1.getTracksSegment)(mainSegment) !== null &&
60
+ (0, exports.getTracksFromMatroska)({
61
+ state,
62
+ }).missingInfo.length === 0);
63
+ };
64
+ exports.matroskaHasTracks = matroskaHasTracks;
@@ -2,8 +2,21 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getSampleFromBlock = void 0;
4
4
  const buffer_iterator_1 = require("../../buffer-iterator");
5
+ const parse_avc_1 = require("../avc/parse-avc");
6
+ const get_ready_tracks_1 = require("./get-ready-tracks");
5
7
  const all_segments_1 = require("./segments/all-segments");
6
8
  const block_simple_block_flags_1 = require("./segments/block-simple-block-flags");
9
+ const addAvcToTrackIfNecessary = ({ partialVideoSample, codec, state, trackNumber, }) => {
10
+ if (codec === 'V_MPEG4/ISO/AVC' &&
11
+ (0, get_ready_tracks_1.getTracksFromMatroska)({ state }).missingInfo.length > 0) {
12
+ const parsed = (0, parse_avc_1.parseAvc)(partialVideoSample.data);
13
+ for (const parse of parsed) {
14
+ if (parse.type === 'avc-profile') {
15
+ state.webm.setAvcProfileForTrackNumber(trackNumber, parse);
16
+ }
17
+ }
18
+ }
19
+ };
7
20
  const getSampleFromBlock = (ebml, state, offset) => {
8
21
  const iterator = (0, buffer_iterator_1.getArrayBufferIterator)(ebml.value, ebml.value.length);
9
22
  const trackNumber = iterator.getVint();
@@ -49,6 +62,12 @@ const getSampleFromBlock = (ebml, state, offset) => {
49
62
  partialVideoSample,
50
63
  };
51
64
  }
65
+ addAvcToTrackIfNecessary({
66
+ codec,
67
+ partialVideoSample,
68
+ state,
69
+ trackNumber,
70
+ });
52
71
  const sample = {
53
72
  ...partialVideoSample,
54
73
  type: keyframe ? 'key' : 'delta',
@@ -1,5 +1,6 @@
1
1
  import type { AudioTrack, MediaParserAudioCodec, VideoTrack } from '../../get-tracks';
2
2
  import type { TrackEntry } from './segments/all-segments';
3
+ export declare const NO_CODEC_PRIVATE_SHOULD_BE_DERIVED_FROM_SPS = "no-codec-private-should-be-derived-from-sps";
3
4
  export declare const getMatroskaAudioCodecWithoutConfigString: ({ track, }: {
4
5
  track: TrackEntry;
5
6
  }) => MediaParserAudioCodec;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getTrack = exports.getMatroskaAudioCodecWithoutConfigString = void 0;
3
+ exports.getTrack = exports.getMatroskaAudioCodecWithoutConfigString = exports.NO_CODEC_PRIVATE_SHOULD_BE_DERIVED_FROM_SPS = void 0;
4
4
  const buffer_iterator_1 = require("../../buffer-iterator");
5
5
  const make_hvc1_codec_strings_1 = require("../../make-hvc1-codec-strings");
6
6
  const av1_codec_private_1 = require("./av1-codec-private");
@@ -8,6 +8,7 @@ const color_1 = require("./color");
8
8
  const description_1 = require("./description");
9
9
  const track_entry_1 = require("./segments/track-entry");
10
10
  const traversal_1 = require("./traversal");
11
+ exports.NO_CODEC_PRIVATE_SHOULD_BE_DERIVED_FROM_SPS = 'no-codec-private-should-be-derived-from-sps';
11
12
  const getDescription = (track) => {
12
13
  const codec = (0, traversal_1.getCodecSegment)(track);
13
14
  if (!codec) {
@@ -46,7 +47,7 @@ const getMatroskaVideoCodecString = ({ track, codecSegment: codec, }) => {
46
47
  if (codec.value === 'V_VP9') {
47
48
  const priv = (0, traversal_1.getPrivateData)(track);
48
49
  if (priv) {
49
- throw new Error('@remotion/media-parser cannot handle the private data for VP9. Do you have an example file you could send so we can implement it?');
50
+ throw new Error('@remotion/media-parser cannot handle the private data for VP9. Do you have an example file you could send so we can implement it? https://remotion.dev/report');
50
51
  }
51
52
  return 'vp09.00.10.08';
52
53
  }
@@ -55,7 +56,12 @@ const getMatroskaVideoCodecString = ({ track, codecSegment: codec, }) => {
55
56
  if (priv) {
56
57
  return `avc1.${priv[1].toString(16).padStart(2, '0')}${priv[2].toString(16).padStart(2, '0')}${priv[3].toString(16).padStart(2, '0')}`;
57
58
  }
58
- throw new Error('Could not find a CodecPrivate field in TrackEntry');
59
+ return exports.NO_CODEC_PRIVATE_SHOULD_BE_DERIVED_FROM_SPS;
60
+ }
61
+ if (codec.value === 'V_MPEGH/ISO/HEVC') {
62
+ const priv = (0, traversal_1.getPrivateData)(track);
63
+ const iterator = (0, buffer_iterator_1.getArrayBufferIterator)(priv, priv.length);
64
+ return 'hvc1.' + (0, make_hvc1_codec_strings_1.getHvc1CodecString)(iterator);
59
65
  }
60
66
  if (codec.value === 'V_AV1') {
61
67
  const priv = (0, traversal_1.getPrivateData)(track);
@@ -64,11 +70,6 @@ const getMatroskaVideoCodecString = ({ track, codecSegment: codec, }) => {
64
70
  }
65
71
  return (0, av1_codec_private_1.parseAv1PrivateData)(priv, null);
66
72
  }
67
- if (codec.value === 'V_MPEGH/ISO/HEVC') {
68
- const priv = (0, traversal_1.getPrivateData)(track);
69
- const iterator = (0, buffer_iterator_1.getArrayBufferIterator)(priv, priv.length);
70
- return 'hvc1.' + (0, make_hvc1_codec_strings_1.getHvc1CodecString)(iterator);
71
- }
72
73
  throw new Error(`Unknown codec: ${codec.value}`);
73
74
  };
74
75
  const getMatroskaAudioCodecWithoutConfigString = ({ track, }) => {
@@ -8,10 +8,17 @@ class MediaParserAbortError extends Error {
8
8
  }
9
9
 
10
10
  // src/readers/fetch/get-body-and-reader.ts
11
- var getLengthAndReader = async (endsWithM3u8, res, ownController) => {
12
- if (endsWithM3u8) {
13
- const text = await res.text();
14
- const encoded = new TextEncoder().encode(text);
11
+ var getLengthAndReader = async ({
12
+ canLiveWithoutContentLength,
13
+ res,
14
+ ownController,
15
+ requestedWithoutRange
16
+ }) => {
17
+ const length = res.headers.get("content-length");
18
+ const contentLength = length === null ? null : parseInt(length, 10);
19
+ if (requestedWithoutRange || canLiveWithoutContentLength && contentLength === null) {
20
+ const buffer = await res.arrayBuffer();
21
+ const encoded = new Uint8Array(buffer);
15
22
  const stream = new ReadableStream({
16
23
  start(controller) {
17
24
  controller.enqueue(encoded);
@@ -29,8 +36,6 @@ var getLengthAndReader = async (endsWithM3u8, res, ownController) => {
29
36
  needsContentRange: false
30
37
  };
31
38
  }
32
- const length = res.headers.get("content-length");
33
- const contentLength = length === null ? null : parseInt(length, 10);
34
39
  if (!res.body) {
35
40
  throw new Error("No body");
36
41
  }
@@ -74,7 +79,11 @@ function parseContentRange(input) {
74
79
  }
75
80
  return range;
76
81
  }
77
- var validateContentRangeAndDetectIfSupported = (actualRange, parsedContentRange, statusCode) => {
82
+ var validateContentRangeAndDetectIfSupported = ({
83
+ actualRange,
84
+ parsedContentRange,
85
+ statusCode
86
+ }) => {
78
87
  if (statusCode === 206) {
79
88
  return { supportsContentRange: true };
80
89
  }
@@ -102,8 +111,10 @@ var fetchReader = {
102
111
  const ownController = new AbortController;
103
112
  const cache = typeof navigator !== "undefined" && navigator.userAgent.includes("Cloudflare-Workers") ? undefined : "no-store";
104
113
  const actualRange = range === null ? 0 : range;
105
- const endsWithM3u8 = (typeof resolvedUrl === "string" ? resolvedUrl : resolvedUrl.pathname).endsWith(".m3u8");
106
- const headers = actualRange === 0 && endsWithM3u8 ? {} : typeof actualRange === "number" ? {
114
+ const asString = typeof resolvedUrl === "string" ? resolvedUrl : resolvedUrl.pathname;
115
+ const requestWithoutRange = asString.endsWith(".m3u8");
116
+ const canLiveWithoutContentLength = asString.endsWith(".m3u8") || asString.endsWith(".ts");
117
+ const headers = actualRange === 0 && requestWithoutRange ? {} : typeof actualRange === "number" ? {
107
118
  Range: `bytes=${actualRange}-`
108
119
  } : {
109
120
  Range: `bytes=${`${actualRange[0]}-${actualRange[1]}`}`
@@ -115,7 +126,11 @@ var fetchReader = {
115
126
  });
116
127
  const contentRange = res.headers.get("content-range");
117
128
  const parsedContentRange = contentRange ? parseContentRange(contentRange) : null;
118
- const { supportsContentRange } = validateContentRangeAndDetectIfSupported(actualRange, parsedContentRange, res.status);
129
+ const { supportsContentRange } = validateContentRangeAndDetectIfSupported({
130
+ actualRange,
131
+ parsedContentRange,
132
+ statusCode: res.status
133
+ });
119
134
  controller._internals.signal.addEventListener("abort", () => {
120
135
  ownController.abort(new MediaParserAbortError("Aborted by user"));
121
136
  }, { once: true });
@@ -125,7 +140,12 @@ var fetchReader = {
125
140
  const contentDisposition = res.headers.get("content-disposition");
126
141
  const name = contentDisposition?.match(/filename="([^"]+)"/)?.[1];
127
142
  const fallbackName = src.split("/").pop();
128
- const { contentLength, needsContentRange, reader } = await getLengthAndReader(endsWithM3u8, res, ownController);
143
+ const { contentLength, needsContentRange, reader } = await getLengthAndReader({
144
+ canLiveWithoutContentLength,
145
+ res,
146
+ ownController,
147
+ requestedWithoutRange: requestWithoutRange
148
+ });
129
149
  if (controller) {
130
150
  controller._internals.signal.addEventListener("abort", () => {
131
151
  reader.reader.cancel().catch(() => {