@remotion/media-utils 4.0.393 → 4.0.395
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/get-partial-audio-data.d.ts +3 -4
- package/dist/get-partial-audio-data.js +108 -35
- package/dist/get-partial-wave-data.d.ts +12 -0
- package/dist/get-partial-wave-data.js +40 -0
- package/dist/probe-wave-file.d.ts +12 -0
- package/dist/probe-wave-file.js +95 -0
- package/dist/use-windowed-audio-data.js +107 -50
- package/package.json +6 -6
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import type { InputAudioTrack } from 'mediabunny';
|
|
2
1
|
export type GetPartialAudioDataProps = {
|
|
3
|
-
track: InputAudioTrack;
|
|
4
2
|
fromSeconds: number;
|
|
5
3
|
toSeconds: number;
|
|
6
4
|
channelIndex: number;
|
|
7
5
|
signal: AbortSignal;
|
|
8
|
-
|
|
6
|
+
src: string;
|
|
7
|
+
isMatroska: boolean;
|
|
9
8
|
};
|
|
10
|
-
export declare const getPartialAudioData: ({
|
|
9
|
+
export declare const getPartialAudioData: ({ fromSeconds, toSeconds, channelIndex, signal, src, isMatroska, }: GetPartialAudioDataProps) => Promise<Float32Array>;
|
|
@@ -1,4 +1,56 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
3
|
+
if (value !== null && value !== void 0) {
|
|
4
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
5
|
+
var dispose, inner;
|
|
6
|
+
if (async) {
|
|
7
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
8
|
+
dispose = value[Symbol.asyncDispose];
|
|
9
|
+
}
|
|
10
|
+
if (dispose === void 0) {
|
|
11
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
12
|
+
dispose = value[Symbol.dispose];
|
|
13
|
+
if (async) inner = dispose;
|
|
14
|
+
}
|
|
15
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
16
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
17
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
18
|
+
}
|
|
19
|
+
else if (async) {
|
|
20
|
+
env.stack.push({ async: true });
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
};
|
|
24
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
25
|
+
return function (env) {
|
|
26
|
+
function fail(e) {
|
|
27
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
28
|
+
env.hasError = true;
|
|
29
|
+
}
|
|
30
|
+
var r, s = 0;
|
|
31
|
+
function next() {
|
|
32
|
+
while (r = env.stack.pop()) {
|
|
33
|
+
try {
|
|
34
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
35
|
+
if (r.dispose) {
|
|
36
|
+
var result = r.dispose.call(r.value);
|
|
37
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
38
|
+
}
|
|
39
|
+
else s |= 1;
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
fail(e);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
46
|
+
if (env.hasError) throw env.error;
|
|
47
|
+
}
|
|
48
|
+
return next();
|
|
49
|
+
};
|
|
50
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
51
|
+
var e = new Error(message);
|
|
52
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
53
|
+
});
|
|
2
54
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
55
|
exports.getPartialAudioData = void 0;
|
|
4
56
|
const mediabunny_1 = require("mediabunny");
|
|
@@ -6,43 +58,64 @@ const mediabunny_1 = require("mediabunny");
|
|
|
6
58
|
// The worst case seems to be FLAC files with a 65'535 sample window, which would be 1486.0ms at 44.1Khz.
|
|
7
59
|
// So let's set a threshold of 1.5 seconds.
|
|
8
60
|
const EXTRA_THRESHOLD_IN_SECONDS = 1.5;
|
|
9
|
-
const getPartialAudioData = async ({
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
const audioSamples = [];
|
|
14
|
-
// matroska must be decoded from the start due to limitation
|
|
15
|
-
// https://www.remotion.dev/docs/media/support#matroska-limitation
|
|
16
|
-
// Also request extra data beforehand to handle audio frame dependencies
|
|
17
|
-
const actualFromSeconds = isMatroska
|
|
18
|
-
? 0
|
|
19
|
-
: Math.max(0, fromSeconds - EXTRA_THRESHOLD_IN_SECONDS);
|
|
20
|
-
// mediabunny docs: constructing the sink is virtually free and does not perform any media data reads.
|
|
21
|
-
const sink = new mediabunny_1.AudioBufferSink(track);
|
|
22
|
-
for await (const { buffer, timestamp, duration } of sink.buffers(actualFromSeconds, toSeconds)) {
|
|
61
|
+
const getPartialAudioData = async ({ fromSeconds, toSeconds, channelIndex, signal, src, isMatroska, }) => {
|
|
62
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
63
|
+
try {
|
|
23
64
|
if (signal.aborted) {
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
65
|
+
throw new Error('Operation was aborted');
|
|
66
|
+
}
|
|
67
|
+
const audioSamples = [];
|
|
68
|
+
// matroska must be decoded from the start due to limitation
|
|
69
|
+
// https://www.remotion.dev/docs/media/support#matroska-limitation
|
|
70
|
+
// Also request extra data beforehand to handle audio frame dependencies
|
|
71
|
+
const actualFromSeconds = isMatroska
|
|
72
|
+
? 0
|
|
73
|
+
: Math.max(0, fromSeconds - EXTRA_THRESHOLD_IN_SECONDS);
|
|
74
|
+
const source = new mediabunny_1.UrlSource(src);
|
|
75
|
+
const input = __addDisposableResource(env_1, new mediabunny_1.Input({
|
|
76
|
+
formats: mediabunny_1.ALL_FORMATS,
|
|
77
|
+
source,
|
|
78
|
+
}), false);
|
|
79
|
+
const track = await input.getPrimaryAudioTrack();
|
|
80
|
+
if (!track) {
|
|
81
|
+
throw new Error('No audio track found');
|
|
82
|
+
}
|
|
83
|
+
// mediabunny docs: constructing the sink is virtually free and does not perform any media data reads.
|
|
84
|
+
const sink = new mediabunny_1.AudioBufferSink(track);
|
|
85
|
+
const iterator = sink.buffers(actualFromSeconds, toSeconds);
|
|
86
|
+
for await (const { buffer, timestamp, duration } of iterator) {
|
|
87
|
+
if (signal.aborted) {
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
const channelData = buffer.getChannelData(channelIndex);
|
|
91
|
+
const bufferStartSeconds = timestamp;
|
|
92
|
+
const bufferEndSeconds = timestamp + duration;
|
|
93
|
+
const overlapStartSecond = Math.max(bufferStartSeconds, fromSeconds);
|
|
94
|
+
const overlapEndSecond = Math.min(bufferEndSeconds, toSeconds);
|
|
95
|
+
if (overlapStartSecond >= overlapEndSecond) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const startSampleInBuffer = Math.floor((overlapStartSecond - bufferStartSeconds) * buffer.sampleRate);
|
|
99
|
+
const endSampleInBuffer = Math.ceil((overlapEndSecond - bufferStartSeconds) * buffer.sampleRate);
|
|
100
|
+
const trimmedData = channelData.slice(startSampleInBuffer, endSampleInBuffer);
|
|
101
|
+
audioSamples.push(trimmedData);
|
|
102
|
+
}
|
|
103
|
+
await iterator.return();
|
|
104
|
+
const totalSamples = audioSamples.reduce((sum, sample) => sum + sample.length, 0);
|
|
105
|
+
const result = new Float32Array(totalSamples);
|
|
106
|
+
let offset = 0;
|
|
107
|
+
for (const audioSample of audioSamples) {
|
|
108
|
+
result.set(audioSample, offset);
|
|
109
|
+
offset += audioSample.length;
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
catch (e_1) {
|
|
114
|
+
env_1.error = e_1;
|
|
115
|
+
env_1.hasError = true;
|
|
38
116
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
let offset = 0;
|
|
42
|
-
for (const audioSample of audioSamples) {
|
|
43
|
-
result.set(audioSample, offset);
|
|
44
|
-
offset += audioSample.length;
|
|
117
|
+
finally {
|
|
118
|
+
__disposeResources(env_1);
|
|
45
119
|
}
|
|
46
|
-
return result;
|
|
47
120
|
};
|
|
48
121
|
exports.getPartialAudioData = getPartialAudioData;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const getPartialWaveData: ({ dataOffset, src, bitsPerSample, channelIndex, sampleRate, fromSeconds, toSeconds, blockAlign, fileSize, signal, }: {
|
|
2
|
+
dataOffset: number;
|
|
3
|
+
src: string;
|
|
4
|
+
bitsPerSample: number;
|
|
5
|
+
channelIndex: number;
|
|
6
|
+
sampleRate: number;
|
|
7
|
+
fromSeconds: number;
|
|
8
|
+
toSeconds: number;
|
|
9
|
+
blockAlign: number;
|
|
10
|
+
fileSize: number;
|
|
11
|
+
signal: AbortSignal;
|
|
12
|
+
}) => Promise<Float32Array<ArrayBuffer>>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getPartialWaveData = void 0;
|
|
4
|
+
const fetch_with_cors_catch_1 = require("./fetch-with-cors-catch");
|
|
5
|
+
const probe_wave_file_1 = require("./probe-wave-file");
|
|
6
|
+
const getPartialWaveData = async ({ dataOffset, src, bitsPerSample, channelIndex, sampleRate, fromSeconds, toSeconds, blockAlign, fileSize, signal, }) => {
|
|
7
|
+
const startByte = dataOffset + Math.floor(fromSeconds * sampleRate) * blockAlign;
|
|
8
|
+
const endByte = Math.min(fileSize, (dataOffset + Math.floor(toSeconds * sampleRate)) * blockAlign) - 1;
|
|
9
|
+
const response = await (0, fetch_with_cors_catch_1.fetchWithCorsCatch)(src, {
|
|
10
|
+
headers: {
|
|
11
|
+
range: `bytes=${startByte}-${endByte}`,
|
|
12
|
+
},
|
|
13
|
+
signal,
|
|
14
|
+
});
|
|
15
|
+
if (response.status === 416) {
|
|
16
|
+
throw new Error(`Tried to read bytes ${startByte}-${endByte} from ${src}, but the response status code was 416 "Range Not Satisfiable". Were too many bytes requested? The file is ${fileSize} bytes long.`);
|
|
17
|
+
}
|
|
18
|
+
if (response.status !== 206) {
|
|
19
|
+
throw new Error(`Tried to read bytes ${startByte}-${endByte} from ${src}, but the response status code was ${response.status} (expected was 206). This means the server might not support returning a partial response.`);
|
|
20
|
+
}
|
|
21
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
22
|
+
const uintArray = new Uint8Array(arrayBuffer);
|
|
23
|
+
const samples = new Float32Array(uintArray.length / blockAlign);
|
|
24
|
+
for (let i = 0; i < uintArray.length; i += blockAlign) {
|
|
25
|
+
const sampleStart = i + channelIndex * (bitsPerSample / 8);
|
|
26
|
+
let sample;
|
|
27
|
+
if (bitsPerSample === 16) {
|
|
28
|
+
sample = (0, probe_wave_file_1.getInt16AsFloat)(uintArray, sampleStart);
|
|
29
|
+
}
|
|
30
|
+
else if (bitsPerSample === 8) {
|
|
31
|
+
sample = (0, probe_wave_file_1.getInt8AsFloat)(uintArray, sampleStart);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
throw new Error(`Unsupported bits per sample: ${bitsPerSample}`);
|
|
35
|
+
}
|
|
36
|
+
samples[i / blockAlign] = sample;
|
|
37
|
+
}
|
|
38
|
+
return samples;
|
|
39
|
+
};
|
|
40
|
+
exports.getPartialWaveData = getPartialWaveData;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const getInt16AsFloat: (bytes: Uint8Array, offset: number) => number;
|
|
2
|
+
export declare const getInt8AsFloat: (bytes: Uint8Array, offset: number) => number;
|
|
3
|
+
export type WaveProbe = {
|
|
4
|
+
dataOffset: number;
|
|
5
|
+
bitsPerSample: number;
|
|
6
|
+
numberOfChannels: number;
|
|
7
|
+
sampleRate: number;
|
|
8
|
+
blockAlign: number;
|
|
9
|
+
fileSize: number;
|
|
10
|
+
durationInSeconds: number;
|
|
11
|
+
};
|
|
12
|
+
export declare const probeWaveFile: (src: string, probeSize?: number) => Promise<WaveProbe>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.probeWaveFile = exports.getInt8AsFloat = exports.getInt16AsFloat = void 0;
|
|
4
|
+
const fetch_with_cors_catch_1 = require("./fetch-with-cors-catch");
|
|
5
|
+
const getUint32 = (bytes, offset) => {
|
|
6
|
+
const val1 = bytes[offset + 3];
|
|
7
|
+
const val2 = bytes[offset + 2];
|
|
8
|
+
const val3 = bytes[offset + 1];
|
|
9
|
+
const val4 = bytes[offset];
|
|
10
|
+
return (val1 << 24) | (val2 << 16) | (val3 << 8) | val4;
|
|
11
|
+
};
|
|
12
|
+
const getUint16 = (bytes, offset) => {
|
|
13
|
+
const val1 = bytes[offset + 1];
|
|
14
|
+
const val2 = bytes[offset];
|
|
15
|
+
return (val1 << 8) | val2;
|
|
16
|
+
};
|
|
17
|
+
const getInt16AsFloat = (bytes, offset) => {
|
|
18
|
+
if (offset >= bytes.length) {
|
|
19
|
+
throw new Error(`Tried to read a 16-bit integer from offset ${offset} but the array length is ${bytes.length}`);
|
|
20
|
+
}
|
|
21
|
+
const val1 = bytes[offset + 1];
|
|
22
|
+
const val2 = bytes[offset];
|
|
23
|
+
const int16 = (val1 << 8) | val2;
|
|
24
|
+
return ((int16 << 16) >> 16) / 32768;
|
|
25
|
+
};
|
|
26
|
+
exports.getInt16AsFloat = getInt16AsFloat;
|
|
27
|
+
const getInt8AsFloat = (bytes, offset) => {
|
|
28
|
+
if (offset >= bytes.length) {
|
|
29
|
+
throw new Error(`Tried to read an 8-bit integer from offset ${offset} but the array length is ${bytes.length}`);
|
|
30
|
+
}
|
|
31
|
+
return bytes[offset] / 128;
|
|
32
|
+
};
|
|
33
|
+
exports.getInt8AsFloat = getInt8AsFloat;
|
|
34
|
+
const probeWaveFile = async (src, probeSize = 1024) => {
|
|
35
|
+
const response = await (0, fetch_with_cors_catch_1.fetchWithCorsCatch)(src, {
|
|
36
|
+
headers: {
|
|
37
|
+
range: `bytes=0-${probeSize - 1}`,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
if (response.status === 416) {
|
|
41
|
+
throw new Error(`Tried to read bytes 0-1024 from ${src}, but the response status code was 416 "Range Not Satisfiable". Is the file at least 256 bytes long?`);
|
|
42
|
+
}
|
|
43
|
+
if (response.status !== 206) {
|
|
44
|
+
throw new Error(`Tried to read bytes 0-1024 from ${src}, but the response status code was ${response.status} (expected was 206). This means the server might not support returning a partial response.`);
|
|
45
|
+
}
|
|
46
|
+
const buffer = await response.arrayBuffer();
|
|
47
|
+
const uintArray = new Uint8Array(buffer);
|
|
48
|
+
const shouldBeRiff = new TextDecoder().decode(uintArray.slice(0, 4));
|
|
49
|
+
if (shouldBeRiff !== 'RIFF') {
|
|
50
|
+
throw new Error('getPartialAudioData() requires a WAVE file, but the first bytes are not RIFF. ');
|
|
51
|
+
}
|
|
52
|
+
const size = getUint32(uintArray, 4);
|
|
53
|
+
const shouldBeWAVE = new TextDecoder().decode(uintArray.slice(8, 12));
|
|
54
|
+
if (shouldBeWAVE !== 'WAVE') {
|
|
55
|
+
throw new Error('getPartialAudioData() requires a WAVE file, but the bytes 8-11 are not "WAVE". ');
|
|
56
|
+
}
|
|
57
|
+
const shouldBeFmt = new TextDecoder().decode(uintArray.slice(12, 16));
|
|
58
|
+
if (shouldBeFmt !== 'fmt ') {
|
|
59
|
+
throw new Error('getPartialAudioData() requires a WAVE file, but the bytes 12-15 are not "fmt ". ');
|
|
60
|
+
}
|
|
61
|
+
// const chunkSize = toUint32(uintArray.slice(16, 20));
|
|
62
|
+
const audioFormat = getUint16(uintArray, 20);
|
|
63
|
+
if (audioFormat !== 1) {
|
|
64
|
+
throw new Error('getPartialAudioData() supports only a WAVE file with PCM audio format, but the audio format is not PCM. ');
|
|
65
|
+
}
|
|
66
|
+
const numberOfChannels = getUint16(uintArray, 22);
|
|
67
|
+
const sampleRate = getUint32(uintArray, 24);
|
|
68
|
+
const blockAlign = getUint16(uintArray, 32);
|
|
69
|
+
const bitsPerSample = getUint16(uintArray, 34);
|
|
70
|
+
let offset = 36;
|
|
71
|
+
const shouldBeDataOrList = new TextDecoder().decode(uintArray.slice(offset, offset + 4));
|
|
72
|
+
if (shouldBeDataOrList === 'LIST') {
|
|
73
|
+
const listSize = getUint32(uintArray, 40);
|
|
74
|
+
offset += listSize;
|
|
75
|
+
offset += 8;
|
|
76
|
+
}
|
|
77
|
+
if (offset + 4 > probeSize) {
|
|
78
|
+
return (0, exports.probeWaveFile)(src, offset + 4);
|
|
79
|
+
}
|
|
80
|
+
const shouldBeData = new TextDecoder().decode(uintArray.slice(offset, offset + 4));
|
|
81
|
+
if (shouldBeData !== 'data') {
|
|
82
|
+
throw new Error(`getPartialAudioData() requires a WAVE file, but the bytes ${offset}-${offset + 4} are not "data". `);
|
|
83
|
+
}
|
|
84
|
+
const dataSize = getUint32(uintArray, offset + 4);
|
|
85
|
+
return {
|
|
86
|
+
dataOffset: offset + 8,
|
|
87
|
+
bitsPerSample,
|
|
88
|
+
numberOfChannels,
|
|
89
|
+
sampleRate,
|
|
90
|
+
blockAlign,
|
|
91
|
+
fileSize: size,
|
|
92
|
+
durationInSeconds: dataSize / (sampleRate * blockAlign),
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
exports.probeWaveFile = probeWaveFile;
|
|
@@ -1,4 +1,56 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
3
|
+
if (value !== null && value !== void 0) {
|
|
4
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
5
|
+
var dispose, inner;
|
|
6
|
+
if (async) {
|
|
7
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
8
|
+
dispose = value[Symbol.asyncDispose];
|
|
9
|
+
}
|
|
10
|
+
if (dispose === void 0) {
|
|
11
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
12
|
+
dispose = value[Symbol.dispose];
|
|
13
|
+
if (async) inner = dispose;
|
|
14
|
+
}
|
|
15
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
16
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
17
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
18
|
+
}
|
|
19
|
+
else if (async) {
|
|
20
|
+
env.stack.push({ async: true });
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
};
|
|
24
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
25
|
+
return function (env) {
|
|
26
|
+
function fail(e) {
|
|
27
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
28
|
+
env.hasError = true;
|
|
29
|
+
}
|
|
30
|
+
var r, s = 0;
|
|
31
|
+
function next() {
|
|
32
|
+
while (r = env.stack.pop()) {
|
|
33
|
+
try {
|
|
34
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
35
|
+
if (r.dispose) {
|
|
36
|
+
var result = r.dispose.call(r.value);
|
|
37
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
38
|
+
}
|
|
39
|
+
else s |= 1;
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
fail(e);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
46
|
+
if (env.hasError) throw env.error;
|
|
47
|
+
}
|
|
48
|
+
return next();
|
|
49
|
+
};
|
|
50
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
51
|
+
var e = new Error(message);
|
|
52
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
53
|
+
});
|
|
2
54
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
55
|
exports.useWindowedAudioData = void 0;
|
|
4
56
|
const mediabunny_1 = require("mediabunny");
|
|
@@ -28,63 +80,68 @@ const useWindowedAudioData = ({ src, frame, fps, windowInSeconds, channelIndex =
|
|
|
28
80
|
});
|
|
29
81
|
requests.current = {};
|
|
30
82
|
setWaveformMap({});
|
|
31
|
-
if (audioUtils) {
|
|
32
|
-
audioUtils.input.dispose();
|
|
33
|
-
}
|
|
34
83
|
};
|
|
35
84
|
}, [audioUtils]);
|
|
36
85
|
const { delayRender, continueRender } = (0, remotion_1.useDelayRender)();
|
|
37
86
|
const fetchMetadata = (0, react_1.useCallback)(async (signal) => {
|
|
38
|
-
const
|
|
39
|
-
const cont = () => {
|
|
40
|
-
continueRender(handle);
|
|
41
|
-
};
|
|
42
|
-
signal.addEventListener('abort', cont, { once: true });
|
|
43
|
-
const input = new mediabunny_1.Input({
|
|
44
|
-
formats: mediabunny_1.ALL_FORMATS,
|
|
45
|
-
source: new mediabunny_1.UrlSource(src),
|
|
46
|
-
});
|
|
47
|
-
const onAbort = () => {
|
|
48
|
-
input.dispose();
|
|
49
|
-
};
|
|
50
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
87
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
51
88
|
try {
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
89
|
+
const handle = delayRender(`Waiting for audio metadata with src="${src}" to be loaded`);
|
|
90
|
+
const cont = () => {
|
|
91
|
+
continueRender(handle);
|
|
92
|
+
};
|
|
93
|
+
signal.addEventListener('abort', cont, { once: true });
|
|
94
|
+
const source = new mediabunny_1.UrlSource(src);
|
|
95
|
+
const input = __addDisposableResource(env_1, new mediabunny_1.Input({
|
|
96
|
+
formats: mediabunny_1.ALL_FORMATS,
|
|
97
|
+
source,
|
|
98
|
+
}), false);
|
|
99
|
+
const onAbort = () => {
|
|
100
|
+
input.dispose();
|
|
101
|
+
};
|
|
102
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
103
|
+
try {
|
|
104
|
+
const durationInSeconds = await input.computeDuration();
|
|
105
|
+
const audioTrack = await input.getPrimaryAudioTrack();
|
|
106
|
+
if (!audioTrack) {
|
|
107
|
+
throw new Error('No audio track found');
|
|
108
|
+
}
|
|
109
|
+
const canDecode = await audioTrack.canDecode();
|
|
110
|
+
if (!canDecode) {
|
|
111
|
+
throw new Error('Audio track cannot be decoded');
|
|
112
|
+
}
|
|
113
|
+
if (channelIndex >= audioTrack.numberOfChannels || channelIndex < 0) {
|
|
114
|
+
throw new Error(`Invalid channel index ${channelIndex} for audio with ${audioTrack.numberOfChannels} channels`);
|
|
115
|
+
}
|
|
116
|
+
const { numberOfChannels, sampleRate } = audioTrack;
|
|
117
|
+
const format = await input.getFormat();
|
|
118
|
+
const isMatroska = format === mediabunny_1.MATROSKA || format === mediabunny_1.WEBM;
|
|
119
|
+
if (isMounted.current) {
|
|
120
|
+
setAudioUtils({
|
|
121
|
+
metadata: {
|
|
122
|
+
durationInSeconds,
|
|
123
|
+
numberOfChannels,
|
|
124
|
+
sampleRate,
|
|
125
|
+
},
|
|
126
|
+
isMatroska,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
continueRender(handle);
|
|
60
130
|
}
|
|
61
|
-
|
|
62
|
-
|
|
131
|
+
catch (err) {
|
|
132
|
+
(0, remotion_1.cancelRender)(err);
|
|
63
133
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (isMounted.current) {
|
|
68
|
-
setAudioUtils({
|
|
69
|
-
input,
|
|
70
|
-
track: audioTrack,
|
|
71
|
-
metadata: {
|
|
72
|
-
durationInSeconds,
|
|
73
|
-
numberOfChannels,
|
|
74
|
-
sampleRate,
|
|
75
|
-
},
|
|
76
|
-
isMatroska,
|
|
77
|
-
});
|
|
134
|
+
finally {
|
|
135
|
+
signal.removeEventListener('abort', cont);
|
|
136
|
+
signal.removeEventListener('abort', onAbort);
|
|
78
137
|
}
|
|
79
|
-
continueRender(handle);
|
|
80
138
|
}
|
|
81
|
-
catch (
|
|
82
|
-
|
|
83
|
-
|
|
139
|
+
catch (e_1) {
|
|
140
|
+
env_1.error = e_1;
|
|
141
|
+
env_1.hasError = true;
|
|
84
142
|
}
|
|
85
143
|
finally {
|
|
86
|
-
|
|
87
|
-
signal.removeEventListener('abort', onAbort);
|
|
144
|
+
__disposeResources(env_1);
|
|
88
145
|
}
|
|
89
146
|
}, [src, delayRender, continueRender, channelIndex]);
|
|
90
147
|
(0, react_1.useLayoutEffect)(() => {
|
|
@@ -112,7 +169,7 @@ const useWindowedAudioData = ({ src, frame, fps, windowInSeconds, channelIndex =
|
|
|
112
169
|
]
|
|
113
170
|
.filter((i) => i !== null)
|
|
114
171
|
.filter((i) => i >= 0);
|
|
115
|
-
}, [currentWindowIndex, audioUtils
|
|
172
|
+
}, [currentWindowIndex, audioUtils, windowInSeconds]);
|
|
116
173
|
const fetchAndSetWaveformData = (0, react_1.useCallback)(async (windowIndex) => {
|
|
117
174
|
if (!(audioUtils === null || audioUtils === void 0 ? void 0 : audioUtils.metadata) || !audioUtils) {
|
|
118
175
|
throw new Error('MediaBunny context is not loaded yet');
|
|
@@ -141,7 +198,7 @@ const useWindowedAudioData = ({ src, frame, fps, windowInSeconds, channelIndex =
|
|
|
141
198
|
remotion_1.Internals.Log.warn({ logLevel: 'info', tag: '@remotion/media-utils' }, `[useWindowedAudioData] Matroska/WebM file detected at "${src}".\n\nDue to format limitation, audio decoding must start from the beginning of the file, which may lead to increased memory usage and slower performance for large files. Consider converting the audio to a more suitable format like MP3 or AAC for better performance.`);
|
|
142
199
|
}
|
|
143
200
|
const partialWaveData = await (0, get_partial_audio_data_1.getPartialAudioData)({
|
|
144
|
-
|
|
201
|
+
src,
|
|
145
202
|
fromSeconds,
|
|
146
203
|
toSeconds,
|
|
147
204
|
channelIndex,
|
|
@@ -191,7 +248,7 @@ const useWindowedAudioData = ({ src, frame, fps, windowInSeconds, channelIndex =
|
|
|
191
248
|
}
|
|
192
249
|
}
|
|
193
250
|
// Only fetch windows that don't already exist
|
|
194
|
-
const windowsToActuallyFetch = windowsToFetch.filter((windowIndex) => !waveFormMap[windowIndex]);
|
|
251
|
+
const windowsToActuallyFetch = windowsToFetch.filter((windowIndex) => !waveFormMap[windowIndex] && !requests.current[windowIndex]);
|
|
195
252
|
if (windowsToActuallyFetch.length === 0) {
|
|
196
253
|
return;
|
|
197
254
|
}
|
|
@@ -233,7 +290,7 @@ const useWindowedAudioData = ({ src, frame, fps, windowInSeconds, channelIndex =
|
|
|
233
290
|
resultId: `${src}-windows-${availableWindows.join(',')}`,
|
|
234
291
|
sampleRate: audioUtils.metadata.sampleRate,
|
|
235
292
|
};
|
|
236
|
-
}, [src, waveFormMap, audioUtils
|
|
293
|
+
}, [src, waveFormMap, audioUtils, availableWindows]);
|
|
237
294
|
const isBeyondAudioDuration = audioUtils
|
|
238
295
|
? currentTime >= audioUtils.metadata.durationInSeconds
|
|
239
296
|
: false;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"url": "https://github.com/remotion-dev/remotion/tree/main/packages/media-utils"
|
|
4
4
|
},
|
|
5
5
|
"name": "@remotion/media-utils",
|
|
6
|
-
"version": "4.0.
|
|
6
|
+
"version": "4.0.395",
|
|
7
7
|
"description": "Utilities for working with media files",
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"sideEffects": false,
|
|
@@ -18,17 +18,17 @@
|
|
|
18
18
|
"url": "https://github.com/remotion-dev/remotion/issues"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@remotion/media-parser": "4.0.
|
|
22
|
-
"@remotion/webcodecs": "4.0.
|
|
23
|
-
"remotion": "4.0.
|
|
24
|
-
"mediabunny": "1.27.
|
|
21
|
+
"@remotion/media-parser": "4.0.395",
|
|
22
|
+
"@remotion/webcodecs": "4.0.395",
|
|
23
|
+
"remotion": "4.0.395",
|
|
24
|
+
"mediabunny": "1.27.2"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"react": ">=16.8.0",
|
|
28
28
|
"react-dom": ">=16.8.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@remotion/eslint-config-internal": "4.0.
|
|
31
|
+
"@remotion/eslint-config-internal": "4.0.395",
|
|
32
32
|
"eslint": "9.19.0"
|
|
33
33
|
},
|
|
34
34
|
"keywords": [
|