Add .gitignore to exclude all node packages and lock files

This commit is contained in:
Adolfo Reyna
2026-02-23 21:56:04 -05:00
parent faae96c9ed
commit dcc5c6c044
9747 changed files with 1555105 additions and 2 deletions
@@ -0,0 +1,7 @@
Copyright (c) 2021 JonnyBurger
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,18 @@
# @remotion/media-utils
Utilities for working with media files
[![NPM Downloads](https://img.shields.io/npm/dm/@remotion/media-utils.svg?style=flat&color=black&label=Downloads)](https://npmcharts.com/compare/@remotion/media-utils?minimal=true)
## Installation
```bash
npm install @remotion/media-utils --save-exact
```
When installing a Remotion package, make sure to align the version of all `remotion` and `@remotion/*` packages to the same version.
Remove the `^` character from the version number to use the exact version.
## Usage
See the [documentation](https://www.remotion.dev/docs/media-utils) for more information.
@@ -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;
@@ -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,
});
}
@@ -0,0 +1 @@
export declare const audioBufferToDataUrl: (buffer: AudioBuffer) => string;
@@ -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;
@@ -0,0 +1 @@
export declare const combineFloat32Arrays: (arrays: Float32Array[]) => Float32Array<ArrayBufferLike>;
@@ -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;
@@ -0,0 +1,7 @@
export type Point = {
x: number;
y: number;
};
export declare const createSmoothSvgPath: ({ points }: {
points: Point[];
}) => string;
@@ -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;
@@ -0,0 +1 @@
export declare const fetchWithCorsCatch: (src: string, init?: RequestInit) => Promise<Response>;
@@ -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;
@@ -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;
@@ -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;
@@ -0,0 +1 @@
export declare const exponent: (k: number, N: number) => [number, number];
@@ -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;
@@ -0,0 +1 @@
export declare const fftAccurate: (vector: Int16Array) => [number, number][];
@@ -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;
@@ -0,0 +1 @@
export declare const fftFast: (vector: Int16Array) => [number, number][];
@@ -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;
@@ -0,0 +1 @@
export declare const fftFreq: (fftBins: [number, number][], sampleRate: number) => number[];
@@ -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;
@@ -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[];
@@ -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;
@@ -0,0 +1 @@
export declare const fftMag: (fftBins: [number, number][]) => number[];
@@ -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;
@@ -0,0 +1,2 @@
import type { MediaUtilsAudioData } from '../types';
export declare const getMaxPossibleMagnitude: (metadata: MediaUtilsAudioData) => number;
@@ -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;
@@ -0,0 +1 @@
export declare const smoothen: (array: number[]) => number[];
@@ -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;
@@ -0,0 +1 @@
export declare const toInt16: (x: number) => number;
@@ -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;
@@ -0,0 +1,6 @@
import type { MediaUtilsAudioData } from './types';
type Options = {
sampleRate?: number;
};
export declare const getAudioData: (src: string, options?: Options) => Promise<MediaUtilsAudioData>;
export {};
@@ -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;
@@ -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>;
@@ -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;
@@ -0,0 +1,2 @@
import type { ImageDimensions } from './types';
export declare function getImageDimensions(src: string): Promise<ImageDimensions>;
@@ -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);
}
@@ -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>;
@@ -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;
@@ -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>;
@@ -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;
@@ -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[];
@@ -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;
@@ -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 };
@@ -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;
@@ -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';
@@ -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; } });
@@ -0,0 +1 @@
export declare const isRemoteAsset: (asset: string) => boolean;
@@ -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;
@@ -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;
@@ -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;
@@ -0,0 +1 @@
export declare const normalizeData: (filteredData: number[]) => number[];
@@ -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;
@@ -0,0 +1 @@
export declare const pLimit: (concurrency: number) => <Arguments extends unknown[], ReturnType>(fn: (..._arguments: Arguments) => PromiseLike<ReturnType> | ReturnType, ...args: Arguments) => Promise<ReturnType>;
@@ -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;
@@ -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;
};
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
import type { MediaUtilsAudioData } from './types';
export declare const useAudioData: (src: string) => MediaUtilsAudioData | null;
@@ -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;
@@ -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;
@@ -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;
@@ -0,0 +1 @@
export declare const validateChannel: (channel: unknown, numberOfChannels: number) => void;
@@ -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;
@@ -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[];
@@ -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;
@@ -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 {};
@@ -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;
@@ -0,0 +1,45 @@
{
"repository": {
"url": "https://github.com/remotion-dev/remotion/tree/main/packages/media-utils"
},
"name": "@remotion/media-utils",
"version": "4.0.423",
"description": "Utilities for working with media files",
"main": "dist/index.js",
"sideEffects": false,
"scripts": {
"formatting": "prettier --experimental-cli src --check",
"lint": "eslint src",
"make": "tsc -d"
},
"author": "Jonny Burger <jonny@remotion.dev>",
"license": "MIT",
"bugs": {
"url": "https://github.com/remotion-dev/remotion/issues"
},
"dependencies": {
"@remotion/media-parser": "4.0.423",
"@remotion/webcodecs": "4.0.423",
"remotion": "4.0.423",
"mediabunny": "1.34.2"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
},
"devDependencies": {
"@remotion/eslint-config-internal": "4.0.423",
"eslint": "9.19.0"
},
"keywords": [
"remotion",
"ffmpeg",
"video",
"react",
"player"
],
"publishConfig": {
"access": "public"
},
"homepage": "https://www.remotion.dev/docs/media-utils"
}