Add .gitignore to exclude all node packages and lock files
This commit is contained in:
Generated
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Inlined from https://github.com/Jam3/audiobuffer-to-wav/commit/2272eb09bd46a05e50a6d684d908aa6f13c58f63#diff-e727e4bdf3657fd1d798edcd6b099d6e092f8573cba266154583a746bba0f346
|
||||
*/
|
||||
export declare function audioBufferToWav(buffer: AudioBuffer, opt: {
|
||||
float32: boolean;
|
||||
}): ArrayBuffer;
|
||||
Generated
Vendored
+94
@@ -0,0 +1,94 @@
|
||||
"use strict";
|
||||
/**
|
||||
* Inlined from https://github.com/Jam3/audiobuffer-to-wav/commit/2272eb09bd46a05e50a6d684d908aa6f13c58f63#diff-e727e4bdf3657fd1d798edcd6b099d6e092f8573cba266154583a746bba0f346
|
||||
*/
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.audioBufferToWav = audioBufferToWav;
|
||||
function interleave(inputL, inputR) {
|
||||
const length = inputL.length + inputR.length;
|
||||
const result = new Float32Array(length);
|
||||
let index = 0;
|
||||
let inputIndex = 0;
|
||||
while (index < length) {
|
||||
result[index++] = inputL[inputIndex];
|
||||
result[index++] = inputR[inputIndex];
|
||||
inputIndex++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function writeFloat32(output, offset, input) {
|
||||
for (let i = 0; i < input.length; i++, offset += 4) {
|
||||
output.setFloat32(offset, input[i], true);
|
||||
}
|
||||
}
|
||||
function floatTo16BitPCM(output, offset, input) {
|
||||
for (let i = 0; i < input.length; i++, offset += 2) {
|
||||
const s = Math.max(-1, Math.min(1, input[i]));
|
||||
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
|
||||
}
|
||||
}
|
||||
function writeString(view, offset, string) {
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
view.setUint8(offset + i, string.charCodeAt(i));
|
||||
}
|
||||
}
|
||||
function encodeWAV({ samples, format, sampleRate, numChannels, bitDepth, }) {
|
||||
const bytesPerSample = bitDepth / 8;
|
||||
const blockAlign = numChannels * bytesPerSample;
|
||||
const buffer = new ArrayBuffer(44 + samples.length * bytesPerSample);
|
||||
const view = new DataView(buffer);
|
||||
/* RIFF identifier */
|
||||
writeString(view, 0, 'RIFF');
|
||||
/* RIFF chunk length */
|
||||
view.setUint32(4, 36 + samples.length * bytesPerSample, true);
|
||||
/* RIFF type */
|
||||
writeString(view, 8, 'WAVE');
|
||||
/* format chunk identifier */
|
||||
writeString(view, 12, 'fmt ');
|
||||
/* format chunk length */
|
||||
view.setUint32(16, 16, true);
|
||||
/* sample format (raw) */
|
||||
view.setUint16(20, format, true);
|
||||
/* channel count */
|
||||
view.setUint16(22, numChannels, true);
|
||||
/* sample rate */
|
||||
view.setUint32(24, sampleRate, true);
|
||||
/* byte rate (sample rate * block align) */
|
||||
view.setUint32(28, sampleRate * blockAlign, true);
|
||||
/* block align (channel count * bytes per sample) */
|
||||
view.setUint16(32, blockAlign, true);
|
||||
/* bits per sample */
|
||||
view.setUint16(34, bitDepth, true);
|
||||
/* data chunk identifier */
|
||||
writeString(view, 36, 'data');
|
||||
/* data chunk length */
|
||||
view.setUint32(40, samples.length * bytesPerSample, true);
|
||||
if (format === 1) {
|
||||
// Raw PCM
|
||||
floatTo16BitPCM(view, 44, samples);
|
||||
}
|
||||
else {
|
||||
writeFloat32(view, 44, samples);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
function audioBufferToWav(buffer, opt) {
|
||||
const numChannels = buffer.numberOfChannels;
|
||||
const { sampleRate } = buffer;
|
||||
const format = opt.float32 ? 3 : 1;
|
||||
const bitDepth = format === 3 ? 32 : 16;
|
||||
let result;
|
||||
if (numChannels === 2) {
|
||||
result = interleave(buffer.getChannelData(0), buffer.getChannelData(1));
|
||||
}
|
||||
else {
|
||||
result = buffer.getChannelData(0);
|
||||
}
|
||||
return encodeWAV({
|
||||
samples: result,
|
||||
format,
|
||||
sampleRate,
|
||||
numChannels,
|
||||
bitDepth,
|
||||
});
|
||||
}
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const audioBufferToDataUrl: (buffer: AudioBuffer) => string;
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.audioBufferToDataUrl = void 0;
|
||||
const audio_buffer_to_wav_1 = require("./audio-buffer-to-wav");
|
||||
/*
|
||||
* @description Takes an AudioBuffer instance and converts it to a Base 64 Data URL so it can be passed to an <Html5Audio /> tag.
|
||||
* @see [Documentation](https://remotion.dev/docs/audio-buffer-to-data-url)
|
||||
*/
|
||||
const audioBufferToDataUrl = (buffer) => {
|
||||
const wavAsArrayBuffer = (0, audio_buffer_to_wav_1.audioBufferToWav)(buffer, {
|
||||
float32: true,
|
||||
});
|
||||
let binary = '';
|
||||
const bytes = new Uint8Array(wavAsArrayBuffer);
|
||||
const len = bytes.byteLength;
|
||||
for (let i = 0; i < len; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return 'data:audio/wav;base64,' + window.btoa(binary);
|
||||
};
|
||||
exports.audioBufferToDataUrl = audioBufferToDataUrl;
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const combineFloat32Arrays: (arrays: Float32Array[]) => Float32Array<ArrayBufferLike>;
|
||||
Generated
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.combineFloat32Arrays = void 0;
|
||||
const combineFloat32Arrays = (arrays) => {
|
||||
if (arrays.length === 0) {
|
||||
return new Float32Array([]);
|
||||
}
|
||||
if (arrays.length === 1) {
|
||||
return arrays[0];
|
||||
}
|
||||
let totalLength = 0;
|
||||
for (const array of arrays) {
|
||||
totalLength += array.length;
|
||||
}
|
||||
const result = new Float32Array(totalLength);
|
||||
let offset = 0;
|
||||
for (const array of arrays) {
|
||||
result.set(array, offset);
|
||||
offset += array.length;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
exports.combineFloat32Arrays = combineFloat32Arrays;
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
export type Point = {
|
||||
x: number;
|
||||
y: number;
|
||||
};
|
||||
export declare const createSmoothSvgPath: ({ points }: {
|
||||
points: Point[];
|
||||
}) => string;
|
||||
Generated
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.createSmoothSvgPath = void 0;
|
||||
const line = (pointA, pointB) => {
|
||||
const lengthX = pointB.x - pointA.x;
|
||||
const lengthY = pointB.y - pointA.y;
|
||||
return {
|
||||
length: Math.sqrt(lengthX ** 2 + lengthY ** 2),
|
||||
angle: Math.atan2(lengthY, lengthX),
|
||||
};
|
||||
};
|
||||
const controlPoint = ({ current, previous, next, reverse, }) => {
|
||||
const p = previous || current;
|
||||
const n = next || current;
|
||||
// The smoothing ratio
|
||||
const smoothing = 0.2;
|
||||
// Properties of the opposed-line
|
||||
const o = line(p, n);
|
||||
const angle = o.angle + (reverse ? Math.PI : 0);
|
||||
const length = o.length * smoothing;
|
||||
const x = current.x + Math.cos(angle) * length;
|
||||
const y = current.y + Math.sin(angle) * length;
|
||||
return { x, y };
|
||||
};
|
||||
const createSmoothSvgPath = ({ points }) => {
|
||||
return points.reduce((acc, current, i, a) => {
|
||||
if (i === 0) {
|
||||
return `M ${current.x},${current.y}`;
|
||||
}
|
||||
const { x, y } = current;
|
||||
const previous = a[i - 1];
|
||||
const twoPrevious = a[i - 2];
|
||||
const next = a[i + 1];
|
||||
const { x: cp1x, y: cp1y } = controlPoint({
|
||||
current: previous,
|
||||
previous: twoPrevious,
|
||||
next: current,
|
||||
reverse: false,
|
||||
});
|
||||
const { x: cp2x, y: cp2y } = controlPoint({
|
||||
current,
|
||||
previous,
|
||||
next,
|
||||
reverse: true,
|
||||
});
|
||||
return `${acc} C ${cp1x},${cp1y} ${cp2x},${cp2y} ${x},${y}`;
|
||||
}, '');
|
||||
};
|
||||
exports.createSmoothSvgPath = createSmoothSvgPath;
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const fetchWithCorsCatch: (src: string, init?: RequestInit) => Promise<Response>;
|
||||
Generated
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.fetchWithCorsCatch = void 0;
|
||||
const fetchWithCorsCatch = async (src, init) => {
|
||||
try {
|
||||
const response = await fetch(src, {
|
||||
mode: 'cors',
|
||||
referrerPolicy: 'no-referrer-when-downgrade',
|
||||
...init,
|
||||
});
|
||||
return response;
|
||||
}
|
||||
catch (err) {
|
||||
const error = err;
|
||||
if (
|
||||
// Chrome
|
||||
error.message.includes('Failed to fetch') ||
|
||||
// Safari
|
||||
error.message.includes('Load failed') ||
|
||||
// Firefox
|
||||
error.message.includes('NetworkError when attempting to fetch resource')) {
|
||||
throw new TypeError(`Failed to read from ${src}: ${error.message}. Does the resource support CORS?`);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
exports.fetchWithCorsCatch = fetchWithCorsCatch;
|
||||
Generated
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
export declare const complexAdd: (a: [number, number], b: [number, number]) => [number, number];
|
||||
export declare const complexSubtract: (a: [number, number], b: [number, number]) => [number, number];
|
||||
export declare const complexMultiply: (a: [number, number], b: [number, number]) => [number, number];
|
||||
export declare const complexMagnitude: (c: [number, number]) => number;
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
// Adapted from node-fft project by Joshua Wong and Ben Bryan
|
||||
// https://github.com/vail-systems/node-fft
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.complexMagnitude = exports.complexMultiply = exports.complexSubtract = exports.complexAdd = void 0;
|
||||
const complexAdd = function (a, b) {
|
||||
return [a[0] + b[0], a[1] + b[1]];
|
||||
};
|
||||
exports.complexAdd = complexAdd;
|
||||
const complexSubtract = function (a, b) {
|
||||
return [a[0] - b[0], a[1] - b[1]];
|
||||
};
|
||||
exports.complexSubtract = complexSubtract;
|
||||
const complexMultiply = function (a, b) {
|
||||
return [a[0] * b[0] - a[1] * b[1], a[0] * b[1] + a[1] * b[0]];
|
||||
};
|
||||
exports.complexMultiply = complexMultiply;
|
||||
const complexMagnitude = function (c) {
|
||||
return Math.sqrt(c[0] * c[0] + c[1] * c[1]);
|
||||
};
|
||||
exports.complexMagnitude = complexMagnitude;
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const exponent: (k: number, N: number) => [number, number];
|
||||
Generated
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
"use strict";
|
||||
// Adapted from node-fft project by Joshua Wong and Ben Bryan
|
||||
// https://github.com/vail-systems/node-fft
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.exponent = void 0;
|
||||
const mapExponent = {};
|
||||
const exponent = function (k, N) {
|
||||
const x = -2 * Math.PI * (k / N);
|
||||
mapExponent[N] = mapExponent[N] || {};
|
||||
mapExponent[N][k] = mapExponent[N][k] || [Math.cos(x), Math.sin(x)]; // [Real, Imaginary]
|
||||
return mapExponent[N][k];
|
||||
};
|
||||
exports.exponent = exponent;
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const fftAccurate: (vector: Int16Array) => [number, number][];
|
||||
Generated
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
"use strict";
|
||||
// Adapted from node-fft project by Joshua Wong and Ben Bryan
|
||||
// https://github.com/vail-systems/node-fft
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.fftAccurate = void 0;
|
||||
const complex_1 = require("./complex");
|
||||
const exponent_1 = require("./exponent");
|
||||
const fftAccurate = function (vector) {
|
||||
const X = [];
|
||||
const N = vector.length;
|
||||
// Base case is X = x + 0i since our input is assumed to be real only.
|
||||
if (N === 1) {
|
||||
if (Array.isArray(vector[0])) {
|
||||
// If input vector contains complex numbers
|
||||
return [[vector[0][0], vector[0][1]]];
|
||||
}
|
||||
return [[vector[0], 0]];
|
||||
}
|
||||
// Recurse: all even samples
|
||||
const X_evens = (0, exports.fftAccurate)(vector.filter((_, ix) => ix % 2 === 0));
|
||||
// Recurse: all odd samples
|
||||
const X_odds = (0, exports.fftAccurate)(vector.filter((__, ix) => ix % 2 === 1));
|
||||
// Now, perform N/2 operations!
|
||||
for (let k = 0; k < N / 2; k++) {
|
||||
// t is a complex number!
|
||||
const t = X_evens[k];
|
||||
const e = (0, complex_1.complexMultiply)((0, exponent_1.exponent)(k, N), X_odds[k]);
|
||||
X[k] = (0, complex_1.complexAdd)(t, e);
|
||||
X[k + N / 2] = (0, complex_1.complexSubtract)(t, e);
|
||||
}
|
||||
return X;
|
||||
};
|
||||
exports.fftAccurate = fftAccurate;
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const fftFast: (vector: Int16Array) => [number, number][];
|
||||
Generated
Vendored
+76
@@ -0,0 +1,76 @@
|
||||
"use strict";
|
||||
// https://pastebin.com/raw/D42RbPe5
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.fftFast = void 0;
|
||||
// Function to reverse bits in an integer
|
||||
function reverseBits(num, numBits) {
|
||||
let result = 0;
|
||||
for (let i = 0; i < numBits; i++) {
|
||||
result = (result << 1) | ((num >> i) & 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// Hamming window function
|
||||
function hammingWindow(N) {
|
||||
const win = new Array(N);
|
||||
for (let i = 0; i < N; i++) {
|
||||
win[i] = 0.8 - 0.46 * Math.cos((2 * Math.PI * i) / (N - 1));
|
||||
}
|
||||
return win;
|
||||
}
|
||||
// Function to calculate the bit-reversed permutation indices
|
||||
function bitReversePermutation(N) {
|
||||
const bitReversed = new Array(N);
|
||||
for (let i = 0; i < N; i++) {
|
||||
bitReversed[i] = reverseBits(i, Math.log2(N));
|
||||
}
|
||||
return bitReversed;
|
||||
}
|
||||
const fftFast = function (vector) {
|
||||
const N = vector.length;
|
||||
const X = new Array(N);
|
||||
if (N <= 1) {
|
||||
for (let i = 0; i < vector.length; i++) {
|
||||
const value = vector[i];
|
||||
X[i] = [value * 2, 0];
|
||||
}
|
||||
return X;
|
||||
}
|
||||
// Apply a windowing function to the input data
|
||||
const window = hammingWindow(N); // You can choose a different window function if needed
|
||||
for (let i = 0; i < N; i++) {
|
||||
X[i] = [vector[i] * window[i], 0];
|
||||
}
|
||||
// Bit-Reversal Permutation
|
||||
const bitReversed = bitReversePermutation(N);
|
||||
for (let i = 0; i < N; i++) {
|
||||
X[i] = [vector[bitReversed[i]], 0];
|
||||
}
|
||||
// Cooley-Tukey FFT
|
||||
for (let s = 1; s <= Math.log2(N); s++) {
|
||||
const m = 1 << s; // Number of elements in each subarray
|
||||
const mHalf = m / 2; // Half the number of elements in each subarray
|
||||
const angleIncrement = (2 * Math.PI) / m;
|
||||
for (let k = 0; k < N; k += m) {
|
||||
let omegaReal = 1.0;
|
||||
let omegaImag = 0.0;
|
||||
for (let j = 0; j < mHalf; j++) {
|
||||
const tReal = omegaReal * X[k + j + mHalf][0] - omegaImag * X[k + j + mHalf][1];
|
||||
const tImag = omegaReal * X[k + j + mHalf][1] + omegaImag * X[k + j + mHalf][0];
|
||||
const uReal = X[k + j][0];
|
||||
const uImag = X[k + j][1];
|
||||
X[k + j] = [uReal + tReal, uImag + tImag];
|
||||
X[k + j + mHalf] = [uReal - tReal, uImag - tImag];
|
||||
// Twiddle factor update
|
||||
const tempReal = omegaReal * Math.cos(angleIncrement) -
|
||||
omegaImag * Math.sin(angleIncrement);
|
||||
omegaImag =
|
||||
omegaReal * Math.sin(angleIncrement) +
|
||||
omegaImag * Math.cos(angleIncrement);
|
||||
omegaReal = tempReal;
|
||||
}
|
||||
}
|
||||
}
|
||||
return X;
|
||||
};
|
||||
exports.fftFast = fftFast;
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const fftFreq: (fftBins: [number, number][], sampleRate: number) => number[];
|
||||
Generated
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
"use strict";
|
||||
// Adapted from node-fft project by Joshua Wong and Ben Bryan
|
||||
// https://github.com/vail-systems/node-fft
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.fftFreq = void 0;
|
||||
const fftFreq = function (fftBins, sampleRate) {
|
||||
const stepFreq = sampleRate / fftBins.length;
|
||||
const ret = fftBins.slice(0, fftBins.length / 2);
|
||||
return ret.map((__, ix) => {
|
||||
return ix * stepFreq;
|
||||
});
|
||||
};
|
||||
exports.fftFreq = fftFreq;
|
||||
Generated
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
export type OptimizeFor = 'accuracy' | 'speed';
|
||||
export declare const getVisualization: ({ sampleSize, data, sampleRate, frame, fps, maxInt, optimizeFor, dataOffsetInSeconds, }: {
|
||||
sampleSize: number;
|
||||
data: Float32Array;
|
||||
frame: number;
|
||||
sampleRate: number;
|
||||
fps: number;
|
||||
maxInt: number;
|
||||
optimizeFor: OptimizeFor;
|
||||
dataOffsetInSeconds: number;
|
||||
}) => number[];
|
||||
Generated
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
"use strict";
|
||||
// Adapted from node-fft project by Joshua Wong and Ben Bryan
|
||||
// https://github.com/vail-systems/node-fft
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getVisualization = void 0;
|
||||
const fft_accurate_1 = require("./fft-accurate");
|
||||
const fft_fast_1 = require("./fft-fast");
|
||||
const mag_1 = require("./mag");
|
||||
const smoothing_1 = require("./smoothing");
|
||||
const to_int_16_1 = require("./to-int-16");
|
||||
const getVisualization = ({ sampleSize, data, sampleRate, frame, fps, maxInt, optimizeFor, dataOffsetInSeconds, }) => {
|
||||
const isPowerOfTwo = sampleSize > 0 && (sampleSize & (sampleSize - 1)) === 0;
|
||||
if (!isPowerOfTwo) {
|
||||
throw new TypeError(`The argument "bars" must be a power of two. For example: 64, 128. Got instead: ${sampleSize}`);
|
||||
}
|
||||
if (!fps) {
|
||||
throw new TypeError('The argument "fps" was not provided');
|
||||
}
|
||||
if (data.length < sampleSize) {
|
||||
throw new TypeError('Audio data is not big enough to provide ' + sampleSize + ' bars.');
|
||||
}
|
||||
const start = Math.floor((frame / fps - dataOffsetInSeconds) * sampleRate);
|
||||
const actualStart = Math.max(0, start - sampleSize / 2);
|
||||
const ints = new Int16Array({
|
||||
length: sampleSize,
|
||||
});
|
||||
ints.set(data.subarray(actualStart, actualStart + sampleSize).map((x) => (0, to_int_16_1.toInt16)(x)));
|
||||
const alg = optimizeFor === 'accuracy' ? fft_accurate_1.fftAccurate : fft_fast_1.fftFast;
|
||||
const phasors = alg(ints);
|
||||
const magnitudes = (0, mag_1.fftMag)(phasors).map((p) => p);
|
||||
return (0, smoothing_1.smoothen)(magnitudes).map((m) => m / (sampleSize / 2) / maxInt);
|
||||
};
|
||||
exports.getVisualization = getVisualization;
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const fftMag: (fftBins: [number, number][]) => number[];
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
"use strict";
|
||||
// Adapted from node-fft project by Joshua Wong and Ben Bryan
|
||||
// https://github.com/vail-systems/node-fft
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.fftMag = void 0;
|
||||
const complex_1 = require("./complex");
|
||||
const fftMag = function (fftBins) {
|
||||
const ret = fftBins.map((f) => (0, complex_1.complexMagnitude)(f));
|
||||
return ret.slice(0, ret.length / 2);
|
||||
};
|
||||
exports.fftMag = fftMag;
|
||||
Generated
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
import type { MediaUtilsAudioData } from '../types';
|
||||
export declare const getMaxPossibleMagnitude: (metadata: MediaUtilsAudioData) => number;
|
||||
Generated
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
"use strict";
|
||||
// Adapted from node-fft project by Joshua Wong and Ben Bryan
|
||||
// https://github.com/vail-systems/node-fft
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getMaxPossibleMagnitude = void 0;
|
||||
const to_int_16_1 = require("./to-int-16");
|
||||
const getMax = (array) => {
|
||||
let max = 0;
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const val = array[i];
|
||||
if (val > max) {
|
||||
max = val;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
};
|
||||
const cache = {};
|
||||
const getMaxPossibleMagnitude = (metadata) => {
|
||||
if (cache[metadata.resultId]) {
|
||||
return cache[metadata.resultId];
|
||||
}
|
||||
const result = (0, to_int_16_1.toInt16)(getMax(metadata.channelWaveforms[0]));
|
||||
cache[metadata.resultId] = result;
|
||||
return result;
|
||||
};
|
||||
exports.getMaxPossibleMagnitude = getMaxPossibleMagnitude;
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const smoothen: (array: number[]) => number[];
|
||||
Generated
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
"use strict";
|
||||
// Adapted from node-fft project by Joshua Wong and Ben Bryan
|
||||
// https://github.com/vail-systems/node-fft
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.smoothen = void 0;
|
||||
const smoothingPasses = 3;
|
||||
const smoothingPoints = 3;
|
||||
const smoothen = function (array) {
|
||||
let lastArray = array;
|
||||
const newArr = [];
|
||||
for (let pass = 0; pass < smoothingPasses; pass++) {
|
||||
const sidePoints = Math.floor(smoothingPoints / 2); // our window is centered so this is both nL and nR
|
||||
const cn = 1 / (2 * sidePoints + 1); // constant
|
||||
for (let i = 0; i < sidePoints; i++) {
|
||||
newArr[i] = lastArray[i];
|
||||
newArr[lastArray.length - i - 1] = lastArray[lastArray.length - i - 1];
|
||||
}
|
||||
for (let i = sidePoints; i < lastArray.length - sidePoints; i++) {
|
||||
let sum = 0;
|
||||
for (let n = -sidePoints; n <= sidePoints; n++) {
|
||||
sum += cn * lastArray[i + n] + n;
|
||||
}
|
||||
newArr[i] = sum;
|
||||
}
|
||||
lastArray = newArr;
|
||||
}
|
||||
return newArr;
|
||||
};
|
||||
exports.smoothen = smoothen;
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const toInt16: (x: number) => number;
|
||||
Generated
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.toInt16 = void 0;
|
||||
const toInt16 = (x) => (x > 0 ? x * 0x7fff : x * 0x8000);
|
||||
exports.toInt16 = toInt16;
|
||||
Generated
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
import type { MediaUtilsAudioData } from './types';
|
||||
type Options = {
|
||||
sampleRate?: number;
|
||||
};
|
||||
export declare const getAudioData: (src: string, options?: Options) => Promise<MediaUtilsAudioData>;
|
||||
export {};
|
||||
Generated
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getAudioData = void 0;
|
||||
const fetch_with_cors_catch_1 = require("./fetch-with-cors-catch");
|
||||
const is_remote_asset_1 = require("./is-remote-asset");
|
||||
const p_limit_1 = require("./p-limit");
|
||||
const metadataCache = {};
|
||||
const limit = (0, p_limit_1.pLimit)(3);
|
||||
const fn = async (src, options) => {
|
||||
var _a;
|
||||
if (metadataCache[src]) {
|
||||
return metadataCache[src];
|
||||
}
|
||||
if (typeof document === 'undefined') {
|
||||
throw new Error('getAudioData() is only available in the browser.');
|
||||
}
|
||||
const audioContext = new AudioContext({
|
||||
sampleRate: (_a = options === null || options === void 0 ? void 0 : options.sampleRate) !== null && _a !== void 0 ? _a : 48000,
|
||||
});
|
||||
const response = await (0, fetch_with_cors_catch_1.fetchWithCorsCatch)(src);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch audio data from ${src}: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
const wave = await audioContext.decodeAudioData(arrayBuffer);
|
||||
const channelWaveforms = new Array(wave.numberOfChannels)
|
||||
.fill(true)
|
||||
.map((_, channel) => {
|
||||
return wave.getChannelData(channel);
|
||||
});
|
||||
const metadata = {
|
||||
channelWaveforms,
|
||||
sampleRate: wave.sampleRate,
|
||||
durationInSeconds: wave.duration,
|
||||
numberOfChannels: wave.numberOfChannels,
|
||||
resultId: String(Math.random()),
|
||||
isRemote: (0, is_remote_asset_1.isRemoteAsset)(src),
|
||||
};
|
||||
metadataCache[src] = metadata;
|
||||
return metadata;
|
||||
};
|
||||
/*
|
||||
* @description Takes an audio or video src, loads it and returns data and metadata for the specified source.
|
||||
* @see [Documentation](https://remotion.dev/docs/get-audio-data)
|
||||
*/
|
||||
const getAudioData = (src, options) => {
|
||||
return limit(fn, src, options);
|
||||
};
|
||||
exports.getAudioData = getAudioData;
|
||||
Generated
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* @description Gets the duration in seconds of an audio source by creating an invisible `<audio>` tag, loading the audio, and returning the duration.
|
||||
* @see [Documentation](https://remotion.dev/docs/get-audio-duration-in-seconds)
|
||||
* @deprecated Use `parseMedia()` instead: https://www.remotion.dev/docs/media-parser/parse-media
|
||||
*/
|
||||
export declare const getAudioDurationInSeconds: (src: string) => Promise<number>;
|
||||
/**
|
||||
* @deprecated Renamed to `getAudioDurationInSeconds`
|
||||
*/
|
||||
export declare const getAudioDuration: (src: string) => Promise<number>;
|
||||
Generated
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getAudioDuration = exports.getAudioDurationInSeconds = void 0;
|
||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
const media_tag_error_handling_1 = require("./media-tag-error-handling");
|
||||
const p_limit_1 = require("./p-limit");
|
||||
const limit = (0, p_limit_1.pLimit)(3);
|
||||
const metadataCache = {};
|
||||
const fn = (src) => {
|
||||
if (metadataCache[src]) {
|
||||
return Promise.resolve(metadataCache[src]);
|
||||
}
|
||||
if (typeof document === 'undefined') {
|
||||
throw new Error('getAudioDuration() is only available in the browser.');
|
||||
}
|
||||
const audio = document.createElement('audio');
|
||||
audio.src = src;
|
||||
return new Promise((resolve, reject) => {
|
||||
const onError = () => {
|
||||
(0, media_tag_error_handling_1.onMediaError)({
|
||||
error: audio.error,
|
||||
src,
|
||||
cleanup,
|
||||
reject,
|
||||
api: 'getAudioDurationInSeconds()',
|
||||
});
|
||||
};
|
||||
const onLoadedMetadata = () => {
|
||||
metadataCache[src] = audio.duration;
|
||||
resolve(audio.duration);
|
||||
cleanup();
|
||||
};
|
||||
const cleanup = () => {
|
||||
audio.removeEventListener('loadedmetadata', onLoadedMetadata);
|
||||
audio.removeEventListener('error', onError);
|
||||
audio.remove();
|
||||
};
|
||||
audio.addEventListener('loadedmetadata', onLoadedMetadata, { once: true });
|
||||
audio.addEventListener('error', onError, { once: true });
|
||||
});
|
||||
};
|
||||
/**
|
||||
* @description Gets the duration in seconds of an audio source by creating an invisible `<audio>` tag, loading the audio, and returning the duration.
|
||||
* @see [Documentation](https://remotion.dev/docs/get-audio-duration-in-seconds)
|
||||
* @deprecated Use `parseMedia()` instead: https://www.remotion.dev/docs/media-parser/parse-media
|
||||
*/
|
||||
const getAudioDurationInSeconds = (src) => {
|
||||
return limit(fn, src);
|
||||
};
|
||||
exports.getAudioDurationInSeconds = getAudioDurationInSeconds;
|
||||
/**
|
||||
* @deprecated Renamed to `getAudioDurationInSeconds`
|
||||
*/
|
||||
const getAudioDuration = (src) => (0, exports.getAudioDurationInSeconds)(src);
|
||||
exports.getAudioDuration = getAudioDuration;
|
||||
Generated
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
import type { ImageDimensions } from './types';
|
||||
export declare function getImageDimensions(src: string): Promise<ImageDimensions>;
|
||||
Generated
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getImageDimensions = getImageDimensions;
|
||||
const p_limit_1 = require("./p-limit");
|
||||
const imageDimensionsCache = {};
|
||||
const limit = (0, p_limit_1.pLimit)(3);
|
||||
const fn = async (src) => {
|
||||
if (imageDimensionsCache[src]) {
|
||||
return imageDimensionsCache[src];
|
||||
}
|
||||
if (typeof document === 'undefined') {
|
||||
throw new Error('getImageDimensions() is only available in the browser.');
|
||||
}
|
||||
const imageDimensions = await new Promise((resolved, reject) => {
|
||||
const image = new Image();
|
||||
image.onload = () => {
|
||||
const { width, height } = image;
|
||||
resolved({ width, height });
|
||||
};
|
||||
image.onerror = reject;
|
||||
image.src = src;
|
||||
});
|
||||
imageDimensionsCache[src] = imageDimensions;
|
||||
return imageDimensions;
|
||||
};
|
||||
/*
|
||||
* @description Takes an image src, retrieves the dimensions of an image.
|
||||
* @see [Documentation](https://remotion.dev/docs/get-image-dimensions)
|
||||
*/
|
||||
function getImageDimensions(src) {
|
||||
return limit(fn, src);
|
||||
}
|
||||
Generated
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
import type { InputAudioTrack } from 'mediabunny';
|
||||
export type GetPartialAudioDataProps = {
|
||||
track: InputAudioTrack;
|
||||
fromSeconds: number;
|
||||
toSeconds: number;
|
||||
channelIndex: number;
|
||||
signal: AbortSignal;
|
||||
isMatroska: boolean;
|
||||
};
|
||||
export declare const getPartialAudioData: ({ track, fromSeconds, toSeconds, channelIndex, signal, isMatroska, }: GetPartialAudioDataProps) => Promise<Float32Array>;
|
||||
Generated
Vendored
+50
@@ -0,0 +1,50 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getPartialAudioData = void 0;
|
||||
const mediabunny_1 = require("mediabunny");
|
||||
// Audio frames might have dependencies on previous and next frames so we need to decode a bit more and then discard it.
|
||||
// The worst case seems to be FLAC files with a 65'535 sample window, which would be 1486.0ms at 44.1Khz.
|
||||
// So let's set a threshold of 1.5 seconds.
|
||||
const EXTRA_THRESHOLD_IN_SECONDS = 1.5;
|
||||
const getPartialAudioData = async ({ track, fromSeconds, toSeconds, channelIndex, signal, isMatroska = false, }) => {
|
||||
if (signal.aborted) {
|
||||
throw new Error('Operation was aborted');
|
||||
}
|
||||
const audioSamples = [];
|
||||
// matroska must be decoded from the start due to limitation
|
||||
// https://www.remotion.dev/docs/media/support#matroska-limitation
|
||||
// Also request extra data beforehand to handle audio frame dependencies
|
||||
const actualFromSeconds = isMatroska
|
||||
? 0
|
||||
: Math.max(0, fromSeconds - EXTRA_THRESHOLD_IN_SECONDS);
|
||||
// mediabunny docs: constructing the sink is virtually free and does not perform any media data reads.
|
||||
const sink = new mediabunny_1.AudioBufferSink(track);
|
||||
const iterator = sink.buffers(actualFromSeconds, toSeconds);
|
||||
for await (const { buffer, timestamp, duration } of iterator) {
|
||||
if (signal.aborted) {
|
||||
break;
|
||||
}
|
||||
const channelData = buffer.getChannelData(channelIndex);
|
||||
const bufferStartSeconds = timestamp;
|
||||
const bufferEndSeconds = timestamp + duration;
|
||||
const overlapStartSecond = Math.max(bufferStartSeconds, fromSeconds);
|
||||
const overlapEndSecond = Math.min(bufferEndSeconds, toSeconds);
|
||||
if (overlapStartSecond >= overlapEndSecond) {
|
||||
continue;
|
||||
}
|
||||
const startSampleInBuffer = Math.floor((overlapStartSecond - bufferStartSeconds) * buffer.sampleRate);
|
||||
const endSampleInBuffer = Math.ceil((overlapEndSecond - bufferStartSeconds) * buffer.sampleRate);
|
||||
const trimmedData = channelData.slice(startSampleInBuffer, endSampleInBuffer);
|
||||
audioSamples.push(trimmedData);
|
||||
}
|
||||
await iterator.return();
|
||||
const totalSamples = audioSamples.reduce((sum, sample) => sum + sample.length, 0);
|
||||
const result = new Float32Array(totalSamples);
|
||||
let offset = 0;
|
||||
for (const audioSample of audioSamples) {
|
||||
result.set(audioSample, offset);
|
||||
offset += audioSample.length;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
exports.getPartialAudioData = getPartialAudioData;
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
import type { VideoMetadata } from './types';
|
||||
/**
|
||||
* @description Takes a src to a video, loads it and returns metadata for the specified source.
|
||||
* @see [Documentation](https://remotion.dev/docs/get-video-metadata)
|
||||
* @deprecated Use `parseMedia()` instead: https://www.remotion.dev/docs/miscellaneous/parse-media-vs-get-video-metadata
|
||||
*/
|
||||
export declare const getVideoMetadata: (src: string) => Promise<VideoMetadata>;
|
||||
Generated
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getVideoMetadata = void 0;
|
||||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
const is_remote_asset_1 = require("./is-remote-asset");
|
||||
const media_tag_error_handling_1 = require("./media-tag-error-handling");
|
||||
const p_limit_1 = require("./p-limit");
|
||||
const cache = {};
|
||||
const limit = (0, p_limit_1.pLimit)(3);
|
||||
const fn = (src) => {
|
||||
if (cache[src]) {
|
||||
return Promise.resolve(cache[src]);
|
||||
}
|
||||
if (typeof document === 'undefined') {
|
||||
throw new Error('getVideoMetadata() is only available in the browser.');
|
||||
}
|
||||
const video = document.createElement('video');
|
||||
video.src = src;
|
||||
return new Promise((resolve, reject) => {
|
||||
const onError = () => {
|
||||
(0, media_tag_error_handling_1.onMediaError)({
|
||||
error: video.error,
|
||||
src,
|
||||
cleanup,
|
||||
reject,
|
||||
api: 'getVideoMetadata()',
|
||||
});
|
||||
};
|
||||
const onLoadedMetadata = () => {
|
||||
const pixels = video.videoHeight * video.videoWidth;
|
||||
if (pixels === 0) {
|
||||
reject(new Error(`Unable to determine video metadata for ${src}`));
|
||||
return;
|
||||
}
|
||||
if (!Number.isFinite(video.duration)) {
|
||||
reject(new Error(`Unable to determine video duration for ${src} - got Infinity. Re-encoding this video may fix this issue.`));
|
||||
return;
|
||||
}
|
||||
const metadata = {
|
||||
durationInSeconds: video.duration,
|
||||
width: video.videoWidth,
|
||||
height: video.videoHeight,
|
||||
aspectRatio: video.videoWidth / video.videoHeight,
|
||||
isRemote: (0, is_remote_asset_1.isRemoteAsset)(src),
|
||||
};
|
||||
resolve(metadata);
|
||||
cache[src] = metadata;
|
||||
cleanup();
|
||||
};
|
||||
const cleanup = () => {
|
||||
video.removeEventListener('loadedmetadata', onLoadedMetadata);
|
||||
video.removeEventListener('error', onError);
|
||||
video.remove();
|
||||
};
|
||||
video.addEventListener('loadedmetadata', onLoadedMetadata, { once: true });
|
||||
video.addEventListener('error', onError, { once: true });
|
||||
});
|
||||
};
|
||||
/**
|
||||
* @description Takes a src to a video, loads it and returns metadata for the specified source.
|
||||
* @see [Documentation](https://remotion.dev/docs/get-video-metadata)
|
||||
* @deprecated Use `parseMedia()` instead: https://www.remotion.dev/docs/miscellaneous/parse-media-vs-get-video-metadata
|
||||
*/
|
||||
const getVideoMetadata = (src) => {
|
||||
return limit(fn, src);
|
||||
};
|
||||
exports.getVideoMetadata = getVideoMetadata;
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
export type SampleOutputRange = 'minus-one-to-one' | 'zero-to-one';
|
||||
export declare const getWaveformSamples: ({ audioBuffer, numberOfSamples, outputRange, normalize, }: {
|
||||
audioBuffer: Float32Array;
|
||||
numberOfSamples: number;
|
||||
outputRange: SampleOutputRange;
|
||||
normalize: boolean;
|
||||
}) => number[];
|
||||
Generated
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getWaveformSamples = void 0;
|
||||
const normalize_data_1 = require("./normalize-data");
|
||||
const getWaveformSamples = ({ audioBuffer, numberOfSamples, outputRange, normalize, }) => {
|
||||
const blockSize = Math.floor(audioBuffer.length / numberOfSamples); // the number of samples in each subdivision
|
||||
if (blockSize === 0) {
|
||||
return [];
|
||||
}
|
||||
const filteredData = [];
|
||||
for (let i = 0; i < numberOfSamples; i++) {
|
||||
const blockStart = blockSize * i; // the location of the first sample in the block
|
||||
let sum = 0;
|
||||
for (let j = 0; j < blockSize; j++) {
|
||||
sum += Math.abs(audioBuffer[blockStart + j]); // find the sum of all the samples in the block
|
||||
}
|
||||
filteredData.push(sum / blockSize); // divide the sum by the block size to get the average
|
||||
}
|
||||
if (normalize) {
|
||||
if (outputRange === 'minus-one-to-one') {
|
||||
return (0, normalize_data_1.normalizeData)(filteredData).map((n, i) => {
|
||||
if (i % 2 === 0) {
|
||||
return n * -1;
|
||||
}
|
||||
return n;
|
||||
});
|
||||
}
|
||||
return (0, normalize_data_1.normalizeData)(filteredData);
|
||||
}
|
||||
if (outputRange === 'minus-one-to-one') {
|
||||
return filteredData.map((n, i) => {
|
||||
if (i % 2 === 0) {
|
||||
return n * -1;
|
||||
}
|
||||
return n;
|
||||
});
|
||||
}
|
||||
return filteredData;
|
||||
};
|
||||
exports.getWaveformSamples = getWaveformSamples;
|
||||
Generated
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
import type { SampleOutputRange } from './get-wave-form-samples';
|
||||
import type { MediaUtilsAudioData } from './types';
|
||||
type Bar = {
|
||||
index: number;
|
||||
amplitude: number;
|
||||
};
|
||||
export type GetWaveformPortion = {
|
||||
audioData: MediaUtilsAudioData;
|
||||
startTimeInSeconds: number;
|
||||
durationInSeconds: number;
|
||||
numberOfSamples: number;
|
||||
channel?: number;
|
||||
outputRange?: SampleOutputRange;
|
||||
dataOffsetInSeconds?: number;
|
||||
normalize?: boolean;
|
||||
};
|
||||
export declare const getWaveformPortion: ({ audioData, startTimeInSeconds, durationInSeconds, numberOfSamples, channel, outputRange, dataOffsetInSeconds, normalize, }: GetWaveformPortion) => Bar[];
|
||||
export { Bar };
|
||||
Generated
Vendored
+56
@@ -0,0 +1,56 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getWaveformPortion = void 0;
|
||||
const no_react_1 = require("remotion/no-react");
|
||||
const get_wave_form_samples_1 = require("./get-wave-form-samples");
|
||||
const validate_channel_1 = require("./validate-channel");
|
||||
const concatArrays = (arrays) => {
|
||||
// sum of individual array lengths
|
||||
const totalLength = arrays.reduce((acc, value) => acc + value.length, 0);
|
||||
const result = new Float32Array(totalLength);
|
||||
// for each array - copy it over result
|
||||
// next array is copied right after the previous one
|
||||
let length = 0;
|
||||
for (const array of arrays) {
|
||||
result.set(array, length);
|
||||
length += array.length;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
/*
|
||||
* @description Takes bulky waveform data (for example fetched by getAudioData()) and returns a trimmed and simplified version of it, for simpler visualization
|
||||
* @see [Documentation](https://remotion.dev/docs/get-waveform-portion)
|
||||
*/
|
||||
const getWaveformPortion = ({ audioData, startTimeInSeconds, durationInSeconds, numberOfSamples, channel = 0, outputRange = 'zero-to-one', dataOffsetInSeconds, normalize = true, }) => {
|
||||
(0, validate_channel_1.validateChannel)(channel, audioData.numberOfChannels);
|
||||
const waveform = audioData.channelWaveforms[channel];
|
||||
const startSample = Math.floor((startTimeInSeconds - (dataOffsetInSeconds !== null && dataOffsetInSeconds !== void 0 ? dataOffsetInSeconds : 0)) * audioData.sampleRate);
|
||||
const endSample = Math.floor((startTimeInSeconds - (dataOffsetInSeconds !== null && dataOffsetInSeconds !== void 0 ? dataOffsetInSeconds : 0) + durationInSeconds) *
|
||||
audioData.sampleRate);
|
||||
const samplesBeforeStart = 0 - startSample;
|
||||
const samplesAfterEnd = endSample - waveform.length;
|
||||
const clampedStart = Math.max(startSample, 0);
|
||||
const clampedEnd = Math.min(waveform.length, endSample);
|
||||
const padStart = samplesBeforeStart > 0
|
||||
? new Float32Array(samplesBeforeStart).fill(0)
|
||||
: null;
|
||||
const padEnd = samplesAfterEnd > 0 ? new Float32Array(samplesAfterEnd).fill(0) : null;
|
||||
const arrs = [
|
||||
padStart,
|
||||
waveform.slice(clampedStart, clampedEnd),
|
||||
padEnd,
|
||||
].filter(no_react_1.NoReactInternals.truthy);
|
||||
const audioBuffer = arrs.length === 1 ? arrs[0] : concatArrays(arrs);
|
||||
return (0, get_wave_form_samples_1.getWaveformSamples)({
|
||||
audioBuffer,
|
||||
numberOfSamples,
|
||||
outputRange,
|
||||
normalize,
|
||||
}).map((w, i) => {
|
||||
return {
|
||||
index: i,
|
||||
amplitude: w,
|
||||
};
|
||||
});
|
||||
};
|
||||
exports.getWaveformPortion = getWaveformPortion;
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
export { audioBufferToDataUrl } from './audio-buffer/audio-url-helpers';
|
||||
export { createSmoothSvgPath } from './create-smooth-svg-path';
|
||||
export { getAudioData } from './get-audio-data';
|
||||
export { getAudioDuration, getAudioDurationInSeconds, } from './get-audio-duration-in-seconds';
|
||||
export { getImageDimensions } from './get-image-dimensions';
|
||||
export { getVideoMetadata } from './get-video-metadata';
|
||||
export { getWaveformPortion } from './get-waveform-portion';
|
||||
export * from './types';
|
||||
export type { AudioData, MediaUtilsAudioData, VideoMetadata as VideoData, } from './types';
|
||||
export { useAudioData } from './use-audio-data';
|
||||
export { UseWindowedAudioDataOptions, UseWindowedAudioDataReturnValue, useWindowedAudioData, } from './use-windowed-audio-data';
|
||||
export { VisualizeAudioOptions, visualizeAudio } from './visualize-audio';
|
||||
export { VisualizeAudioWaveformOptions, visualizeAudioWaveform, } from './visualize-audio-waveform';
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
||||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.visualizeAudioWaveform = exports.visualizeAudio = exports.useWindowedAudioData = exports.useAudioData = exports.getWaveformPortion = exports.getVideoMetadata = exports.getImageDimensions = exports.getAudioDurationInSeconds = exports.getAudioDuration = exports.getAudioData = exports.createSmoothSvgPath = exports.audioBufferToDataUrl = void 0;
|
||||
var audio_url_helpers_1 = require("./audio-buffer/audio-url-helpers");
|
||||
Object.defineProperty(exports, "audioBufferToDataUrl", { enumerable: true, get: function () { return audio_url_helpers_1.audioBufferToDataUrl; } });
|
||||
var create_smooth_svg_path_1 = require("./create-smooth-svg-path");
|
||||
Object.defineProperty(exports, "createSmoothSvgPath", { enumerable: true, get: function () { return create_smooth_svg_path_1.createSmoothSvgPath; } });
|
||||
var get_audio_data_1 = require("./get-audio-data");
|
||||
Object.defineProperty(exports, "getAudioData", { enumerable: true, get: function () { return get_audio_data_1.getAudioData; } });
|
||||
var get_audio_duration_in_seconds_1 = require("./get-audio-duration-in-seconds");
|
||||
Object.defineProperty(exports, "getAudioDuration", { enumerable: true, get: function () { return get_audio_duration_in_seconds_1.getAudioDuration; } });
|
||||
Object.defineProperty(exports, "getAudioDurationInSeconds", { enumerable: true, get: function () { return get_audio_duration_in_seconds_1.getAudioDurationInSeconds; } });
|
||||
var get_image_dimensions_1 = require("./get-image-dimensions");
|
||||
Object.defineProperty(exports, "getImageDimensions", { enumerable: true, get: function () { return get_image_dimensions_1.getImageDimensions; } });
|
||||
var get_video_metadata_1 = require("./get-video-metadata");
|
||||
Object.defineProperty(exports, "getVideoMetadata", { enumerable: true, get: function () { return get_video_metadata_1.getVideoMetadata; } });
|
||||
var get_waveform_portion_1 = require("./get-waveform-portion");
|
||||
Object.defineProperty(exports, "getWaveformPortion", { enumerable: true, get: function () { return get_waveform_portion_1.getWaveformPortion; } });
|
||||
__exportStar(require("./types"), exports);
|
||||
var use_audio_data_1 = require("./use-audio-data");
|
||||
Object.defineProperty(exports, "useAudioData", { enumerable: true, get: function () { return use_audio_data_1.useAudioData; } });
|
||||
var use_windowed_audio_data_1 = require("./use-windowed-audio-data");
|
||||
Object.defineProperty(exports, "useWindowedAudioData", { enumerable: true, get: function () { return use_windowed_audio_data_1.useWindowedAudioData; } });
|
||||
var visualize_audio_1 = require("./visualize-audio");
|
||||
Object.defineProperty(exports, "visualizeAudio", { enumerable: true, get: function () { return visualize_audio_1.visualizeAudio; } });
|
||||
var visualize_audio_waveform_1 = require("./visualize-audio-waveform");
|
||||
Object.defineProperty(exports, "visualizeAudioWaveform", { enumerable: true, get: function () { return visualize_audio_waveform_1.visualizeAudioWaveform; } });
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const isRemoteAsset: (asset: string) => boolean;
|
||||
Generated
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.isRemoteAsset = void 0;
|
||||
const isRemoteAsset = (asset) => !asset.startsWith(window.origin) && !asset.startsWith('data');
|
||||
exports.isRemoteAsset = isRemoteAsset;
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
export declare const onMediaError: ({ error, src, reject, cleanup, api, }: {
|
||||
error: MediaError;
|
||||
src: string;
|
||||
reject: (reason: unknown) => void;
|
||||
cleanup: () => void;
|
||||
api: string;
|
||||
}) => void;
|
||||
Generated
Vendored
+74
@@ -0,0 +1,74 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.onMediaError = void 0;
|
||||
async function fetchWithTimeout(url, options, timeout = 3000) {
|
||||
const controller = new AbortController();
|
||||
const id = setTimeout(() => controller.abort(), timeout);
|
||||
options.signal = controller.signal;
|
||||
try {
|
||||
const response = await fetch(url, options);
|
||||
clearTimeout(id);
|
||||
return response;
|
||||
}
|
||||
catch (_a) {
|
||||
clearTimeout(id);
|
||||
throw new Error(`Fetch timed out after ${timeout}ms`);
|
||||
}
|
||||
}
|
||||
const checkFor404 = (src) => {
|
||||
return fetchWithTimeout(src, {
|
||||
method: 'HEAD',
|
||||
mode: 'no-cors',
|
||||
}).then((res) => res.status);
|
||||
};
|
||||
const checkFor404OrSkip = async ({ suspecting404, sameOrigin, src, }) => {
|
||||
if (!suspecting404) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
if (!sameOrigin) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
try {
|
||||
return await checkFor404(src);
|
||||
}
|
||||
catch (_a) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
};
|
||||
const onMediaError = ({ error, src, reject, cleanup, api, }) => {
|
||||
const suspecting404 = error.MEDIA_ERR_SRC_NOT_SUPPORTED === error.code;
|
||||
const isSrcSameOriginAsCurrent = new URL(src, window.location.origin)
|
||||
.toString()
|
||||
.startsWith(window.location.origin);
|
||||
checkFor404OrSkip({
|
||||
suspecting404,
|
||||
sameOrigin: isSrcSameOriginAsCurrent,
|
||||
src,
|
||||
})
|
||||
.then((status) => {
|
||||
const err = status === 404
|
||||
? new Error([
|
||||
`Failed to execute ${api}: Received a 404 error loading "${src}".`,
|
||||
'Correct the URL of the file.',
|
||||
].join(' '))
|
||||
: new Error([
|
||||
`Failed to execute ${api}, Received a MediaError loading "${src}". Consider using parseMedia() instead which supports more codecs: https://www.remotion.dev/docs/miscellaneous/parse-media-vs-get-video-metadata`,
|
||||
status === null
|
||||
? null
|
||||
: `HTTP Status code of the file: ${status}.`,
|
||||
error.message
|
||||
? `Browser error message: ${error.message}`
|
||||
: null,
|
||||
'Check the path of the file and if it is a valid video.',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' '));
|
||||
reject(err);
|
||||
cleanup();
|
||||
})
|
||||
.catch((e) => {
|
||||
reject(e);
|
||||
cleanup();
|
||||
});
|
||||
};
|
||||
exports.onMediaError = onMediaError;
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const normalizeData: (filteredData: number[]) => number[];
|
||||
Generated
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.normalizeData = void 0;
|
||||
const normalizeData = (filteredData) => {
|
||||
const max = Math.max(...filteredData);
|
||||
const multiplier = max === 0 ? 0 : max ** -1;
|
||||
return filteredData.map((n) => n * multiplier);
|
||||
};
|
||||
exports.normalizeData = normalizeData;
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const pLimit: (concurrency: number) => <Arguments extends unknown[], ReturnType>(fn: (..._arguments: Arguments) => PromiseLike<ReturnType> | ReturnType, ...args: Arguments) => Promise<ReturnType>;
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.pLimit = void 0;
|
||||
const pLimit = (concurrency) => {
|
||||
const queue = [];
|
||||
let activeCount = 0;
|
||||
const next = () => {
|
||||
var _a;
|
||||
activeCount--;
|
||||
if (queue.length > 0) {
|
||||
(_a = queue.shift()) === null || _a === void 0 ? void 0 : _a();
|
||||
}
|
||||
};
|
||||
const run = async (fn, resolve, ...args) => {
|
||||
activeCount++;
|
||||
// eslint-disable-next-line require-await
|
||||
const result = (async () => fn(...args))();
|
||||
resolve(result);
|
||||
try {
|
||||
await result;
|
||||
}
|
||||
catch (_a) { }
|
||||
next();
|
||||
};
|
||||
const enqueue = (fn, resolve, ...args) => {
|
||||
queue.push(() => run(fn, resolve, ...args));
|
||||
(async () => {
|
||||
var _a;
|
||||
// This function needs to wait until the next microtask before comparing
|
||||
// `activeCount` to `concurrency`, because `activeCount` is updated asynchronously
|
||||
// when the run function is dequeued and called. The comparison in the if-statement
|
||||
// needs to happen asynchronously as well to get an up-to-date value for `activeCount`.
|
||||
await Promise.resolve();
|
||||
if (activeCount < concurrency && queue.length > 0) {
|
||||
(_a = queue.shift()) === null || _a === void 0 ? void 0 : _a();
|
||||
}
|
||||
})();
|
||||
};
|
||||
const generator = (fn, ...args) => new Promise((resolve) => {
|
||||
enqueue(fn, resolve, ...args);
|
||||
});
|
||||
Object.defineProperties(generator, {
|
||||
activeCount: {
|
||||
get: () => activeCount,
|
||||
},
|
||||
pendingCount: {
|
||||
get: () => queue.length,
|
||||
},
|
||||
clearQueue: {
|
||||
value: () => {
|
||||
queue.length = 0;
|
||||
},
|
||||
},
|
||||
});
|
||||
return generator;
|
||||
};
|
||||
exports.pLimit = pLimit;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
export type MediaUtilsAudioData = {
|
||||
channelWaveforms: Float32Array[];
|
||||
sampleRate: number;
|
||||
durationInSeconds: number;
|
||||
numberOfChannels: number;
|
||||
resultId: string;
|
||||
isRemote: boolean;
|
||||
};
|
||||
/**
|
||||
* @deprecated renamed to MediaUtilsAudioData instead
|
||||
*/
|
||||
export type AudioData = MediaUtilsAudioData;
|
||||
export type VideoMetadata = {
|
||||
durationInSeconds: number;
|
||||
width: number;
|
||||
height: number;
|
||||
aspectRatio: number;
|
||||
isRemote: boolean;
|
||||
};
|
||||
export type ImageDimensions = {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
Generated
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
import type { MediaUtilsAudioData } from './types';
|
||||
export declare const useAudioData: (src: string) => MediaUtilsAudioData | null;
|
||||
Generated
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.useAudioData = void 0;
|
||||
const react_1 = require("react");
|
||||
const remotion_1 = require("remotion");
|
||||
const get_audio_data_1 = require("./get-audio-data");
|
||||
/*
|
||||
* @description Wraps the getAudioData() function into a hook and does three things: keeps the audio data in a state, wraps the function in a delayRender() / continueRender() pattern, and handles the case where the component gets unmounted while fetching is in progress to prevent React errors.
|
||||
* @see [Documentation](https://www.remotion.dev/docs/use-audio-data)
|
||||
*/
|
||||
const useAudioData = (src) => {
|
||||
if (!src) {
|
||||
throw new TypeError("useAudioData requires a 'src' parameter");
|
||||
}
|
||||
const mountState = (0, react_1.useRef)({ isMounted: true });
|
||||
(0, react_1.useEffect)(() => {
|
||||
const { current } = mountState;
|
||||
current.isMounted = true;
|
||||
return () => {
|
||||
current.isMounted = false;
|
||||
};
|
||||
}, []);
|
||||
const [metadata, setMetadata] = (0, react_1.useState)(null);
|
||||
const { delayRender, continueRender } = (0, remotion_1.useDelayRender)();
|
||||
const fetchMetadata = (0, react_1.useCallback)(async () => {
|
||||
const handle = delayRender(`Waiting for audio metadata with src="${src}" to be loaded`);
|
||||
try {
|
||||
const data = await (0, get_audio_data_1.getAudioData)(src);
|
||||
if (mountState.current.isMounted) {
|
||||
setMetadata(data);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
(0, remotion_1.cancelRender)(err);
|
||||
}
|
||||
continueRender(handle);
|
||||
}, [src, delayRender, continueRender]);
|
||||
(0, react_1.useLayoutEffect)(() => {
|
||||
fetchMetadata();
|
||||
}, [fetchMetadata]);
|
||||
return metadata;
|
||||
};
|
||||
exports.useAudioData = useAudioData;
|
||||
Generated
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
import type { MediaUtilsAudioData } from './types';
|
||||
export type UseWindowedAudioDataOptions = {
|
||||
src: string;
|
||||
frame: number;
|
||||
fps: number;
|
||||
windowInSeconds: number;
|
||||
channelIndex?: number;
|
||||
};
|
||||
export type UseWindowedAudioDataReturnValue = {
|
||||
audioData: MediaUtilsAudioData | null;
|
||||
dataOffsetInSeconds: number;
|
||||
};
|
||||
export declare const useWindowedAudioData: ({ src, frame, fps, windowInSeconds, channelIndex, }: UseWindowedAudioDataOptions) => UseWindowedAudioDataReturnValue;
|
||||
Generated
Vendored
+283
@@ -0,0 +1,283 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.useWindowedAudioData = void 0;
|
||||
const mediabunny_1 = require("mediabunny");
|
||||
const react_1 = require("react");
|
||||
const remotion_1 = require("remotion");
|
||||
const combine_float32_arrays_1 = require("./combine-float32-arrays");
|
||||
const get_partial_audio_data_1 = require("./get-partial-audio-data");
|
||||
const is_remote_asset_1 = require("./is-remote-asset");
|
||||
const warnedMatroska = {};
|
||||
const useWindowedAudioData = ({ src, frame, fps, windowInSeconds, channelIndex = 0, }) => {
|
||||
const isMounted = (0, react_1.useRef)(true);
|
||||
const [audioUtils, setAudioUtils] = (0, react_1.useState)(null);
|
||||
const [waveFormMap, setWaveformMap] = (0, react_1.useState)({});
|
||||
const requests = (0, react_1.useRef)({});
|
||||
const [initialWindowInSeconds] = (0, react_1.useState)(windowInSeconds);
|
||||
if (windowInSeconds !== initialWindowInSeconds) {
|
||||
throw new Error('windowInSeconds cannot be changed dynamically');
|
||||
}
|
||||
(0, react_1.useEffect)(() => {
|
||||
isMounted.current = true;
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
Object.values(requests.current).forEach((controller) => {
|
||||
if (controller) {
|
||||
controller.abort();
|
||||
}
|
||||
});
|
||||
requests.current = {};
|
||||
setWaveformMap({});
|
||||
if (audioUtils) {
|
||||
audioUtils.input.dispose();
|
||||
}
|
||||
};
|
||||
}, [audioUtils]);
|
||||
const { delayRender, continueRender } = (0, remotion_1.useDelayRender)();
|
||||
const fetchMetadata = (0, react_1.useCallback)(async (signal) => {
|
||||
const handle = delayRender(`Waiting for audio metadata with src="${src}" to be loaded`);
|
||||
const cont = () => {
|
||||
continueRender(handle);
|
||||
};
|
||||
signal.addEventListener('abort', cont, { once: true });
|
||||
const input = new mediabunny_1.Input({
|
||||
formats: mediabunny_1.ALL_FORMATS,
|
||||
source: new mediabunny_1.UrlSource(src),
|
||||
});
|
||||
const onAbort = () => {
|
||||
input.dispose();
|
||||
};
|
||||
signal.addEventListener('abort', onAbort, { once: true });
|
||||
try {
|
||||
const durationInSeconds = await input.computeDuration();
|
||||
const audioTrack = await input.getPrimaryAudioTrack();
|
||||
if (!audioTrack) {
|
||||
throw new Error('No audio track found');
|
||||
}
|
||||
const canDecode = await audioTrack.canDecode();
|
||||
if (!canDecode) {
|
||||
throw new Error('Audio track cannot be decoded');
|
||||
}
|
||||
if (channelIndex >= audioTrack.numberOfChannels || channelIndex < 0) {
|
||||
throw new Error(`Invalid channel index ${channelIndex} for audio with ${audioTrack.numberOfChannels} channels`);
|
||||
}
|
||||
const { numberOfChannels, sampleRate } = audioTrack;
|
||||
const format = await input.getFormat();
|
||||
const isMatroska = format === mediabunny_1.MATROSKA || format === mediabunny_1.WEBM;
|
||||
if (isMounted.current) {
|
||||
setAudioUtils({
|
||||
input,
|
||||
track: audioTrack,
|
||||
metadata: {
|
||||
durationInSeconds,
|
||||
numberOfChannels,
|
||||
sampleRate,
|
||||
},
|
||||
isMatroska,
|
||||
});
|
||||
}
|
||||
continueRender(handle);
|
||||
}
|
||||
catch (err) {
|
||||
(0, remotion_1.cancelRender)(err);
|
||||
}
|
||||
finally {
|
||||
signal.removeEventListener('abort', cont);
|
||||
signal.removeEventListener('abort', onAbort);
|
||||
}
|
||||
}, [src, delayRender, continueRender, channelIndex]);
|
||||
(0, react_1.useLayoutEffect)(() => {
|
||||
const controller = new AbortController();
|
||||
fetchMetadata(controller.signal);
|
||||
return () => {
|
||||
controller.abort();
|
||||
};
|
||||
}, [fetchMetadata]);
|
||||
const currentTime = frame / fps;
|
||||
const currentWindowIndex = Math.floor(currentTime / windowInSeconds);
|
||||
const windowsToFetch = (0, react_1.useMemo)(() => {
|
||||
if (!(audioUtils === null || audioUtils === void 0 ? void 0 : audioUtils.metadata)) {
|
||||
return [];
|
||||
}
|
||||
const maxWindowIndex = Math.floor(
|
||||
// If an audio is exactly divisible by windowInSeconds, we need to
|
||||
// subtract 0.000000000001 to avoid fetching an extra window.
|
||||
audioUtils.metadata.durationInSeconds / windowInSeconds - 0.000000000001);
|
||||
// needs to be in order because we rely on the concatenation below
|
||||
return [
|
||||
currentWindowIndex === 0 ? null : currentWindowIndex - 1,
|
||||
currentWindowIndex,
|
||||
currentWindowIndex + 1 > maxWindowIndex ? null : currentWindowIndex + 1,
|
||||
]
|
||||
.filter((i) => i !== null)
|
||||
.filter((i) => i >= 0);
|
||||
}, [currentWindowIndex, audioUtils, windowInSeconds]);
|
||||
const fetchAndSetWaveformData = (0, react_1.useCallback)(async (windowIndex) => {
|
||||
if (!(audioUtils === null || audioUtils === void 0 ? void 0 : audioUtils.metadata) || !audioUtils) {
|
||||
throw new Error('MediaBunny context is not loaded yet');
|
||||
}
|
||||
// Cancel any existing request for this window, we don't want to over-fetch
|
||||
const existingController = requests.current[windowIndex];
|
||||
if (existingController) {
|
||||
existingController.abort();
|
||||
}
|
||||
const controller = new AbortController();
|
||||
requests.current[windowIndex] = controller;
|
||||
if (controller.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
const fromSeconds = windowIndex * windowInSeconds;
|
||||
const toSeconds = (windowIndex + 1) * windowInSeconds;
|
||||
// if both fromSeconds and toSeconds are outside of the audio duration, skip fetching
|
||||
if (fromSeconds >= audioUtils.metadata.durationInSeconds ||
|
||||
toSeconds <= 0) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const { isMatroska } = audioUtils;
|
||||
if (isMatroska && !warnedMatroska[src]) {
|
||||
warnedMatroska[src] = true;
|
||||
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.`);
|
||||
}
|
||||
const partialWaveData = await (0, get_partial_audio_data_1.getPartialAudioData)({
|
||||
track: audioUtils.track,
|
||||
fromSeconds,
|
||||
toSeconds,
|
||||
channelIndex,
|
||||
signal: controller.signal,
|
||||
isMatroska,
|
||||
});
|
||||
if (!controller.signal.aborted) {
|
||||
setWaveformMap((prev) => {
|
||||
const entries = Object.keys(prev);
|
||||
const windowsToClear = entries.filter((entry) => !windowsToFetch.includes(Number(entry)));
|
||||
return {
|
||||
...prev,
|
||||
...windowsToClear.reduce((acc, key) => {
|
||||
acc[key] = null;
|
||||
return acc;
|
||||
}, {}),
|
||||
[windowIndex]: partialWaveData,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
if (controller.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
if (err instanceof mediabunny_1.InputDisposedError) {
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
finally {
|
||||
if (requests.current[windowIndex] === controller) {
|
||||
requests.current[windowIndex] = null;
|
||||
}
|
||||
}
|
||||
}, [channelIndex, audioUtils, windowInSeconds, windowsToFetch, src]);
|
||||
(0, react_1.useEffect)(() => {
|
||||
if (!(audioUtils === null || audioUtils === void 0 ? void 0 : audioUtils.metadata)) {
|
||||
return;
|
||||
}
|
||||
const windowsToClear = Object.keys(requests.current).filter((entry) => !windowsToFetch.includes(Number(entry)));
|
||||
for (const windowIndex of windowsToClear) {
|
||||
const controller = requests.current[windowIndex];
|
||||
if (controller) {
|
||||
controller.abort();
|
||||
requests.current[windowIndex] = null;
|
||||
}
|
||||
}
|
||||
// Only fetch windows that don't already exist
|
||||
const windowsToActuallyFetch = windowsToFetch.filter((windowIndex) => !waveFormMap[windowIndex] && !requests.current[windowIndex]);
|
||||
if (windowsToActuallyFetch.length === 0) {
|
||||
return;
|
||||
}
|
||||
// Prioritize the current window where playback is at.
|
||||
// On slow connections, this ensures the most important window loads first.
|
||||
const currentWindowNeedsFetch = windowsToActuallyFetch.includes(currentWindowIndex);
|
||||
const otherWindowsToFetch = windowsToActuallyFetch.filter((w) => w !== currentWindowIndex);
|
||||
const fetchWindows = async () => {
|
||||
// First, load the current window where playback is at
|
||||
if (currentWindowNeedsFetch) {
|
||||
await fetchAndSetWaveformData(currentWindowIndex);
|
||||
}
|
||||
// Then load the surrounding windows in parallel
|
||||
if (otherWindowsToFetch.length > 0) {
|
||||
await Promise.all(otherWindowsToFetch.map((windowIndex) => {
|
||||
return fetchAndSetWaveformData(windowIndex);
|
||||
}));
|
||||
}
|
||||
};
|
||||
fetchWindows().catch((err) => {
|
||||
var _a, _b, _c, _d, _e;
|
||||
if ((_a = err.stack) === null || _a === void 0 ? void 0 : _a.includes('Cancelled')) {
|
||||
return;
|
||||
}
|
||||
if ((_c = (_b = err.stack) === null || _b === void 0 ? void 0 : _b.toLowerCase()) === null || _c === void 0 ? void 0 : _c.includes('aborted')) {
|
||||
return;
|
||||
}
|
||||
// firefox
|
||||
if ((_e = (_d = err.message) === null || _d === void 0 ? void 0 : _d.toLowerCase()) === null || _e === void 0 ? void 0 : _e.includes('aborted')) {
|
||||
return;
|
||||
}
|
||||
(0, remotion_1.cancelRender)(err);
|
||||
});
|
||||
}, [
|
||||
fetchAndSetWaveformData,
|
||||
audioUtils,
|
||||
windowsToFetch,
|
||||
waveFormMap,
|
||||
currentWindowIndex,
|
||||
]);
|
||||
// Calculate available windows for reuse
|
||||
const availableWindows = (0, react_1.useMemo)(() => {
|
||||
return windowsToFetch.filter((i) => waveFormMap[i]);
|
||||
}, [windowsToFetch, waveFormMap]);
|
||||
const currentAudioData = (0, react_1.useMemo)(() => {
|
||||
if (!(audioUtils === null || audioUtils === void 0 ? void 0 : audioUtils.metadata)) {
|
||||
return null;
|
||||
}
|
||||
if (availableWindows.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const windows = availableWindows.map((i) => waveFormMap[i]);
|
||||
const data = (0, combine_float32_arrays_1.combineFloat32Arrays)(windows);
|
||||
return {
|
||||
channelWaveforms: [data],
|
||||
durationInSeconds: audioUtils.metadata.durationInSeconds,
|
||||
isRemote: (0, is_remote_asset_1.isRemoteAsset)(src),
|
||||
numberOfChannels: 1,
|
||||
resultId: `${src}-windows-${availableWindows.join(',')}`,
|
||||
sampleRate: audioUtils.metadata.sampleRate,
|
||||
};
|
||||
}, [src, waveFormMap, audioUtils, availableWindows]);
|
||||
const isBeyondAudioDuration = audioUtils
|
||||
? currentTime >= audioUtils.metadata.durationInSeconds
|
||||
: false;
|
||||
(0, react_1.useLayoutEffect)(() => {
|
||||
if (currentAudioData) {
|
||||
return;
|
||||
}
|
||||
if (isBeyondAudioDuration) {
|
||||
return;
|
||||
}
|
||||
const handle = delayRender(`Waiting for audio data with src="${src}" to be loaded`);
|
||||
return () => {
|
||||
continueRender(handle);
|
||||
};
|
||||
}, [
|
||||
currentAudioData,
|
||||
src,
|
||||
delayRender,
|
||||
continueRender,
|
||||
isBeyondAudioDuration,
|
||||
]);
|
||||
const audioData = isBeyondAudioDuration ? null : currentAudioData;
|
||||
return {
|
||||
audioData,
|
||||
dataOffsetInSeconds: availableWindows.length > 0 ? availableWindows[0] * windowInSeconds : 0,
|
||||
};
|
||||
};
|
||||
exports.useWindowedAudioData = useWindowedAudioData;
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const validateChannel: (channel: unknown, numberOfChannels: number) => void;
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.validateChannel = void 0;
|
||||
const validateChannel = (channel, numberOfChannels) => {
|
||||
if (typeof channel !== 'number') {
|
||||
throw new TypeError(`"channel" must be a number`);
|
||||
}
|
||||
if (channel % 1 !== 0) {
|
||||
throw new TypeError(`"channel" must an integer, got ${channel}`);
|
||||
}
|
||||
if (Number.isNaN(channel)) {
|
||||
throw new TypeError(`The channel parameter is NaN.`);
|
||||
}
|
||||
if (channel < 0) {
|
||||
throw new TypeError('"channel" cannot be negative');
|
||||
}
|
||||
if (channel > numberOfChannels - 1) {
|
||||
throw new TypeError(`"channel" must be ${numberOfChannels - 1} or lower. The audio has ${numberOfChannels} channels`);
|
||||
}
|
||||
};
|
||||
exports.validateChannel = validateChannel;
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
import type { MediaUtilsAudioData } from './types';
|
||||
export type VisualizeAudioWaveformOptions = {
|
||||
audioData: MediaUtilsAudioData;
|
||||
frame: number;
|
||||
fps: number;
|
||||
windowInSeconds: number;
|
||||
numberOfSamples: number;
|
||||
channel?: number;
|
||||
dataOffsetInSeconds?: number;
|
||||
normalize?: boolean;
|
||||
};
|
||||
export declare const visualizeAudioWaveform: (parameters: VisualizeAudioWaveformOptions) => number[];
|
||||
Generated
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.visualizeAudioWaveform = void 0;
|
||||
const get_waveform_portion_1 = require("./get-waveform-portion");
|
||||
const cache = {};
|
||||
const visualizeAudioWaveformFrame = ({ audioData, frame, fps, numberOfSamples, windowInSeconds, channel, dataOffsetInSeconds, normalize = false, }) => {
|
||||
if (windowInSeconds * audioData.sampleRate < numberOfSamples) {
|
||||
throw new TypeError(windowInSeconds +
|
||||
's audiodata does not have ' +
|
||||
numberOfSamples +
|
||||
' bars. Increase windowInSeconds or decrease numberOfSamples');
|
||||
}
|
||||
const cacheKey = audioData.resultId +
|
||||
frame +
|
||||
fps +
|
||||
numberOfSamples +
|
||||
'waveform' +
|
||||
dataOffsetInSeconds;
|
||||
if (cache[cacheKey]) {
|
||||
return cache[cacheKey];
|
||||
}
|
||||
const time = frame / fps;
|
||||
const startTimeInSeconds = time - windowInSeconds / 2;
|
||||
return (0, get_waveform_portion_1.getWaveformPortion)({
|
||||
audioData,
|
||||
startTimeInSeconds,
|
||||
durationInSeconds: windowInSeconds,
|
||||
numberOfSamples,
|
||||
outputRange: 'minus-one-to-one',
|
||||
channel,
|
||||
dataOffsetInSeconds,
|
||||
normalize,
|
||||
});
|
||||
};
|
||||
const visualizeAudioWaveform = (parameters) => {
|
||||
const data = visualizeAudioWaveformFrame(parameters);
|
||||
return data.map((value) => value.amplitude);
|
||||
};
|
||||
exports.visualizeAudioWaveform = visualizeAudioWaveform;
|
||||
Generated
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
import type { OptimizeFor } from './fft/get-visualization';
|
||||
import type { MediaUtilsAudioData } from './types';
|
||||
type MandatoryVisualizeAudioOptions = {
|
||||
audioData: MediaUtilsAudioData;
|
||||
frame: number;
|
||||
fps: number;
|
||||
numberOfSamples: number;
|
||||
};
|
||||
type OptionalVisualizeAudioOptions = {
|
||||
optimizeFor: OptimizeFor;
|
||||
dataOffsetInSeconds: number;
|
||||
smoothing: boolean;
|
||||
};
|
||||
export type VisualizeAudioOptions = MandatoryVisualizeAudioOptions & Partial<OptionalVisualizeAudioOptions>;
|
||||
export declare const visualizeAudio: ({ smoothing, optimizeFor, dataOffsetInSeconds, ...parameters }: MandatoryVisualizeAudioOptions & Partial<OptionalVisualizeAudioOptions> & {}) => number[];
|
||||
export {};
|
||||
Generated
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.visualizeAudio = void 0;
|
||||
const no_react_1 = require("remotion/no-react");
|
||||
const get_visualization_1 = require("./fft/get-visualization");
|
||||
const max_value_cached_1 = require("./fft/max-value-cached");
|
||||
const cache = {};
|
||||
/**
|
||||
* @description Takes in AudioData (preferably fetched by the useAudioData() hook) and processes it in a way that makes visualizing the audio that is playing at the current frame easy.
|
||||
* @description part of @remotion/media-utils
|
||||
* @see [Documentation](https://www.remotion.dev/docs/visualize-audio)
|
||||
*/
|
||||
const visualizeAudioFrame = ({ audioData, frame, fps, numberOfSamples, optimizeFor, dataOffsetInSeconds, }) => {
|
||||
const cacheKey = audioData.resultId + frame + fps + numberOfSamples;
|
||||
if (cache[cacheKey]) {
|
||||
return cache[cacheKey];
|
||||
}
|
||||
const maxInt = (0, max_value_cached_1.getMaxPossibleMagnitude)(audioData);
|
||||
return (0, get_visualization_1.getVisualization)({
|
||||
sampleSize: numberOfSamples * 2,
|
||||
data: audioData.channelWaveforms[0],
|
||||
frame,
|
||||
fps,
|
||||
sampleRate: audioData.sampleRate,
|
||||
maxInt,
|
||||
optimizeFor,
|
||||
dataOffsetInSeconds,
|
||||
});
|
||||
};
|
||||
const visualizeAudio = ({ smoothing = true, optimizeFor = no_react_1.NoReactInternals.ENABLE_V5_BREAKING_CHANGES
|
||||
? 'speed'
|
||||
: 'accuracy', dataOffsetInSeconds = 0, ...parameters }) => {
|
||||
if (!smoothing) {
|
||||
return visualizeAudioFrame({
|
||||
...parameters,
|
||||
optimizeFor,
|
||||
dataOffsetInSeconds,
|
||||
smoothing,
|
||||
});
|
||||
}
|
||||
const toSmooth = [
|
||||
parameters.frame - 1,
|
||||
parameters.frame,
|
||||
parameters.frame + 1,
|
||||
];
|
||||
const all = toSmooth.map((s) => {
|
||||
return visualizeAudioFrame({
|
||||
...parameters,
|
||||
frame: s,
|
||||
dataOffsetInSeconds,
|
||||
optimizeFor,
|
||||
smoothing,
|
||||
});
|
||||
});
|
||||
return new Array(parameters.numberOfSamples).fill(true).map((_x, i) => {
|
||||
return (new Array(toSmooth.length)
|
||||
.fill(true)
|
||||
.map((_, j) => {
|
||||
return all[j][i];
|
||||
})
|
||||
.reduce((a, b) => a + b, 0) / toSmooth.length);
|
||||
});
|
||||
};
|
||||
exports.visualizeAudio = visualizeAudio;
|
||||
Reference in New Issue
Block a user