@remotion/media 4.0.356 → 4.0.357
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.
- package/dist/audio/audio-for-preview.d.ts +30 -0
- package/dist/audio/audio-for-preview.js +213 -0
- package/dist/audio/audio-for-rendering.js +32 -15
- package/dist/audio/audio.js +7 -49
- package/dist/audio/props.d.ts +8 -14
- package/dist/audio-extraction/audio-cache.d.ts +1 -1
- package/dist/audio-extraction/audio-cache.js +5 -1
- package/dist/audio-extraction/audio-iterator.d.ts +4 -1
- package/dist/audio-extraction/audio-iterator.js +22 -10
- package/dist/audio-extraction/audio-manager.d.ts +8 -37
- package/dist/audio-extraction/audio-manager.js +35 -8
- package/dist/audio-extraction/extract-audio.d.ts +9 -2
- package/dist/audio-extraction/extract-audio.js +28 -15
- package/dist/caches.d.ts +9 -44
- package/dist/convert-audiodata/apply-tonefrequency.js +0 -1
- package/dist/convert-audiodata/combine-audiodata.js +2 -23
- package/dist/convert-audiodata/convert-audiodata.d.ts +1 -5
- package/dist/convert-audiodata/convert-audiodata.js +16 -24
- package/dist/convert-audiodata/wsola.js +1 -1
- package/dist/esm/index.mjs +2681 -2162
- package/dist/extract-frame-and-audio.d.ts +6 -7
- package/dist/extract-frame-and-audio.js +28 -19
- package/dist/get-sink-weak.d.ts +1 -1
- package/dist/get-sink-weak.js +3 -11
- package/dist/get-sink.d.ts +13 -0
- package/dist/get-sink.js +15 -0
- package/dist/get-time-in-seconds.d.ts +10 -0
- package/dist/get-time-in-seconds.js +25 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/is-network-error.d.ts +6 -0
- package/dist/is-network-error.js +17 -0
- package/dist/render-timestamp-range.d.ts +1 -0
- package/dist/render-timestamp-range.js +9 -0
- package/dist/video/media-player.d.ts +28 -7
- package/dist/video/media-player.js +123 -58
- package/dist/video/props.d.ts +1 -0
- package/dist/video/resolve-playback-time.d.ts +8 -0
- package/dist/video/resolve-playback-time.js +22 -0
- package/dist/video/video-for-preview.d.ts +8 -0
- package/dist/video/video-for-preview.js +113 -90
- package/dist/video/video-for-rendering.d.ts +3 -0
- package/dist/video/video-for-rendering.js +58 -25
- package/dist/video/video.js +6 -10
- package/dist/video-extraction/extract-frame-via-broadcast-channel.d.ts +18 -6
- package/dist/video-extraction/extract-frame-via-broadcast-channel.js +21 -7
- package/dist/video-extraction/extract-frame.d.ts +20 -2
- package/dist/video-extraction/extract-frame.js +40 -9
- package/dist/video-extraction/get-frames-since-keyframe.d.ts +5 -3
- package/dist/video-extraction/get-frames-since-keyframe.js +7 -4
- package/dist/video-extraction/keyframe-bank.d.ts +3 -2
- package/dist/video-extraction/keyframe-bank.js +32 -12
- package/dist/video-extraction/keyframe-manager.d.ts +3 -8
- package/dist/video-extraction/keyframe-manager.js +25 -10
- package/package.json +4 -4
|
@@ -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 =
|
|
30
|
+
const deleteOldestIterator = () => {
|
|
30
31
|
const iterator = getIteratorMostInThePast();
|
|
31
32
|
if (iterator) {
|
|
32
|
-
|
|
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
|
-
|
|
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 (
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
const { getAudio, actualMatroskaTimestamps, isMatroska, getDuration } = await
|
|
8
|
-
let
|
|
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
|
-
|
|
10
|
+
mediaDurationInSeconds = await getDuration();
|
|
11
11
|
}
|
|
12
12
|
const audio = await getAudio(audioStreamIndex);
|
|
13
13
|
if (audio === 'no-audio-track') {
|
|
@@ -19,9 +19,19 @@ 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 =
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
const timeInSeconds = getTimeInSeconds({
|
|
23
|
+
loop,
|
|
24
|
+
mediaDurationInSeconds,
|
|
25
|
+
unloopedTimeInSeconds,
|
|
26
|
+
src,
|
|
27
|
+
trimAfter,
|
|
28
|
+
playbackRate,
|
|
29
|
+
trimBefore,
|
|
30
|
+
fps,
|
|
31
|
+
});
|
|
32
|
+
if (timeInSeconds === null) {
|
|
33
|
+
return { data: null, durationInSeconds: mediaDurationInSeconds };
|
|
34
|
+
}
|
|
25
35
|
const sampleIterator = await audioManager.getIterator({
|
|
26
36
|
src,
|
|
27
37
|
timeInSeconds,
|
|
@@ -30,6 +40,7 @@ export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds,
|
|
|
30
40
|
actualMatroskaTimestamps,
|
|
31
41
|
logLevel,
|
|
32
42
|
});
|
|
43
|
+
const durationInSeconds = durationNotYetApplyingPlaybackRate * playbackRate;
|
|
33
44
|
const samples = await sampleIterator.getSamples(timeInSeconds, durationInSeconds);
|
|
34
45
|
audioManager.logOpenFrames();
|
|
35
46
|
const audioDataArray = [];
|
|
@@ -51,14 +62,13 @@ export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds,
|
|
|
51
62
|
// amount of samples to shave from start and end
|
|
52
63
|
let trimStartInSeconds = 0;
|
|
53
64
|
let trimEndInSeconds = 0;
|
|
54
|
-
// TODO: Apply tone frequency
|
|
55
65
|
if (isFirstSample) {
|
|
56
66
|
trimStartInSeconds = timeInSeconds - sample.timestamp;
|
|
57
67
|
if (trimStartInSeconds < 0 && trimStartInSeconds > -1e-10) {
|
|
58
68
|
trimStartInSeconds = 0;
|
|
59
69
|
}
|
|
60
70
|
if (trimStartInSeconds < 0) {
|
|
61
|
-
throw new Error(`trimStartInSeconds is negative: ${trimStartInSeconds}`);
|
|
71
|
+
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
72
|
}
|
|
63
73
|
}
|
|
64
74
|
if (isLastSample) {
|
|
@@ -70,10 +80,8 @@ export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds,
|
|
|
70
80
|
}
|
|
71
81
|
const audioData = convertAudioData({
|
|
72
82
|
audioData: audioDataRaw,
|
|
73
|
-
newSampleRate: TARGET_SAMPLE_RATE,
|
|
74
83
|
trimStartInSeconds,
|
|
75
84
|
trimEndInSeconds,
|
|
76
|
-
targetNumberOfChannels: TARGET_NUMBER_OF_CHANNELS,
|
|
77
85
|
playbackRate,
|
|
78
86
|
});
|
|
79
87
|
audioDataRaw.close();
|
|
@@ -83,8 +91,13 @@ export const extractAudio = async ({ src, timeInSeconds: unloopedTimeInSeconds,
|
|
|
83
91
|
audioDataArray.push(audioData);
|
|
84
92
|
}
|
|
85
93
|
if (audioDataArray.length === 0) {
|
|
86
|
-
return { data: null, durationInSeconds:
|
|
94
|
+
return { data: null, durationInSeconds: mediaDurationInSeconds };
|
|
87
95
|
}
|
|
88
96
|
const combined = combineAudioDataAndClosePrevious(audioDataArray);
|
|
89
|
-
return { data: combined, durationInSeconds:
|
|
97
|
+
return { data: combined, durationInSeconds: mediaDurationInSeconds };
|
|
98
|
+
};
|
|
99
|
+
let queue = Promise.resolve(undefined);
|
|
100
|
+
export const extractAudio = (params) => {
|
|
101
|
+
queue = queue.then(() => extractAudioInternal(params));
|
|
102
|
+
return queue;
|
|
90
103
|
};
|
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: () =>
|
|
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,6 +1,5 @@
|
|
|
1
1
|
import { FORMAT } from './convert-audiodata';
|
|
2
2
|
import { resampleAudioData, TARGET_SAMPLE_RATE } from './resample-audiodata';
|
|
3
|
-
import { wsolaInt16Interleaved } from './wsola';
|
|
4
3
|
export const applyToneFrequency = (audioData, toneFrequency) => {
|
|
5
4
|
// In FFmpeg, we apply toneFrequency as follows:
|
|
6
5
|
// `asetrate=${DEFAULT_SAMPLE_RATE}*${toneFrequency},aresample=${DEFAULT_SAMPLE_RATE},atempo=1/${toneFrequency}`
|
|
@@ -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
|
-
|
|
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,
|
|
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
|
|
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 /
|
|
12
|
-
|
|
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.
|
|
16
|
-
const newNumberOfFrames = Math.
|
|
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 *
|
|
27
|
+
const data = new Int16Array(newNumberOfFrames * TARGET_NUMBER_OF_CHANNELS);
|
|
31
28
|
const chunkSize = frameCount / newNumberOfFrames;
|
|
32
29
|
if (newNumberOfFrames === frameCount &&
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
timestamp: audioData.timestamp + trimStartInSeconds * 1000000,
|
|
48
|
+
timestamp: audioData.timestamp + (frameOffset / audioData.sampleRate) * 1000000,
|
|
57
49
|
};
|
|
58
50
|
return newAudioData;
|
|
59
51
|
};
|
|
@@ -28,7 +28,7 @@ export function wsolaInt16Interleaved(input, channels, f) {
|
|
|
28
28
|
const sampleRate = 48000;
|
|
29
29
|
const frameMs = 30; // 20–40 ms typical
|
|
30
30
|
const overlapRatio = 0.5;
|
|
31
|
-
const searchMs =
|
|
31
|
+
const searchMs = 8; // +/- 8 ms local search
|
|
32
32
|
const winKind = 'hann';
|
|
33
33
|
const headReinf = 3;
|
|
34
34
|
const tailReinf = 3;
|