@remotion/media-parser 4.0.271 → 4.0.273

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 (36) hide show
  1. package/dist/containers/flac/get-channel-count.d.ts +1 -1
  2. package/dist/containers/flac/get-metadata-from-flac.d.ts +1 -1
  3. package/dist/containers/iso-base-media/get-seeking-from-mp4.d.ts +5 -0
  4. package/dist/containers/iso-base-media/get-seeking-from-mp4.js +50 -0
  5. package/dist/containers/m3u/iterate-over-segment-files.d.ts +2 -2
  6. package/dist/containers/m3u/iterate-over-segment-files.js +32 -28
  7. package/dist/containers/m3u/parse-directive.js +14 -0
  8. package/dist/containers/m3u/run-over-m3u.js +60 -46
  9. package/dist/containers/m3u/sample-sorter.d.ts +2 -0
  10. package/dist/containers/m3u/sample-sorter.js +13 -1
  11. package/dist/containers/m3u/types.d.ts +8 -1
  12. package/dist/containers/transport-stream/get-tracks.d.ts +2 -0
  13. package/dist/containers/transport-stream/get-tracks.js +6 -2
  14. package/dist/containers/transport-stream/parse-pmt.js +5 -5
  15. package/dist/containers/transport-stream/process-stream-buffers.js +2 -1
  16. package/dist/esm/index.mjs +64 -20
  17. package/dist/esm/worker-server-entry.mjs +63 -19
  18. package/dist/esm/worker-web-entry.mjs +63 -19
  19. package/dist/fields.d.ts +63 -0
  20. package/dist/fields.js +1 -0
  21. package/dist/get-seeking-info.d.ts +5 -0
  22. package/dist/get-seeking-info.js +24 -0
  23. package/dist/index.d.ts +3 -0
  24. package/dist/seeking-info.d.ts +8 -0
  25. package/dist/seeking-info.js +1 -0
  26. package/dist/state/has-tracks-section.d.ts +2 -2
  27. package/dist/state/has-tracks-section.js +4 -2
  28. package/dist/state/m3u-state.d.ts +3 -0
  29. package/dist/state/m3u-state.js +4 -1
  30. package/dist/state/parser-state.d.ts +3 -0
  31. package/dist/state/parser-state.js +1 -0
  32. package/dist/state/sample-callbacks.d.ts +3 -2
  33. package/dist/state/sample-callbacks.js +2 -2
  34. package/dist/version.d.ts +1 -1
  35. package/dist/version.js +1 -1
  36. package/package.json +3 -3
@@ -1,2 +1,2 @@
1
1
  import type { BufferIterator } from '../../buffer-iterator';
2
- export declare const getChannelCount: (iterator: BufferIterator) => 2 | 8 | 1 | 7 | 3 | 4 | 5 | 6;
2
+ export declare const getChannelCount: (iterator: BufferIterator) => 2 | 8 | 1 | 7 | 3 | 5 | 4 | 6;
@@ -1,2 +1,2 @@
1
1
  import type { FlacStructure } from './types';
2
- export declare const getMetadataFromFlac: (structure: FlacStructure) => import("../..").MetadataEntry[] | null;
2
+ export declare const getMetadataFromFlac: (structure: FlacStructure) => import("../..").MediaParserMetadataEntry[] | null;
@@ -0,0 +1,5 @@
1
+ import type { SamplePosition } from '../../get-sample-positions';
2
+ import type { IsoBaseMediaSeekingInfo, SeekingInfo } from '../../seeking-info';
3
+ import type { ParserState } from '../../state/parser-state';
4
+ export declare const getSeekingInfoFromMp4: (state: ParserState) => SeekingInfo | null;
5
+ export declare const getSeekingByteFromIsoBaseMedia: (info: IsoBaseMediaSeekingInfo, time: number) => SamplePosition;
@@ -0,0 +1,50 @@
1
+ import { getTracksFromMoovBox } from '../../get-tracks';
2
+ import { getSamplePositionsFromTrack } from './get-sample-positions-from-track';
3
+ import { getMoofBoxes, getMoovBoxFromState } from './traversal';
4
+ export const getSeekingInfoFromMp4 = (state) => {
5
+ const structure = state.getIsoStructure();
6
+ const moovAtom = getMoovBoxFromState(state);
7
+ const moofBoxes = getMoofBoxes(structure.boxes);
8
+ if (!moovAtom) {
9
+ return null;
10
+ }
11
+ return {
12
+ type: 'iso-base-media-seeking-info',
13
+ moovBox: moovAtom,
14
+ moofBoxes,
15
+ };
16
+ };
17
+ export const getSeekingByteFromIsoBaseMedia = (info, time) => {
18
+ const tracks = getTracksFromMoovBox(info.moovBox);
19
+ const allTracks = [
20
+ ...tracks.videoTracks,
21
+ ...tracks.audioTracks,
22
+ ...tracks.otherTracks,
23
+ ];
24
+ let byte = 0;
25
+ let sam = null;
26
+ for (const t of allTracks) {
27
+ const { timescale: ts, type } = t;
28
+ if (type !== 'video') {
29
+ continue;
30
+ }
31
+ const samplePositions = getSamplePositionsFromTrack({
32
+ trakBox: t.trakBox,
33
+ moofBoxes: info.moofBoxes,
34
+ });
35
+ for (const sample of samplePositions) {
36
+ const timestamp = sample.cts / ts;
37
+ if (timestamp <= time &&
38
+ byte < sample.offset &&
39
+ type === 'video' &&
40
+ sample.isKeyframe) {
41
+ byte = sample.offset;
42
+ sam = sample;
43
+ }
44
+ }
45
+ }
46
+ if (!sam) {
47
+ throw new Error('No sample found');
48
+ }
49
+ return sam;
50
+ };
@@ -7,8 +7,8 @@ import type { OnAudioSample, OnVideoSample } from '../../webcodec-sample-types';
7
7
  import type { M3uStructure } from './types';
8
8
  export declare const iteratorOverSegmentFiles: ({ structure, onVideoTrack, m3uState, onAudioTrack, onDoneWithTracks, playlistUrl, logLevel, parentController, onInitialProgress, readerInterface, }: {
9
9
  structure: M3uStructure;
10
- onVideoTrack: (track: VideoTrack) => Promise<OnVideoSample | null>;
11
- onAudioTrack: (track: AudioTrack) => Promise<OnAudioSample | null>;
10
+ onVideoTrack: null | ((track: VideoTrack) => Promise<OnVideoSample | null>);
11
+ onAudioTrack: null | ((track: AudioTrack) => Promise<OnAudioSample | null>);
12
12
  onDoneWithTracks: () => void;
13
13
  m3uState: M3uState;
14
14
  playlistUrl: string;
@@ -52,36 +52,40 @@ export const iteratorOverSegmentFiles = async ({ structure, onVideoTrack, m3uSta
52
52
  return null;
53
53
  }
54
54
  },
55
- onAudioTrack: async ({ track }) => {
56
- const callbackOrFalse = m3uState.hasEmittedAudioTrack(playlistUrl);
57
- if (callbackOrFalse === false) {
58
- const callback = await onAudioTrack(track);
59
- if (!callback) {
60
- m3uState.setHasEmittedAudioTrack(playlistUrl, null);
61
- return null;
55
+ onAudioTrack: onAudioTrack === null
56
+ ? null
57
+ : async ({ track }) => {
58
+ const callbackOrFalse = m3uState.hasEmittedAudioTrack(playlistUrl);
59
+ if (callbackOrFalse === false) {
60
+ const callback = await onAudioTrack(track);
61
+ if (!callback) {
62
+ m3uState.setHasEmittedAudioTrack(playlistUrl, null);
63
+ return null;
64
+ }
65
+ m3uState.setHasEmittedAudioTrack(playlistUrl, callback);
66
+ return (sample) => {
67
+ return callback(sample);
68
+ };
62
69
  }
63
- m3uState.setHasEmittedAudioTrack(playlistUrl, callback);
64
- return (sample) => {
65
- return callback(sample);
66
- };
67
- }
68
- return callbackOrFalse;
69
- },
70
- onVideoTrack: async ({ track }) => {
71
- const callbackOrFalse = m3uState.hasEmittedVideoTrack(playlistUrl);
72
- if (callbackOrFalse === false) {
73
- const callback = await onVideoTrack(track);
74
- if (!callback) {
75
- m3uState.setHasEmittedVideoTrack(playlistUrl, null);
76
- return null;
70
+ return callbackOrFalse;
71
+ },
72
+ onVideoTrack: onVideoTrack === null
73
+ ? null
74
+ : async ({ track }) => {
75
+ const callbackOrFalse = m3uState.hasEmittedVideoTrack(playlistUrl);
76
+ if (callbackOrFalse === false) {
77
+ const callback = await onVideoTrack(track);
78
+ if (!callback) {
79
+ m3uState.setHasEmittedVideoTrack(playlistUrl, null);
80
+ return null;
81
+ }
82
+ m3uState.setHasEmittedVideoTrack(playlistUrl, callback);
83
+ return (sample) => {
84
+ return callback(sample);
85
+ };
77
86
  }
78
- m3uState.setHasEmittedVideoTrack(playlistUrl, callback);
79
- return (sample) => {
80
- return callback(sample);
81
- };
82
- }
83
- return callbackOrFalse;
84
- },
87
+ return callbackOrFalse;
88
+ },
85
89
  reader: readerInterface,
86
90
  mp4HeaderSegment,
87
91
  });
@@ -82,6 +82,20 @@ export const parseM3uDirective = (str) => {
82
82
  const res = parseStreamInf(value);
83
83
  return res;
84
84
  }
85
+ if (directive === '#EXT-X-I-FRAME-STREAM-INF') {
86
+ return {
87
+ type: 'm3u-i-frame-stream-info',
88
+ };
89
+ }
90
+ if (directive === '#EXT-X-ALLOW-CACHE') {
91
+ if (!value) {
92
+ throw new Error('#EXT-X-ALLOW-CACHE directive must have a value');
93
+ }
94
+ return {
95
+ type: 'm3u-allow-cache',
96
+ allowsCache: value === 'YES',
97
+ };
98
+ }
85
99
  if (directive === '#EXT-X-MAP') {
86
100
  if (!value) {
87
101
  throw new Error('#EXT-X-MAP directive must have a value');
@@ -2,6 +2,16 @@ import { Log } from '../../log';
2
2
  import { registerAudioTrack, registerVideoTrack } from '../../register-track';
3
3
  import { iteratorOverSegmentFiles } from './iterate-over-segment-files';
4
4
  export const runOverM3u = async ({ state, structure, playlistUrl, logLevel, }) => {
5
+ const tracksDone = state.m3u.getTrackDone(playlistUrl);
6
+ const hasAudioStreamToConsider = state.m3u.sampleSorter.hasAudioStreamToConsider(playlistUrl);
7
+ const hasVideoStreamToConsider = state.m3u.sampleSorter.hasVideoStreamToConsider(playlistUrl);
8
+ const audioDone = !hasAudioStreamToConsider && tracksDone;
9
+ const videoDone = !hasVideoStreamToConsider && tracksDone;
10
+ const bothDone = audioDone && videoDone;
11
+ if (bothDone) {
12
+ state.m3u.setAllChunksProcessed(playlistUrl);
13
+ return;
14
+ }
5
15
  const existingRun = state.m3u.getM3uStreamRun(playlistUrl);
6
16
  if (existingRun) {
7
17
  Log.trace(logLevel, 'Existing M3U parsing process found for', playlistUrl);
@@ -28,52 +38,56 @@ export const runOverM3u = async ({ state, structure, playlistUrl, logLevel, }) =
28
38
  state.callbacks.tracks.setIsDone(state.logLevel);
29
39
  }
30
40
  },
31
- onAudioTrack: async (track) => {
32
- const existingTracks = state.callbacks.tracks.getTracks();
33
- let { trackId } = track;
34
- while (existingTracks.find((t) => t.trackId === trackId)) {
35
- trackId++;
36
- }
37
- const onAudioSample = await registerAudioTrack({
38
- container: 'm3u8',
39
- state,
40
- track: {
41
- ...track,
42
- trackId,
43
- },
44
- });
45
- state.m3u.sampleSorter.addToStreamWithTrack(playlistUrl);
46
- if (onAudioSample === null) {
47
- return null;
48
- }
49
- state.m3u.sampleSorter.addAudioStreamToConsider(playlistUrl, onAudioSample);
50
- return async (sample) => {
51
- await state.m3u.sampleSorter.addAudioSample(playlistUrl, sample);
52
- };
53
- },
54
- onVideoTrack: async (track) => {
55
- const existingTracks = state.callbacks.tracks.getTracks();
56
- let { trackId } = track;
57
- while (existingTracks.find((t) => t.trackId === trackId)) {
58
- trackId++;
59
- }
60
- const onVideoSample = await registerVideoTrack({
61
- container: 'm3u8',
62
- state,
63
- track: {
64
- ...track,
65
- trackId,
66
- },
67
- });
68
- state.m3u.sampleSorter.addToStreamWithTrack(playlistUrl);
69
- if (onVideoSample === null) {
70
- return null;
71
- }
72
- state.m3u.sampleSorter.addVideoStreamToConsider(playlistUrl, onVideoSample);
73
- return async (sample) => {
74
- await state.m3u.sampleSorter.addVideoSample(playlistUrl, sample);
75
- };
76
- },
41
+ onAudioTrack: audioDone
42
+ ? null
43
+ : async (track) => {
44
+ const existingTracks = state.callbacks.tracks.getTracks();
45
+ let { trackId } = track;
46
+ while (existingTracks.find((t) => t.trackId === trackId)) {
47
+ trackId++;
48
+ }
49
+ const onAudioSample = await registerAudioTrack({
50
+ container: 'm3u8',
51
+ state,
52
+ track: {
53
+ ...track,
54
+ trackId,
55
+ },
56
+ });
57
+ state.m3u.sampleSorter.addToStreamWithTrack(playlistUrl);
58
+ if (onAudioSample === null) {
59
+ return null;
60
+ }
61
+ state.m3u.sampleSorter.addAudioStreamToConsider(playlistUrl, onAudioSample);
62
+ return async (sample) => {
63
+ await state.m3u.sampleSorter.addAudioSample(playlistUrl, sample);
64
+ };
65
+ },
66
+ onVideoTrack: videoDone
67
+ ? null
68
+ : async (track) => {
69
+ const existingTracks = state.callbacks.tracks.getTracks();
70
+ let { trackId } = track;
71
+ while (existingTracks.find((t) => t.trackId === trackId)) {
72
+ trackId++;
73
+ }
74
+ const onVideoSample = await registerVideoTrack({
75
+ container: 'm3u8',
76
+ state,
77
+ track: {
78
+ ...track,
79
+ trackId,
80
+ },
81
+ });
82
+ state.m3u.sampleSorter.addToStreamWithTrack(playlistUrl);
83
+ if (onVideoSample === null) {
84
+ return null;
85
+ }
86
+ state.m3u.sampleSorter.addVideoStreamToConsider(playlistUrl, onVideoSample);
87
+ return async (sample) => {
88
+ await state.m3u.sampleSorter.addVideoSample(playlistUrl, sample);
89
+ };
90
+ },
77
91
  m3uState: state.m3u,
78
92
  parentController: state.controller,
79
93
  readerInterface: state.readerInterface,
@@ -7,6 +7,8 @@ export declare const sampleSorter: ({ logLevel, getAllChunksProcessedForPlaylist
7
7
  addToStreamWithTrack: (src: string) => void;
8
8
  addVideoStreamToConsider: (src: string, callback: OnVideoSample) => void;
9
9
  addAudioStreamToConsider: (src: string, callback: OnAudioSample) => void;
10
+ hasAudioStreamToConsider: (src: string) => boolean;
11
+ hasVideoStreamToConsider: (src: string) => boolean;
10
12
  addAudioSample: (src: string, sample: AudioOrVideoSample) => Promise<void>;
11
13
  addVideoSample: (src: string, sample: AudioOrVideoSample) => Promise<void>;
12
14
  getNextStreamToRun: (streams: string[]) => string;
@@ -14,6 +14,12 @@ export const sampleSorter = ({ logLevel, getAllChunksProcessedForPlaylist, }) =>
14
14
  addAudioStreamToConsider: (src, callback) => {
15
15
  audioCallbacks[src] = callback;
16
16
  },
17
+ hasAudioStreamToConsider: (src) => {
18
+ return Boolean(audioCallbacks[src]);
19
+ },
20
+ hasVideoStreamToConsider: (src) => {
21
+ return Boolean(videoCallbacks[src]);
22
+ },
17
23
  addAudioSample: async (src, sample) => {
18
24
  const callback = audioCallbacks[src];
19
25
  if (!callback) {
@@ -25,7 +31,7 @@ export const sampleSorter = ({ logLevel, getAllChunksProcessedForPlaylist, }) =>
25
31
  addVideoSample: async (src, sample) => {
26
32
  const callback = videoCallbacks[src];
27
33
  if (!callback) {
28
- throw new Error('No callback found for audio sample');
34
+ throw new Error('No callback found for video sample.');
29
35
  }
30
36
  latestSample[src] = sample.dts;
31
37
  await callback(sample);
@@ -33,6 +39,9 @@ export const sampleSorter = ({ logLevel, getAllChunksProcessedForPlaylist, }) =>
33
39
  getNextStreamToRun: (streams) => {
34
40
  var _a, _b, _c;
35
41
  for (const stream of streams) {
42
+ if (getAllChunksProcessedForPlaylist(stream)) {
43
+ continue;
44
+ }
36
45
  // If a stream does not have a track yet, work on that
37
46
  if (!streamsWithTracks.includes(stream)) {
38
47
  Log.trace(logLevel, `Did not yet detect track of ${stream}, working on that`);
@@ -49,6 +58,9 @@ export const sampleSorter = ({ logLevel, getAllChunksProcessedForPlaylist, }) =>
49
58
  }
50
59
  }
51
60
  for (const stream of streams) {
61
+ if (getAllChunksProcessedForPlaylist(stream)) {
62
+ continue;
63
+ }
52
64
  if (((_c = latestSample[stream]) !== null && _c !== void 0 ? _c : 0) === smallestDts) {
53
65
  Log.trace(logLevel, `Working on ${stream} because it has the smallest DTS`);
54
66
  return stream;
@@ -40,6 +40,9 @@ export type M3uDiscontinuitySequence = {
40
40
  type: 'm3u-discontinuity-sequence';
41
41
  value: number;
42
42
  };
43
+ export type M3uIFrameStreamInfo = {
44
+ type: 'm3u-i-frame-stream-info';
45
+ };
43
46
  export type M3uMap = {
44
47
  type: 'm3u-map';
45
48
  value: string;
@@ -69,7 +72,11 @@ export type M3uTextValue = {
69
72
  type: 'm3u-text-value';
70
73
  value: string;
71
74
  };
72
- export type M3uBox = M3uHeader | M3uPlaylist | M3uVersion | M3uIndependentSegments | M3uStreamInfo | M3uTargetDuration | M3uPlaylistType | M3uExtInf | M3uMedia | M3uMediaInfo | M3uEndList | M3uMediaSequence | M3uDiscontinuitySequence | M3uMap | M3uTextValue;
75
+ export type M3uAllowCache = {
76
+ type: 'm3u-allow-cache';
77
+ allowsCache: boolean;
78
+ };
79
+ export type M3uBox = M3uHeader | M3uPlaylist | M3uVersion | M3uIndependentSegments | M3uStreamInfo | M3uTargetDuration | M3uPlaylistType | M3uExtInf | M3uMedia | M3uMediaInfo | M3uEndList | M3uMediaSequence | M3uDiscontinuitySequence | M3uMap | M3uIFrameStreamInfo | M3uTextValue | M3uAllowCache;
73
80
  export type M3uStructure = {
74
81
  type: 'm3u';
75
82
  boxes: M3uBox[];
@@ -1,4 +1,6 @@
1
1
  import type { ParserState } from '../../state/parser-state';
2
2
  import type { AllTracks } from '../riff/get-tracks-from-avi';
3
+ import type { TransportStreamEntry } from './parse-pmt';
4
+ export declare const filterStreamsBySupportedTypes: (streams: TransportStreamEntry[]) => TransportStreamEntry[];
3
5
  export declare const getTracksFromTransportStream: (parserState: ParserState) => AllTracks;
4
6
  export declare const hasAllTracksFromTransportStream: (parserState: ParserState) => boolean;
@@ -1,15 +1,19 @@
1
1
  import { truthy } from '../../truthy';
2
2
  import { findProgramMapTableOrThrow } from './traversal';
3
+ export const filterStreamsBySupportedTypes = (streams) => {
4
+ return streams.filter((stream) => stream.streamType === 27 || stream.streamType === 15);
5
+ };
3
6
  export const getTracksFromTransportStream = (parserState) => {
4
7
  const structure = parserState.getTsStructure();
5
8
  const programMapTable = findProgramMapTableOrThrow(structure);
6
9
  const parserTracks = parserState.callbacks.tracks.getTracks();
7
- const mapped = programMapTable.streams
10
+ const mapped = filterStreamsBySupportedTypes(programMapTable.streams)
8
11
  .map((stream) => {
9
12
  return parserTracks.find((track) => track.trackId === stream.pid);
10
13
  })
11
14
  .filter(truthy);
12
- if (mapped.length !== programMapTable.streams.length) {
15
+ if (mapped.length !==
16
+ filterStreamsBySupportedTypes(programMapTable.streams).length) {
13
17
  throw new Error('Not all tracks found');
14
18
  }
15
19
  return {
@@ -8,11 +8,12 @@ const parsePmtTable = ({ iterator, tableId, sectionLength, }) => {
8
8
  const sectionNumber = iterator.getBits(8);
9
9
  const lastSectionNumber = iterator.getBits(8);
10
10
  const tables = [];
11
+ iterator.getBits(3); // reserved
12
+ iterator.getBits(13); // PCR PID
13
+ iterator.getBits(4); // reserved
14
+ const programInfoLength = iterator.getBits(12);
15
+ iterator.getBits(programInfoLength * 8); // program descriptor
11
16
  for (let i = sectionNumber; i <= lastSectionNumber; i++) {
12
- iterator.getBits(3); // reserved
13
- iterator.getBits(13); // PCR PID
14
- iterator.getBits(4); // reserved
15
- const programInfoLength = iterator.getBits(12);
16
17
  const streams = [];
17
18
  while (true) {
18
19
  const streamType = iterator.getBits(8);
@@ -22,7 +23,6 @@ const parsePmtTable = ({ iterator, tableId, sectionLength, }) => {
22
23
  const esInfoLength = iterator.getBits(12);
23
24
  iterator.getBits(esInfoLength * 8);
24
25
  streams.push({ streamType, pid: elementaryPid });
25
- iterator.getBits(programInfoLength * 8); // program descriptor
26
26
  const remaining = sectionLength - (iterator.counter.getOffset() - start);
27
27
  if (remaining <= 4) {
28
28
  break;
@@ -1,3 +1,4 @@
1
+ import { filterStreamsBySupportedTypes } from './get-tracks';
1
2
  import { handleAacPacket } from './handle-aac-packet';
2
3
  import { handleAvcPacket } from './handle-avc-packet';
3
4
  import { findProgramMapTableOrThrow, getStreamForId } from './traversal';
@@ -27,7 +28,7 @@ export const processStreamBuffer = async ({ streamBuffer, state, programId, stru
27
28
  if (!state.callbacks.tracks.hasAllTracks()) {
28
29
  const tracksRegistered = state.callbacks.tracks.getTracks().length;
29
30
  const { streams } = findProgramMapTableOrThrow(structure);
30
- if (streams.length === tracksRegistered) {
31
+ if (filterStreamsBySupportedTypes(streams).length === tracksRegistered) {
31
32
  state.callbacks.tracks.setIsDone(state.logLevel);
32
33
  }
33
34
  }