@remotion/media 4.0.356 → 4.0.358

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 (61) hide show
  1. package/dist/audio/audio-for-preview.d.ts +30 -0
  2. package/dist/audio/audio-for-preview.js +229 -0
  3. package/dist/audio/audio-for-rendering.js +35 -19
  4. package/dist/audio/audio.js +7 -49
  5. package/dist/audio/props.d.ts +8 -14
  6. package/dist/audio-extraction/audio-cache.d.ts +1 -1
  7. package/dist/audio-extraction/audio-cache.js +5 -1
  8. package/dist/audio-extraction/audio-iterator.d.ts +4 -1
  9. package/dist/audio-extraction/audio-iterator.js +22 -10
  10. package/dist/audio-extraction/audio-manager.d.ts +8 -37
  11. package/dist/audio-extraction/audio-manager.js +35 -8
  12. package/dist/audio-extraction/extract-audio.d.ts +9 -2
  13. package/dist/audio-extraction/extract-audio.js +29 -15
  14. package/dist/caches.d.ts +9 -44
  15. package/dist/convert-audiodata/combine-audiodata.js +2 -23
  16. package/dist/convert-audiodata/convert-audiodata.d.ts +1 -5
  17. package/dist/convert-audiodata/convert-audiodata.js +16 -24
  18. package/dist/esm/index.mjs +2864 -2173
  19. package/dist/extract-frame-and-audio.d.ts +6 -7
  20. package/dist/extract-frame-and-audio.js +28 -19
  21. package/dist/{get-sink-weak.d.ts → get-sink.d.ts} +1 -1
  22. package/dist/get-sink.js +15 -0
  23. package/dist/get-time-in-seconds.d.ts +11 -0
  24. package/dist/get-time-in-seconds.js +25 -0
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.js +1 -0
  27. package/dist/is-network-error.d.ts +6 -0
  28. package/dist/is-network-error.js +17 -0
  29. package/dist/render-timestamp-range.d.ts +1 -0
  30. package/dist/render-timestamp-range.js +9 -0
  31. package/dist/show-in-timeline.d.ts +8 -0
  32. package/dist/show-in-timeline.js +31 -0
  33. package/dist/use-media-in-timeline.d.ts +19 -0
  34. package/dist/use-media-in-timeline.js +103 -0
  35. package/dist/video/media-player.d.ts +34 -7
  36. package/dist/video/media-player.js +164 -63
  37. package/dist/video/props.d.ts +1 -0
  38. package/dist/video/video-for-preview.d.ts +17 -9
  39. package/dist/video/video-for-preview.js +138 -92
  40. package/dist/video/video-for-rendering.d.ts +3 -0
  41. package/dist/video/video-for-rendering.js +58 -25
  42. package/dist/video/video.js +6 -10
  43. package/dist/video-extraction/extract-frame-via-broadcast-channel.d.ts +18 -6
  44. package/dist/video-extraction/extract-frame-via-broadcast-channel.js +21 -7
  45. package/dist/video-extraction/extract-frame.d.ts +20 -2
  46. package/dist/video-extraction/extract-frame.js +41 -9
  47. package/dist/video-extraction/get-frames-since-keyframe.d.ts +5 -3
  48. package/dist/video-extraction/get-frames-since-keyframe.js +7 -4
  49. package/dist/video-extraction/keyframe-bank.d.ts +3 -2
  50. package/dist/video-extraction/keyframe-bank.js +32 -12
  51. package/dist/video-extraction/keyframe-manager.d.ts +3 -8
  52. package/dist/video-extraction/keyframe-manager.js +25 -10
  53. package/package.json +54 -54
  54. package/LICENSE.md +0 -49
  55. package/dist/convert-audiodata/apply-tonefrequency.d.ts +0 -2
  56. package/dist/convert-audiodata/apply-tonefrequency.js +0 -44
  57. package/dist/convert-audiodata/wsola.d.ts +0 -13
  58. package/dist/convert-audiodata/wsola.js +0 -197
  59. package/dist/get-sink-weak.js +0 -23
  60. package/dist/log.d.ts +0 -10
  61. package/dist/log.js +0 -33
@@ -1,28 +1,8 @@
1
1
  import type { AudioSampleSink } from 'mediabunny';
2
- import type { LogLevel } from 'remotion';
2
+ import { type LogLevel } from 'remotion';
3
3
  import type { RememberActualMatroskaTimestamps } from '../video-extraction/remember-actual-matroska-timestamps';
4
+ import type { AudioSampleIterator } from './audio-iterator';
4
5
  export declare const makeAudioManager: () => {
5
- makeIterator: ({ timeInSeconds, src, audioSampleSink, isMatroska, actualMatroskaTimestamps, logLevel, }: {
6
- timeInSeconds: number;
7
- src: string;
8
- audioSampleSink: AudioSampleSink;
9
- isMatroska: boolean;
10
- actualMatroskaTimestamps: RememberActualMatroskaTimestamps;
11
- logLevel: LogLevel;
12
- }) => {
13
- src: string;
14
- getSamples: (ts: number, dur: number) => Promise<import("mediabunny").AudioSample[]>;
15
- waitForCompletion: () => Promise<boolean>;
16
- canSatisfyRequestedTime: (timestamp: number) => boolean;
17
- logOpenFrames: () => void;
18
- getCacheStats: () => {
19
- count: number;
20
- size: number;
21
- };
22
- getLastUsed: () => number;
23
- prepareForDeletion: () => Promise<void>;
24
- startTimestamp: number;
25
- };
26
6
  getIterator: ({ src, timeInSeconds, audioSampleSink, isMatroska, actualMatroskaTimestamps, logLevel, }: {
27
7
  src: string;
28
8
  timeInSeconds: number;
@@ -30,20 +10,7 @@ export declare const makeAudioManager: () => {
30
10
  isMatroska: boolean;
31
11
  actualMatroskaTimestamps: RememberActualMatroskaTimestamps;
32
12
  logLevel: LogLevel;
33
- }) => Promise<{
34
- src: string;
35
- getSamples: (ts: number, dur: number) => Promise<import("mediabunny").AudioSample[]>;
36
- waitForCompletion: () => Promise<boolean>;
37
- canSatisfyRequestedTime: (timestamp: number) => boolean;
38
- logOpenFrames: () => void;
39
- getCacheStats: () => {
40
- count: number;
41
- size: number;
42
- };
43
- getLastUsed: () => number;
44
- prepareForDeletion: () => Promise<void>;
45
- startTimestamp: number;
46
- }>;
13
+ }) => Promise<AudioSampleIterator>;
47
14
  getCacheStats: () => {
48
15
  count: number;
49
16
  totalSize: number;
@@ -59,8 +26,12 @@ export declare const makeAudioManager: () => {
59
26
  size: number;
60
27
  };
61
28
  getLastUsed: () => number;
62
- prepareForDeletion: () => Promise<void>;
29
+ prepareForDeletion: () => void;
63
30
  startTimestamp: number;
31
+ clearBeforeThreshold: (threshold: number) => void;
32
+ getOldestTimestamp: () => number;
33
+ getNewestTimestamp: () => number | null;
64
34
  } | null;
65
35
  logOpenFrames: () => void;
36
+ deleteDuplicateIterators: (logLevel: LogLevel) => void;
66
37
  };
@@ -1,3 +1,4 @@
1
+ import { Internals } from 'remotion';
1
2
  import { getMaxVideoCacheSize, getTotalCacheStats } from '../caches';
2
3
  import { makeAudioIterator } from './audio-iterator';
3
4
  export const makeAudioManager = () => {
@@ -26,17 +27,30 @@ export const makeAudioManager = () => {
26
27
  }
27
28
  return mostInThePastIterator;
28
29
  };
29
- const deleteOldestIterator = async () => {
30
+ const deleteOldestIterator = () => {
30
31
  const iterator = getIteratorMostInThePast();
31
32
  if (iterator) {
32
- await iterator.prepareForDeletion();
33
+ iterator.prepareForDeletion();
33
34
  iterators.splice(iterators.indexOf(iterator), 1);
34
35
  }
35
36
  };
37
+ const deleteDuplicateIterators = (logLevel) => {
38
+ const seenKeys = new Set();
39
+ for (let i = 0; i < iterators.length; i++) {
40
+ const iterator = iterators[i];
41
+ const key = `${iterator.src}-${iterator.getOldestTimestamp()}-${iterator.getNewestTimestamp()}`;
42
+ if (seenKeys.has(key)) {
43
+ iterator.prepareForDeletion();
44
+ iterators.splice(iterators.indexOf(iterator), 1);
45
+ Internals.Log.verbose({ logLevel, tag: '@remotion/media' }, `Deleted duplicate iterator for ${iterator.src}`);
46
+ }
47
+ seenKeys.add(key);
48
+ }
49
+ };
36
50
  const getIterator = async ({ src, timeInSeconds, audioSampleSink, isMatroska, actualMatroskaTimestamps, logLevel, }) => {
37
51
  const maxCacheSize = getMaxVideoCacheSize(logLevel);
38
52
  while ((await getTotalCacheStats()).totalSize > maxCacheSize) {
39
- await deleteOldestIterator();
53
+ deleteOldestIterator();
40
54
  }
41
55
  for (const iterator of iterators) {
42
56
  if (iterator.src === src &&
@@ -45,13 +59,15 @@ export const makeAudioManager = () => {
45
59
  return iterator;
46
60
  }
47
61
  }
48
- for (const iterator of iterators) {
49
- // delete iterator with same starting timestamp
62
+ for (let i = 0; i < iterators.length; i++) {
63
+ const iterator = iterators[i];
64
+ // delete iterator with same starting timestamp as requested
50
65
  if (iterator.src === src && iterator.startTimestamp === timeInSeconds) {
51
- await iterator.prepareForDeletion();
66
+ iterator.prepareForDeletion();
52
67
  iterators.splice(iterators.indexOf(iterator), 1);
53
68
  }
54
69
  }
70
+ deleteDuplicateIterators(logLevel);
55
71
  return makeIterator({
56
72
  src,
57
73
  timeInSeconds,
@@ -76,11 +92,22 @@ export const makeAudioManager = () => {
76
92
  iterator.logOpenFrames();
77
93
  }
78
94
  };
95
+ let queue = Promise.resolve(undefined);
79
96
  return {
80
- makeIterator,
81
- getIterator,
97
+ getIterator: ({ src, timeInSeconds, audioSampleSink, isMatroska, actualMatroskaTimestamps, logLevel, }) => {
98
+ queue = queue.then(() => getIterator({
99
+ src,
100
+ timeInSeconds,
101
+ audioSampleSink,
102
+ isMatroska,
103
+ actualMatroskaTimestamps,
104
+ logLevel,
105
+ }));
106
+ return queue;
107
+ },
82
108
  getCacheStats,
83
109
  getIteratorMostInThePast,
84
110
  logOpenFrames,
111
+ deleteDuplicateIterators,
85
112
  };
86
113
  };
@@ -1,6 +1,7 @@
1
1
  import { type LogLevel } from 'remotion';
2
2
  import type { PcmS16AudioData } from '../convert-audiodata/convert-audiodata';
3
- export declare const extractAudio: ({ src, timeInSeconds: unloopedTimeInSeconds, durationInSeconds, logLevel, loop, playbackRate, audioStreamIndex, }: {
3
+ type ExtractAudioReturnType = Awaited<ReturnType<typeof extractAudioInternal>>;
4
+ type ExtractAudioParams = {
4
5
  src: string;
5
6
  timeInSeconds: number;
6
7
  durationInSeconds: number;
@@ -8,7 +9,13 @@ export declare const extractAudio: ({ src, timeInSeconds: unloopedTimeInSeconds,
8
9
  loop: boolean;
9
10
  playbackRate: number;
10
11
  audioStreamIndex: number;
11
- }) => Promise<{
12
+ trimBefore: number | undefined;
13
+ trimAfter: number | undefined;
14
+ fps: number;
15
+ };
16
+ declare const extractAudioInternal: ({ src, timeInSeconds: unloopedTimeInSeconds, durationInSeconds: durationNotYetApplyingPlaybackRate, logLevel, loop, playbackRate, audioStreamIndex, trimBefore, trimAfter, fps, }: ExtractAudioParams) => Promise<{
12
17
  data: PcmS16AudioData | null;
13
18
  durationInSeconds: number | null;
14
19
  } | "cannot-decode" | "unknown-container-format">;
20
+ export declare const extractAudio: (params: ExtractAudioParams) => Promise<ExtractAudioReturnType>;
21
+ export {};
@@ -1,13 +1,13 @@
1
1
  import { audioManager } from '../caches';
2
2
  import { combineAudioDataAndClosePrevious } from '../convert-audiodata/combine-audiodata';
3
3
  import { convertAudioData } from '../convert-audiodata/convert-audiodata';
4
- import { TARGET_NUMBER_OF_CHANNELS, TARGET_SAMPLE_RATE, } from '../convert-audiodata/resample-audiodata';
5
- import { getSinkWeak } from '../get-sink-weak';
6
- export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds, durationInSeconds, logLevel, loop, playbackRate, audioStreamIndex, }) => {
7
- const { getAudio, actualMatroskaTimestamps, isMatroska, getDuration } = await getSinkWeak(src, logLevel);
8
- let duration = null;
4
+ import { getSink } from '../get-sink';
5
+ import { getTimeInSeconds } from '../get-time-in-seconds';
6
+ const extractAudioInternal = async ({ src, timeInSeconds: unloopedTimeInSeconds, durationInSeconds: durationNotYetApplyingPlaybackRate, logLevel, loop, playbackRate, audioStreamIndex, trimBefore, trimAfter, fps, }) => {
7
+ const { getAudio, actualMatroskaTimestamps, isMatroska, getDuration } = await getSink(src, logLevel);
8
+ let mediaDurationInSeconds = null;
9
9
  if (loop) {
10
- duration = await getDuration();
10
+ mediaDurationInSeconds = await getDuration();
11
11
  }
12
12
  const audio = await getAudio(audioStreamIndex);
13
13
  if (audio === 'no-audio-track') {
@@ -19,9 +19,20 @@ export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds,
19
19
  if (audio === 'unknown-container-format') {
20
20
  return 'unknown-container-format';
21
21
  }
22
- const timeInSeconds = loop
23
- ? unloopedTimeInSeconds % duration
24
- : unloopedTimeInSeconds;
22
+ const timeInSeconds = getTimeInSeconds({
23
+ loop,
24
+ mediaDurationInSeconds,
25
+ unloopedTimeInSeconds,
26
+ src,
27
+ trimAfter,
28
+ playbackRate,
29
+ trimBefore,
30
+ fps,
31
+ ifNoMediaDuration: 'fail',
32
+ });
33
+ if (timeInSeconds === null) {
34
+ return { data: null, durationInSeconds: mediaDurationInSeconds };
35
+ }
25
36
  const sampleIterator = await audioManager.getIterator({
26
37
  src,
27
38
  timeInSeconds,
@@ -30,6 +41,7 @@ export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds,
30
41
  actualMatroskaTimestamps,
31
42
  logLevel,
32
43
  });
44
+ const durationInSeconds = durationNotYetApplyingPlaybackRate * playbackRate;
33
45
  const samples = await sampleIterator.getSamples(timeInSeconds, durationInSeconds);
34
46
  audioManager.logOpenFrames();
35
47
  const audioDataArray = [];
@@ -51,14 +63,13 @@ export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds,
51
63
  // amount of samples to shave from start and end
52
64
  let trimStartInSeconds = 0;
53
65
  let trimEndInSeconds = 0;
54
- // TODO: Apply tone frequency
55
66
  if (isFirstSample) {
56
67
  trimStartInSeconds = timeInSeconds - sample.timestamp;
57
68
  if (trimStartInSeconds < 0 && trimStartInSeconds > -1e-10) {
58
69
  trimStartInSeconds = 0;
59
70
  }
60
71
  if (trimStartInSeconds < 0) {
61
- throw new Error(`trimStartInSeconds is negative: ${trimStartInSeconds}`);
72
+ throw new Error(`trimStartInSeconds is negative: ${trimStartInSeconds}. ${JSON.stringify({ timeInSeconds, ts: sample.timestamp, d: sample.duration, isFirstSample, isLastSample, durationInSeconds, i, st: samples.map((s) => s.timestamp) })}`);
62
73
  }
63
74
  }
64
75
  if (isLastSample) {
@@ -70,10 +81,8 @@ export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds,
70
81
  }
71
82
  const audioData = convertAudioData({
72
83
  audioData: audioDataRaw,
73
- newSampleRate: TARGET_SAMPLE_RATE,
74
84
  trimStartInSeconds,
75
85
  trimEndInSeconds,
76
- targetNumberOfChannels: TARGET_NUMBER_OF_CHANNELS,
77
86
  playbackRate,
78
87
  });
79
88
  audioDataRaw.close();
@@ -83,8 +92,13 @@ export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds,
83
92
  audioDataArray.push(audioData);
84
93
  }
85
94
  if (audioDataArray.length === 0) {
86
- return { data: null, durationInSeconds: duration };
95
+ return { data: null, durationInSeconds: mediaDurationInSeconds };
87
96
  }
88
97
  const combined = combineAudioDataAndClosePrevious(audioDataArray);
89
- return { data: combined, durationInSeconds: duration };
98
+ return { data: combined, durationInSeconds: mediaDurationInSeconds };
99
+ };
100
+ let queue = Promise.resolve(undefined);
101
+ export const extractAudio = (params) => {
102
+ queue = queue.then(() => extractAudioInternal(params));
103
+ return queue;
90
104
  };
package/dist/caches.d.ts CHANGED
@@ -2,45 +2,19 @@ import { type LogLevel } from 'remotion';
2
2
  export declare const SAFE_BACK_WINDOW_IN_SECONDS = 1;
3
3
  export declare const keyframeManager: {
4
4
  requestKeyframeBank: ({ packetSink, timestamp, videoSampleSink, src, logLevel, }: {
5
- timestamp: number;
6
5
  packetSink: import("mediabunny").EncodedPacketSink;
6
+ timestamp: number;
7
7
  videoSampleSink: import("mediabunny").VideoSampleSink;
8
8
  src: string;
9
9
  logLevel: LogLevel;
10
- }) => Promise<import("./video-extraction/keyframe-bank").KeyframeBank>;
11
- addKeyframeBank: ({ src, bank, startTimestampInSeconds, }: {
12
- src: string;
13
- bank: Promise<import("./video-extraction/keyframe-bank").KeyframeBank>;
14
- startTimestampInSeconds: number;
15
- }) => void;
10
+ }) => Promise<import("./video-extraction/keyframe-bank").KeyframeBank | null>;
16
11
  getCacheStats: () => Promise<{
17
12
  count: number;
18
13
  totalSize: number;
19
14
  }>;
20
- clearAll: () => Promise<void>;
15
+ clearAll: (logLevel: LogLevel) => Promise<void>;
21
16
  };
22
17
  export declare const audioManager: {
23
- makeIterator: ({ timeInSeconds, src, audioSampleSink, isMatroska, actualMatroskaTimestamps, logLevel, }: {
24
- timeInSeconds: number;
25
- src: string;
26
- audioSampleSink: import("mediabunny").AudioSampleSink;
27
- isMatroska: boolean;
28
- actualMatroskaTimestamps: import("./video-extraction/remember-actual-matroska-timestamps").RememberActualMatroskaTimestamps;
29
- logLevel: LogLevel;
30
- }) => {
31
- src: string;
32
- getSamples: (ts: number, dur: number) => Promise<import("mediabunny").AudioSample[]>;
33
- waitForCompletion: () => Promise<boolean>;
34
- canSatisfyRequestedTime: (timestamp: number) => boolean;
35
- logOpenFrames: () => void;
36
- getCacheStats: () => {
37
- count: number;
38
- size: number;
39
- };
40
- getLastUsed: () => number;
41
- prepareForDeletion: () => Promise<void>;
42
- startTimestamp: number;
43
- };
44
18
  getIterator: ({ src, timeInSeconds, audioSampleSink, isMatroska, actualMatroskaTimestamps, logLevel, }: {
45
19
  src: string;
46
20
  timeInSeconds: number;
@@ -48,20 +22,7 @@ export declare const audioManager: {
48
22
  isMatroska: boolean;
49
23
  actualMatroskaTimestamps: import("./video-extraction/remember-actual-matroska-timestamps").RememberActualMatroskaTimestamps;
50
24
  logLevel: LogLevel;
51
- }) => Promise<{
52
- src: string;
53
- getSamples: (ts: number, dur: number) => Promise<import("mediabunny").AudioSample[]>;
54
- waitForCompletion: () => Promise<boolean>;
55
- canSatisfyRequestedTime: (timestamp: number) => boolean;
56
- logOpenFrames: () => void;
57
- getCacheStats: () => {
58
- count: number;
59
- size: number;
60
- };
61
- getLastUsed: () => number;
62
- prepareForDeletion: () => Promise<void>;
63
- startTimestamp: number;
64
- }>;
25
+ }) => Promise<import("./audio-extraction/audio-iterator").AudioSampleIterator>;
65
26
  getCacheStats: () => {
66
27
  count: number;
67
28
  totalSize: number;
@@ -77,10 +38,14 @@ export declare const audioManager: {
77
38
  size: number;
78
39
  };
79
40
  getLastUsed: () => number;
80
- prepareForDeletion: () => Promise<void>;
41
+ prepareForDeletion: () => void;
81
42
  startTimestamp: number;
43
+ clearBeforeThreshold: (threshold: number) => void;
44
+ getOldestTimestamp: () => number;
45
+ getNewestTimestamp: () => number | null;
82
46
  } | null;
83
47
  logOpenFrames: () => void;
48
+ deleteDuplicateIterators: (logLevel: LogLevel) => void;
84
49
  };
85
50
  export declare const getTotalCacheStats: () => Promise<{
86
51
  count: number;
@@ -1,30 +1,11 @@
1
+ import { TARGET_NUMBER_OF_CHANNELS } from './resample-audiodata';
1
2
  export const combineAudioDataAndClosePrevious = (audioDataArray) => {
2
3
  let numberOfFrames = 0;
3
- let numberOfChannels = null;
4
- let sampleRate = null;
5
4
  const { timestamp } = audioDataArray[0];
6
5
  for (const audioData of audioDataArray) {
7
6
  numberOfFrames += audioData.numberOfFrames;
8
- if (!numberOfChannels) {
9
- numberOfChannels = audioData.numberOfChannels;
10
- }
11
- else if (numberOfChannels !== audioData.numberOfChannels) {
12
- throw new Error('Number of channels do not match');
13
- }
14
- if (!sampleRate) {
15
- sampleRate = audioData.sampleRate;
16
- }
17
- else if (sampleRate !== audioData.sampleRate) {
18
- throw new Error('Sample rates do not match');
19
- }
20
7
  }
21
- if (!numberOfChannels) {
22
- throw new Error('Number of channels is not set');
23
- }
24
- if (!sampleRate) {
25
- throw new Error('Sample rate is not set');
26
- }
27
- const arr = new Int16Array(numberOfFrames * numberOfChannels);
8
+ const arr = new Int16Array(numberOfFrames * TARGET_NUMBER_OF_CHANNELS);
28
9
  let offset = 0;
29
10
  for (const audioData of audioDataArray) {
30
11
  arr.set(audioData.data, offset);
@@ -32,9 +13,7 @@ export const combineAudioDataAndClosePrevious = (audioDataArray) => {
32
13
  }
33
14
  return {
34
15
  data: arr,
35
- numberOfChannels,
36
16
  numberOfFrames,
37
- sampleRate,
38
17
  timestamp,
39
18
  };
40
19
  };
@@ -1,16 +1,12 @@
1
1
  export type ConvertAudioDataOptions = {
2
2
  audioData: AudioData;
3
- newSampleRate: number;
4
3
  trimStartInSeconds: number;
5
4
  trimEndInSeconds: number;
6
- targetNumberOfChannels: number;
7
5
  playbackRate: number;
8
6
  };
9
7
  export type PcmS16AudioData = {
10
8
  data: Int16Array;
11
- sampleRate: number;
12
- numberOfChannels: number;
13
9
  numberOfFrames: number;
14
10
  timestamp: number;
15
11
  };
16
- export declare const convertAudioData: ({ audioData, newSampleRate, trimStartInSeconds, trimEndInSeconds, targetNumberOfChannels, playbackRate, }: ConvertAudioDataOptions) => PcmS16AudioData;
12
+ export declare const convertAudioData: ({ audioData, trimStartInSeconds, trimEndInSeconds, playbackRate, }: ConvertAudioDataOptions) => PcmS16AudioData;
@@ -1,25 +1,22 @@
1
- import { resampleAudioData } from './resample-audiodata';
1
+ import { resampleAudioData, TARGET_NUMBER_OF_CHANNELS, TARGET_SAMPLE_RATE, } from './resample-audiodata';
2
2
  const FORMAT = 's16';
3
- const roundButRoundDownZeroPointFive = (value) => {
4
- if (value % 1 <= 0.5) {
5
- return Math.floor(value);
6
- }
7
- return Math.ceil(value);
8
- };
9
- export const convertAudioData = ({ audioData, newSampleRate, trimStartInSeconds, trimEndInSeconds, targetNumberOfChannels, playbackRate, }) => {
3
+ export const convertAudioData = ({ audioData, trimStartInSeconds, trimEndInSeconds, playbackRate, }) => {
10
4
  const { numberOfChannels: srcNumberOfChannels, sampleRate: currentSampleRate, numberOfFrames, } = audioData;
11
- const ratio = currentSampleRate / newSampleRate;
12
- const frameOffset = roundButRoundDownZeroPointFive(trimStartInSeconds * audioData.sampleRate);
5
+ const ratio = currentSampleRate / TARGET_SAMPLE_RATE;
6
+ // Always rounding down start timestamps and rounding up end durations
7
+ // to ensure there are no gaps when the samples don't align
8
+ // In @remotion/renderer inline audio mixing, we also round down the sample start
9
+ // timestamp and round up the end timestamp
10
+ // This might lead to overlapping, hopefully aligning perfectly!
11
+ // Test case: https://github.com/remotion-dev/remotion/issues/5758
12
+ const frameOffset = Math.floor(trimStartInSeconds * audioData.sampleRate);
13
13
  const unroundedFrameCount = numberOfFrames -
14
14
  (trimEndInSeconds + trimStartInSeconds) * audioData.sampleRate;
15
- const frameCount = Math.round(unroundedFrameCount);
16
- const newNumberOfFrames = Math.round(unroundedFrameCount / ratio / playbackRate);
15
+ const frameCount = Math.ceil(unroundedFrameCount);
16
+ const newNumberOfFrames = Math.ceil(unroundedFrameCount / ratio / playbackRate);
17
17
  if (newNumberOfFrames === 0) {
18
18
  throw new Error('Cannot resample - the given sample rate would result in less than 1 sample');
19
19
  }
20
- if (newSampleRate < 3000 || newSampleRate > 768000) {
21
- throw new Error('newSampleRate must be between 3000 and 768000');
22
- }
23
20
  const srcChannels = new Int16Array(srcNumberOfChannels * frameCount);
24
21
  audioData.copyTo(srcChannels, {
25
22
  planeIndex: 0,
@@ -27,17 +24,15 @@ export const convertAudioData = ({ audioData, newSampleRate, trimStartInSeconds,
27
24
  frameOffset,
28
25
  frameCount,
29
26
  });
30
- const data = new Int16Array(newNumberOfFrames * targetNumberOfChannels);
27
+ const data = new Int16Array(newNumberOfFrames * TARGET_NUMBER_OF_CHANNELS);
31
28
  const chunkSize = frameCount / newNumberOfFrames;
32
29
  if (newNumberOfFrames === frameCount &&
33
- targetNumberOfChannels === srcNumberOfChannels &&
30
+ TARGET_NUMBER_OF_CHANNELS === srcNumberOfChannels &&
34
31
  playbackRate === 1) {
35
32
  return {
36
33
  data: srcChannels,
37
- numberOfChannels: targetNumberOfChannels,
38
34
  numberOfFrames: newNumberOfFrames,
39
- sampleRate: newSampleRate,
40
- timestamp: audioData.timestamp + trimStartInSeconds * 1000000,
35
+ timestamp: audioData.timestamp + (frameOffset / audioData.sampleRate) * 1000000,
41
36
  };
42
37
  }
43
38
  resampleAudioData({
@@ -49,11 +44,8 @@ export const convertAudioData = ({ audioData, newSampleRate, trimStartInSeconds,
49
44
  });
50
45
  const newAudioData = {
51
46
  data,
52
- format: FORMAT,
53
- numberOfChannels: targetNumberOfChannels,
54
47
  numberOfFrames: newNumberOfFrames,
55
- sampleRate: newSampleRate,
56
- timestamp: audioData.timestamp + trimStartInSeconds * 1000000,
48
+ timestamp: audioData.timestamp + (frameOffset / audioData.sampleRate) * 1000000,
57
49
  };
58
50
  return newAudioData;
59
51
  };