Add .gitignore to exclude all node packages and lock files
This commit is contained in:
Generated
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
import type { CancelSignal } from '../make-cancel-signal';
|
||||
export declare const applyToneFrequencyUsingFfmpeg: ({ input, output, toneFrequency, indent, logLevel, binariesDirectory, cancelSignal, }: {
|
||||
input: string;
|
||||
output: string;
|
||||
toneFrequency: number;
|
||||
indent: boolean;
|
||||
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
|
||||
binariesDirectory: string | null;
|
||||
cancelSignal: CancelSignal | undefined;
|
||||
}) => Promise<void>;
|
||||
Generated
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.applyToneFrequencyUsingFfmpeg = void 0;
|
||||
const call_ffmpeg_1 = require("../call-ffmpeg");
|
||||
const logger_1 = require("../logger");
|
||||
const sample_rate_1 = require("../sample-rate");
|
||||
const applyToneFrequencyUsingFfmpeg = async ({ input, output, toneFrequency, indent, logLevel, binariesDirectory, cancelSignal, }) => {
|
||||
const filter = `asetrate=${sample_rate_1.DEFAULT_SAMPLE_RATE}*${toneFrequency},aresample=${sample_rate_1.DEFAULT_SAMPLE_RATE},atempo=1/${toneFrequency}`;
|
||||
const args = [
|
||||
'-hide_banner',
|
||||
'-i',
|
||||
input,
|
||||
['-ac', '2'],
|
||||
'-filter:a',
|
||||
filter,
|
||||
['-c:a', 'pcm_s16le'],
|
||||
['-ar', String(sample_rate_1.DEFAULT_SAMPLE_RATE)],
|
||||
'-y',
|
||||
output,
|
||||
].flat(2);
|
||||
logger_1.Log.verbose({ indent, logLevel }, 'Changing tone frequency using FFmpeg:', JSON.stringify(args.join(' ')), 'Filter:', filter);
|
||||
const startTimestamp = Date.now();
|
||||
const task = (0, call_ffmpeg_1.callFf)({
|
||||
bin: 'ffmpeg',
|
||||
args,
|
||||
indent,
|
||||
logLevel,
|
||||
binariesDirectory,
|
||||
cancelSignal,
|
||||
});
|
||||
await task;
|
||||
logger_1.Log.verbose({ indent, logLevel }, 'Changed tone frequency using FFmpeg', `${Date.now() - startTimestamp}ms`);
|
||||
};
|
||||
exports.applyToneFrequencyUsingFfmpeg = applyToneFrequencyUsingFfmpeg;
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
import type { AudioOrVideoAsset } from 'remotion/no-react';
|
||||
import type { Assets } from './types';
|
||||
export declare const calculateAssetPositions: (frames: AudioOrVideoAsset[][]) => Assets;
|
||||
Generated
Vendored
+72
@@ -0,0 +1,72 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.calculateAssetPositions = void 0;
|
||||
const filter_asset_types_1 = require("../filter-asset-types");
|
||||
const resolve_asset_src_1 = require("../resolve-asset-src");
|
||||
const flatten_volume_array_1 = require("./flatten-volume-array");
|
||||
const types_1 = require("./types");
|
||||
const areEqual = (a, b) => {
|
||||
return a.id === b.id;
|
||||
};
|
||||
const findFrom = (target, renderAsset) => {
|
||||
const index = target.findIndex((a) => areEqual(a, renderAsset));
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
target.splice(index, 1);
|
||||
return true;
|
||||
};
|
||||
const copyAndDeduplicateAssets = (renderAssets) => {
|
||||
const onlyAudioAndVideo = (0, filter_asset_types_1.onlyAudioAndVideoAssets)(renderAssets);
|
||||
const deduplicated = [];
|
||||
for (const renderAsset of onlyAudioAndVideo) {
|
||||
if (!deduplicated.find((d) => d.id === renderAsset.id)) {
|
||||
deduplicated.push(renderAsset);
|
||||
}
|
||||
}
|
||||
return deduplicated;
|
||||
};
|
||||
const calculateAssetPositions = (frames) => {
|
||||
var _a, _b;
|
||||
const assets = [];
|
||||
const flattened = frames.flat(1);
|
||||
for (let frame = 0; frame < frames.length; frame++) {
|
||||
const prev = copyAndDeduplicateAssets((_a = frames[frame - 1]) !== null && _a !== void 0 ? _a : []);
|
||||
const current = copyAndDeduplicateAssets(frames[frame]);
|
||||
const next = copyAndDeduplicateAssets((_b = frames[frame + 1]) !== null && _b !== void 0 ? _b : []);
|
||||
for (const asset of current) {
|
||||
if (!findFrom(prev, asset)) {
|
||||
assets.push({
|
||||
src: (0, resolve_asset_src_1.resolveAssetSrc)((0, types_1.uncompressMediaAsset)(flattened, asset).src),
|
||||
type: asset.type,
|
||||
duration: null,
|
||||
id: asset.id,
|
||||
startInVideo: frame,
|
||||
trimLeft: asset.mediaFrame,
|
||||
volume: [],
|
||||
playbackRate: asset.playbackRate,
|
||||
toneFrequency: asset.toneFrequency,
|
||||
audioStartFrame: asset.audioStartFrame,
|
||||
audioStreamIndex: asset.audioStreamIndex,
|
||||
});
|
||||
}
|
||||
const found = assets.find((a) => a.duration === null && areEqual(a, asset));
|
||||
if (!found)
|
||||
throw new Error('something wrong');
|
||||
if (!findFrom(next, asset)) {
|
||||
// Duration calculation:
|
||||
// start 0, range 0-59:
|
||||
// 59 - 0 + 1 ==> 60 frames duration
|
||||
found.duration = frame - found.startInVideo + 1;
|
||||
}
|
||||
found.volume = [...found.volume, asset.volume];
|
||||
}
|
||||
}
|
||||
for (const asset of assets) {
|
||||
if (asset.duration === null) {
|
||||
throw new Error('duration is unexpectedly null');
|
||||
}
|
||||
}
|
||||
return assets.map((a) => (0, flatten_volume_array_1.convertAssetToFlattenedVolume)(a));
|
||||
};
|
||||
exports.calculateAssetPositions = calculateAssetPositions;
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const calculateATempo: (playbackRate: number) => string | null;
|
||||
Generated
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
// Solving this problem of FFMPEG:
|
||||
// https://trac.ffmpeg.org/wiki/How%20to%20speed%20up%20/%20slow%20down%20a%20video
|
||||
// "The atempo filter is limited to using values between 0.5 and 2.0 (so it can slow it down to no less than half the original speed, and speed up to no more than double the input). If you need to, you can get around this limitation by stringing multiple atempo filters together. The following with quadruple the audio speed:"
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.calculateATempo = void 0;
|
||||
const calculateATempo = (playbackRate) => {
|
||||
if (playbackRate === 1) {
|
||||
return null;
|
||||
}
|
||||
if (playbackRate >= 0.5 && playbackRate <= 2) {
|
||||
return `atempo=${playbackRate.toFixed(5)}`;
|
||||
}
|
||||
return `${(0, exports.calculateATempo)(Math.sqrt(playbackRate))},${(0, exports.calculateATempo)(Math.sqrt(playbackRate))}`;
|
||||
};
|
||||
exports.calculateATempo = calculateATempo;
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
import type { AudioOrVideoAsset } from 'remotion/no-react';
|
||||
import type { FrameAndAssets } from '../render-frames';
|
||||
import type { RenderMediaOnDownload } from './download-and-map-assets-to-file';
|
||||
import type { DownloadMap } from './download-map';
|
||||
export declare const convertAssetsToFileUrls: ({ assets, onDownload, downloadMap, indent, logLevel, binariesDirectory, }: {
|
||||
assets: FrameAndAssets[];
|
||||
onDownload: RenderMediaOnDownload;
|
||||
downloadMap: DownloadMap;
|
||||
indent: boolean;
|
||||
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
|
||||
binariesDirectory: string | null;
|
||||
}) => Promise<AudioOrVideoAsset[][]>;
|
||||
Generated
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.convertAssetsToFileUrls = void 0;
|
||||
const download_and_map_assets_to_file_1 = require("./download-and-map-assets-to-file");
|
||||
const chunk = (input, size) => {
|
||||
return input.reduce((arr, item, idx) => {
|
||||
return idx % size === 0
|
||||
? [...arr, [item]]
|
||||
: [...arr.slice(0, -1), [...arr.slice(-1)[0], item]];
|
||||
}, []);
|
||||
};
|
||||
const convertAssetsToFileUrls = async ({ assets, onDownload, downloadMap, indent, logLevel, binariesDirectory, }) => {
|
||||
const chunks = chunk(assets, 1000);
|
||||
const results = [];
|
||||
for (const ch of chunks) {
|
||||
const assetPromises = ch.map((frame) => {
|
||||
const frameAssetPromises = frame.audioAndVideoAssets.map((a) => {
|
||||
return (0, download_and_map_assets_to_file_1.downloadAndMapAssetsToFileUrl)({
|
||||
renderAsset: a,
|
||||
onDownload,
|
||||
downloadMap,
|
||||
indent,
|
||||
logLevel,
|
||||
binariesDirectory,
|
||||
cancelSignalForAudioAnalysis: undefined,
|
||||
shouldAnalyzeAudioImmediately: true,
|
||||
});
|
||||
});
|
||||
return Promise.all(frameAssetPromises);
|
||||
});
|
||||
const result = await Promise.all(assetPromises);
|
||||
results.push(result);
|
||||
}
|
||||
return results.flat(1);
|
||||
};
|
||||
exports.convertAssetsToFileUrls = convertAssetsToFileUrls;
|
||||
Generated
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
import type { AudioOrVideoAsset } from 'remotion/no-react';
|
||||
import type { CancelSignal } from '../make-cancel-signal';
|
||||
import type { DownloadMap } from './download-map';
|
||||
export type RenderMediaOnDownload = (src: string) => ((progress: {
|
||||
percent: number | null;
|
||||
downloaded: number;
|
||||
totalSize: number | null;
|
||||
}) => void) | undefined | void;
|
||||
export declare const downloadAsset: ({ src, downloadMap, indent, logLevel, shouldAnalyzeAudioImmediately, binariesDirectory, cancelSignalForAudioAnalysis, audioStreamIndex, }: {
|
||||
src: string;
|
||||
downloadMap: DownloadMap;
|
||||
indent: boolean;
|
||||
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
|
||||
shouldAnalyzeAudioImmediately: boolean;
|
||||
binariesDirectory: string | null;
|
||||
cancelSignalForAudioAnalysis: CancelSignal | undefined;
|
||||
audioStreamIndex: number | undefined;
|
||||
}) => Promise<string>;
|
||||
export declare const markAllAssetsAsDownloaded: (downloadMap: DownloadMap) => void;
|
||||
export declare const getSanitizedFilenameForAssetUrl: ({ src, downloadDir, contentDisposition, contentType, }: {
|
||||
src: string;
|
||||
downloadDir: string;
|
||||
contentDisposition: string | null;
|
||||
contentType: string | null;
|
||||
}) => string;
|
||||
export declare const downloadAndMapAssetsToFileUrl: ({ renderAsset, onDownload, downloadMap, logLevel, indent, binariesDirectory, cancelSignalForAudioAnalysis, shouldAnalyzeAudioImmediately, }: {
|
||||
renderAsset: AudioOrVideoAsset;
|
||||
onDownload: RenderMediaOnDownload | null;
|
||||
downloadMap: DownloadMap;
|
||||
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
|
||||
indent: boolean;
|
||||
shouldAnalyzeAudioImmediately: boolean;
|
||||
binariesDirectory: string | null;
|
||||
cancelSignalForAudioAnalysis: CancelSignal | undefined;
|
||||
}) => Promise<AudioOrVideoAsset>;
|
||||
export declare const attachDownloadListenerToEmitter: (downloadMap: DownloadMap, onDownload: RenderMediaOnDownload | null) => () => void;
|
||||
Generated
Vendored
+313
@@ -0,0 +1,313 @@
|
||||
"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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.attachDownloadListenerToEmitter = exports.downloadAndMapAssetsToFileUrl = exports.getSanitizedFilenameForAssetUrl = exports.markAllAssetsAsDownloaded = exports.downloadAsset = void 0;
|
||||
const node_fs_1 = __importDefault(require("node:fs"));
|
||||
const node_path_1 = __importStar(require("node:path"));
|
||||
const no_react_1 = require("remotion/no-react");
|
||||
const compress_assets_1 = require("../compress-assets");
|
||||
const ensure_output_directory_1 = require("../ensure-output-directory");
|
||||
const mime_types_1 = require("../mime-types");
|
||||
const download_file_1 = require("./download-file");
|
||||
const get_audio_channels_1 = require("./get-audio-channels");
|
||||
const sanitize_filepath_1 = require("./sanitize-filepath");
|
||||
const waitForAssetToBeDownloaded = ({ src, downloadDir, downloadMap, }) => {
|
||||
var _a, _b;
|
||||
if ((_a = downloadMap.hasBeenDownloadedMap[src]) === null || _a === void 0 ? void 0 : _a[downloadDir]) {
|
||||
return Promise.resolve((_b = downloadMap.hasBeenDownloadedMap[src]) === null || _b === void 0 ? void 0 : _b[downloadDir]);
|
||||
}
|
||||
if (!downloadMap.listeners[src]) {
|
||||
downloadMap.listeners[src] = {};
|
||||
}
|
||||
if (!downloadMap.listeners[src][downloadDir]) {
|
||||
downloadMap.listeners[src][downloadDir] = [];
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
downloadMap.listeners[src][downloadDir].push(() => {
|
||||
const srcMap = downloadMap.hasBeenDownloadedMap[src];
|
||||
if (!(srcMap === null || srcMap === void 0 ? void 0 : srcMap[downloadDir])) {
|
||||
throw new Error('Expected file for ' + src + 'to be available in ' + downloadDir);
|
||||
}
|
||||
resolve(srcMap[downloadDir]);
|
||||
});
|
||||
});
|
||||
};
|
||||
const notifyAssetIsDownloaded = ({ src, downloadDir, to, downloadMap, }) => {
|
||||
if (!downloadMap.listeners[src]) {
|
||||
downloadMap.listeners[src] = {};
|
||||
}
|
||||
if (!downloadMap.listeners[src][downloadDir]) {
|
||||
downloadMap.listeners[src][downloadDir] = [];
|
||||
}
|
||||
if (!downloadMap.isDownloadingMap[src]) {
|
||||
downloadMap.isDownloadingMap[src] = {};
|
||||
}
|
||||
downloadMap.isDownloadingMap[src][downloadDir] = false;
|
||||
if (!downloadMap.hasBeenDownloadedMap[src]) {
|
||||
downloadMap.hasBeenDownloadedMap[src] = {};
|
||||
}
|
||||
downloadMap.hasBeenDownloadedMap[src][downloadDir] = to;
|
||||
downloadMap.listeners[src][downloadDir].forEach((fn) => fn());
|
||||
};
|
||||
const validateMimeType = (mimeType, src) => {
|
||||
if (!mimeType.includes('/')) {
|
||||
const errMessage = [
|
||||
'A data URL was passed but did not have the correct format so that Remotion could convert it for the video to be rendered.',
|
||||
'The format of the data URL must be `data:[mime-type];[encoding],[data]`.',
|
||||
'The `mime-type` parameter must be a valid mime type.',
|
||||
'The data that was received is (truncated to 100 characters):',
|
||||
src.substr(0, 100),
|
||||
].join(' ');
|
||||
throw new TypeError(errMessage);
|
||||
}
|
||||
};
|
||||
function validateBufferEncoding(potentialEncoding, dataUrl) {
|
||||
const asserted = potentialEncoding;
|
||||
const validEncodings = [
|
||||
'ascii',
|
||||
'base64',
|
||||
'base64url',
|
||||
'binary',
|
||||
'hex',
|
||||
'latin1',
|
||||
'ucs-2',
|
||||
'ucs2',
|
||||
'utf-8',
|
||||
'utf16le',
|
||||
'utf8',
|
||||
];
|
||||
if (!validEncodings.find((en) => asserted === en)) {
|
||||
const errMessage = [
|
||||
'A data URL was passed but did not have the correct format so that Remotion could convert it for the video to be rendered.',
|
||||
'The format of the data URL must be `data:[mime-type];[encoding],[data]`.',
|
||||
'The `encoding` parameter must be one of the following:',
|
||||
`${validEncodings.join(' ')}.`,
|
||||
'The data that was received is (truncated to 100 characters):',
|
||||
dataUrl.substr(0, 100),
|
||||
].join(' ');
|
||||
throw new TypeError(errMessage);
|
||||
}
|
||||
}
|
||||
const downloadAsset = async ({ src, downloadMap, indent, logLevel, shouldAnalyzeAudioImmediately, binariesDirectory, cancelSignalForAudioAnalysis, audioStreamIndex, }) => {
|
||||
var _a, _b, _c;
|
||||
if ((0, compress_assets_1.isAssetCompressed)(src)) {
|
||||
return src;
|
||||
}
|
||||
const { downloadDir } = downloadMap;
|
||||
if ((_a = downloadMap.hasBeenDownloadedMap[src]) === null || _a === void 0 ? void 0 : _a[downloadDir]) {
|
||||
const claimedDownloadLocation = (_b = downloadMap.hasBeenDownloadedMap[src]) === null || _b === void 0 ? void 0 : _b[downloadDir];
|
||||
// The OS might have deleted the file since even though we marked it as downloaded. In that case we reset the state and download it again
|
||||
if (node_fs_1.default.existsSync(claimedDownloadLocation)) {
|
||||
return claimedDownloadLocation;
|
||||
}
|
||||
downloadMap.hasBeenDownloadedMap[src][downloadDir] = null;
|
||||
if (!downloadMap.isDownloadingMap[src]) {
|
||||
downloadMap.isDownloadingMap[src] = {};
|
||||
}
|
||||
downloadMap.isDownloadingMap[src][downloadDir] = false;
|
||||
}
|
||||
if ((_c = downloadMap.isDownloadingMap[src]) === null || _c === void 0 ? void 0 : _c[downloadDir]) {
|
||||
return waitForAssetToBeDownloaded({ downloadMap, src, downloadDir });
|
||||
}
|
||||
if (!downloadMap.isDownloadingMap[src]) {
|
||||
downloadMap.isDownloadingMap[src] = {};
|
||||
}
|
||||
downloadMap.isDownloadingMap[src][downloadDir] = true;
|
||||
downloadMap.emitter.dispatchDownload(src);
|
||||
if (src.startsWith('data:')) {
|
||||
const [assetDetails, assetData] = src.substring('data:'.length).split(',');
|
||||
if (!assetDetails.includes(';')) {
|
||||
const errMessage = [
|
||||
'A data URL was passed but did not have the correct format so that Remotion could convert it for the video to be rendered.',
|
||||
'The format of the data URL must be `data:[mime-type];[encoding],[data]`.',
|
||||
'The data that was received is (truncated to 100 characters):',
|
||||
src.substring(0, 100),
|
||||
].join(' ');
|
||||
throw new TypeError(errMessage);
|
||||
}
|
||||
const [mimeType, encoding] = assetDetails.split(';');
|
||||
validateMimeType(mimeType, src);
|
||||
validateBufferEncoding(encoding, src);
|
||||
const output = (0, exports.getSanitizedFilenameForAssetUrl)({
|
||||
contentDisposition: null,
|
||||
downloadDir,
|
||||
src,
|
||||
contentType: mimeType,
|
||||
});
|
||||
(0, ensure_output_directory_1.ensureOutputDirectory)(output);
|
||||
const buff = Buffer.from(assetData, encoding);
|
||||
await node_fs_1.default.promises.writeFile(output, buff);
|
||||
notifyAssetIsDownloaded({ src, downloadMap, downloadDir, to: output });
|
||||
return output;
|
||||
}
|
||||
const { to } = await (0, download_file_1.downloadFile)({
|
||||
url: src,
|
||||
onProgress: (progress) => {
|
||||
downloadMap.emitter.dispatchDownloadProgress(src, progress.percent, progress.downloaded, progress.totalSize);
|
||||
},
|
||||
to: (contentDisposition, contentType) => (0, exports.getSanitizedFilenameForAssetUrl)({
|
||||
contentDisposition,
|
||||
downloadDir,
|
||||
src,
|
||||
contentType,
|
||||
}),
|
||||
indent,
|
||||
logLevel,
|
||||
abortSignal: downloadMap.cleanupController.signal,
|
||||
});
|
||||
notifyAssetIsDownloaded({ src, downloadMap, downloadDir, to });
|
||||
if (shouldAnalyzeAudioImmediately) {
|
||||
await (0, get_audio_channels_1.getAudioChannelsAndDuration)({
|
||||
binariesDirectory,
|
||||
downloadMap,
|
||||
src: to,
|
||||
indent,
|
||||
logLevel,
|
||||
cancelSignal: cancelSignalForAudioAnalysis,
|
||||
audioStreamIndex,
|
||||
});
|
||||
}
|
||||
return to;
|
||||
};
|
||||
exports.downloadAsset = downloadAsset;
|
||||
const markAllAssetsAsDownloaded = (downloadMap) => {
|
||||
Object.keys(downloadMap.hasBeenDownloadedMap).forEach((key) => {
|
||||
delete downloadMap.hasBeenDownloadedMap[key];
|
||||
});
|
||||
Object.keys(downloadMap.isDownloadingMap).forEach((key) => {
|
||||
delete downloadMap.isDownloadingMap[key];
|
||||
});
|
||||
};
|
||||
exports.markAllAssetsAsDownloaded = markAllAssetsAsDownloaded;
|
||||
const getFilename = ({ contentDisposition, src, contentType, }) => {
|
||||
const filenameProbe = 'filename=';
|
||||
if (contentDisposition === null || contentDisposition === void 0 ? void 0 : contentDisposition.includes(filenameProbe)) {
|
||||
const start = contentDisposition.indexOf(filenameProbe);
|
||||
const onlyFromFileName = contentDisposition.substring(start + filenameProbe.length);
|
||||
const hasSemi = onlyFromFileName.indexOf(';');
|
||||
if (hasSemi === -1) {
|
||||
return { pathname: onlyFromFileName.trim(), search: '' };
|
||||
}
|
||||
return {
|
||||
search: '',
|
||||
pathname: onlyFromFileName.substring(0, hasSemi).trim(),
|
||||
};
|
||||
}
|
||||
const { pathname, search } = new URL(src);
|
||||
const ext = (0, node_path_1.extname)(pathname);
|
||||
// Has no file extension, check if we can derive it from contentType
|
||||
if (!ext && contentType) {
|
||||
const matchedExt = (0, mime_types_1.getExt)(contentType);
|
||||
return {
|
||||
pathname: `${pathname}.${matchedExt}`,
|
||||
search,
|
||||
};
|
||||
}
|
||||
return { pathname, search };
|
||||
};
|
||||
const getSanitizedFilenameForAssetUrl = ({ src, downloadDir, contentDisposition, contentType, }) => {
|
||||
if ((0, compress_assets_1.isAssetCompressed)(src)) {
|
||||
return src;
|
||||
}
|
||||
const { pathname, search } = getFilename({
|
||||
contentDisposition,
|
||||
contentType,
|
||||
src,
|
||||
});
|
||||
const split = pathname.split('.');
|
||||
const fileExtension = split.length > 1 && split[split.length - 1]
|
||||
? `.${split[split.length - 1]}`
|
||||
: '';
|
||||
const hashedFileName = String((0, no_react_1.random)(`${src}${pathname}${search}`)).replace('0.', '');
|
||||
const filename = hashedFileName + fileExtension;
|
||||
return node_path_1.default.join(downloadDir, (0, sanitize_filepath_1.sanitizeFilePath)(filename));
|
||||
};
|
||||
exports.getSanitizedFilenameForAssetUrl = getSanitizedFilenameForAssetUrl;
|
||||
const downloadAndMapAssetsToFileUrl = async ({ renderAsset, onDownload, downloadMap, logLevel, indent, binariesDirectory, cancelSignalForAudioAnalysis, shouldAnalyzeAudioImmediately, }) => {
|
||||
const cleanup = (0, exports.attachDownloadListenerToEmitter)(downloadMap, onDownload);
|
||||
const newSrc = await (0, exports.downloadAsset)({
|
||||
src: renderAsset.src,
|
||||
downloadMap,
|
||||
indent,
|
||||
logLevel,
|
||||
shouldAnalyzeAudioImmediately,
|
||||
binariesDirectory,
|
||||
cancelSignalForAudioAnalysis,
|
||||
audioStreamIndex: renderAsset.audioStreamIndex,
|
||||
});
|
||||
cleanup();
|
||||
return {
|
||||
...renderAsset,
|
||||
src: newSrc,
|
||||
};
|
||||
};
|
||||
exports.downloadAndMapAssetsToFileUrl = downloadAndMapAssetsToFileUrl;
|
||||
const attachDownloadListenerToEmitter = (downloadMap, onDownload) => {
|
||||
const cleanup = [];
|
||||
if (!onDownload) {
|
||||
return () => undefined;
|
||||
}
|
||||
if (downloadMap.downloadListeners.includes(onDownload)) {
|
||||
return () => undefined;
|
||||
}
|
||||
downloadMap.downloadListeners.push(onDownload);
|
||||
cleanup.push(() => {
|
||||
downloadMap.downloadListeners = downloadMap.downloadListeners.filter((l) => l !== onDownload);
|
||||
return Promise.resolve();
|
||||
});
|
||||
const cleanupDownloadListener = downloadMap.emitter.addEventListener('download', ({ detail: { src: initialSrc } }) => {
|
||||
const progress = onDownload(initialSrc);
|
||||
const cleanupProgressListener = downloadMap.emitter.addEventListener('progress', ({ detail: { downloaded, percent, src: progressSrc, totalSize } }) => {
|
||||
if (initialSrc === progressSrc) {
|
||||
progress === null || progress === void 0 ? void 0 : progress({ downloaded, percent, totalSize });
|
||||
}
|
||||
});
|
||||
cleanup.push(() => {
|
||||
cleanupProgressListener();
|
||||
return Promise.resolve();
|
||||
});
|
||||
});
|
||||
cleanup.push(() => {
|
||||
cleanupDownloadListener();
|
||||
return Promise.resolve();
|
||||
});
|
||||
return () => {
|
||||
cleanup.forEach((c) => c());
|
||||
};
|
||||
};
|
||||
exports.attachDownloadListenerToEmitter = attachDownloadListenerToEmitter;
|
||||
Generated
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
import type { LogLevel } from '../log-level';
|
||||
type Response = {
|
||||
sizeInBytes: number;
|
||||
to: string;
|
||||
};
|
||||
type Options = {
|
||||
url: string;
|
||||
to: (contentDisposition: string | null, contentType: string | null) => string;
|
||||
onProgress: ((progress: {
|
||||
percent: number | null;
|
||||
downloaded: number;
|
||||
totalSize: number | null;
|
||||
}) => void) | undefined;
|
||||
logLevel: LogLevel;
|
||||
indent: boolean;
|
||||
abortSignal: AbortSignal;
|
||||
};
|
||||
export declare const downloadFile: (options: Options, retries?: number, attempt?: number) => Promise<Response>;
|
||||
export {};
|
||||
Generated
Vendored
+150
@@ -0,0 +1,150 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.downloadFile = void 0;
|
||||
const node_fs_1 = require("node:fs");
|
||||
const ensure_output_directory_1 = require("../ensure-output-directory");
|
||||
const logger_1 = require("../logger");
|
||||
const read_file_1 = require("./read-file");
|
||||
const CANCELLED_ERROR = 'cancelled';
|
||||
const incorrectContentLengthToken = 'Download finished with';
|
||||
const downloadFileWithoutRetries = ({ onProgress, url, to: toFn, abortSignal, }) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let rejected = false;
|
||||
let resolved = false;
|
||||
let timeout;
|
||||
const resolveAndFlag = (val) => {
|
||||
abortSignal.removeEventListener('abort', onAbort);
|
||||
resolved = true;
|
||||
resolve(val);
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
};
|
||||
const rejectAndFlag = (err) => {
|
||||
abortSignal.removeEventListener('abort', onAbort);
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
reject(err);
|
||||
rejected = true;
|
||||
};
|
||||
const refreshTimeout = () => {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
timeout = setTimeout(() => {
|
||||
if (resolved) {
|
||||
return;
|
||||
}
|
||||
rejectAndFlag(new Error(`Tried to download file ${url}, but the server sent no data for 20 seconds`));
|
||||
}, 20000);
|
||||
};
|
||||
refreshTimeout();
|
||||
let finishEventSent = false;
|
||||
let closeConnection = () => undefined;
|
||||
const onAbort = () => {
|
||||
rejectAndFlag(new Error(CANCELLED_ERROR));
|
||||
closeConnection();
|
||||
};
|
||||
abortSignal.addEventListener('abort', onAbort);
|
||||
(0, read_file_1.readFile)(url)
|
||||
.then(({ response, request }) => {
|
||||
var _a, _b;
|
||||
closeConnection = () => {
|
||||
request.destroy();
|
||||
response.destroy();
|
||||
};
|
||||
if (abortSignal.aborted) {
|
||||
onAbort();
|
||||
return;
|
||||
}
|
||||
const contentDisposition = (_a = response.headers['content-disposition']) !== null && _a !== void 0 ? _a : null;
|
||||
const contentType = (_b = response.headers['content-type']) !== null && _b !== void 0 ? _b : null;
|
||||
const to = toFn(contentDisposition, contentType);
|
||||
(0, ensure_output_directory_1.ensureOutputDirectory)(to);
|
||||
const sizeHeader = response.headers['content-length'];
|
||||
const totalSize = typeof sizeHeader === 'undefined' ? null : Number(sizeHeader);
|
||||
const writeStream = (0, node_fs_1.createWriteStream)(to);
|
||||
let downloaded = 0;
|
||||
// Listen to 'close' event instead of more
|
||||
// concise method to avoid this problem
|
||||
// https://github.com/remotion-dev/remotion/issues/384#issuecomment-844398183
|
||||
writeStream.on('close', () => {
|
||||
if (rejected) {
|
||||
return;
|
||||
}
|
||||
if (!finishEventSent) {
|
||||
onProgress === null || onProgress === void 0 ? void 0 : onProgress({
|
||||
downloaded,
|
||||
percent: 1,
|
||||
totalSize: downloaded,
|
||||
});
|
||||
}
|
||||
refreshTimeout();
|
||||
return resolveAndFlag({ sizeInBytes: downloaded, to });
|
||||
});
|
||||
writeStream.on('error', (err) => rejectAndFlag(err));
|
||||
response.on('error', (err) => {
|
||||
closeConnection();
|
||||
rejectAndFlag(err);
|
||||
});
|
||||
response.pipe(writeStream).on('error', (err) => rejectAndFlag(err));
|
||||
response.on('data', (d) => {
|
||||
refreshTimeout();
|
||||
downloaded += d.length;
|
||||
refreshTimeout();
|
||||
const percent = totalSize === null ? null : downloaded / totalSize;
|
||||
onProgress === null || onProgress === void 0 ? void 0 : onProgress({
|
||||
downloaded,
|
||||
percent,
|
||||
totalSize,
|
||||
});
|
||||
if (percent === 1) {
|
||||
finishEventSent = true;
|
||||
}
|
||||
});
|
||||
response.on('close', () => {
|
||||
if (totalSize !== null && downloaded !== totalSize) {
|
||||
rejectAndFlag(new Error(`${incorrectContentLengthToken} ${downloaded} bytes, but expected ${totalSize} bytes from 'Content-Length'.`));
|
||||
}
|
||||
writeStream.close();
|
||||
closeConnection();
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
rejectAndFlag(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
const downloadFile = async (options, retries = 2, attempt = 1) => {
|
||||
try {
|
||||
const res = await downloadFileWithoutRetries(options);
|
||||
return res;
|
||||
}
|
||||
catch (err) {
|
||||
const { message } = err;
|
||||
if (message === CANCELLED_ERROR) {
|
||||
throw err;
|
||||
}
|
||||
if (message === 'aborted' ||
|
||||
message.includes('ECONNRESET') ||
|
||||
message.includes(incorrectContentLengthToken) ||
|
||||
// Try again if hitting internal errors
|
||||
message.includes('503') ||
|
||||
message.includes('502') ||
|
||||
message.includes('504') ||
|
||||
message.includes('500')) {
|
||||
if (retries === 0) {
|
||||
throw err;
|
||||
}
|
||||
logger_1.Log.warn({ indent: options.indent, logLevel: options.logLevel }, `Downloading ${options.url} failed (will retry): ${message}`);
|
||||
const backoffInSeconds = (attempt + 1) ** 2;
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(() => resolve(), backoffInSeconds * 1000);
|
||||
});
|
||||
return (0, exports.downloadFile)(options, retries - 1, attempt + 1);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
exports.downloadFile = downloadFile;
|
||||
Generated
Vendored
+56
@@ -0,0 +1,56 @@
|
||||
import { OffthreadVideoServerEmitter } from '../offthread-video-server';
|
||||
import type { FrameAndAssets } from '../render-frames';
|
||||
import type { RenderMediaOnDownload } from './download-and-map-assets-to-file';
|
||||
import { type InlineAudioMixing } from './inline-audio-mixing';
|
||||
export type AudioChannelsAndDurationResultCache = {
|
||||
channels: number;
|
||||
duration: number | null;
|
||||
startTime: number | null;
|
||||
};
|
||||
export type DownloadMap = {
|
||||
id: string;
|
||||
emitter: OffthreadVideoServerEmitter;
|
||||
downloadListeners: RenderMediaOnDownload[];
|
||||
isDownloadingMap: {
|
||||
[src: string]: {
|
||||
[downloadDir: string]: boolean;
|
||||
} | undefined;
|
||||
};
|
||||
hasBeenDownloadedMap: {
|
||||
[src: string]: {
|
||||
[downloadDir: string]: string | null;
|
||||
} | undefined;
|
||||
};
|
||||
listeners: {
|
||||
[key: string]: {
|
||||
[downloadDir: string]: (() => void)[];
|
||||
};
|
||||
};
|
||||
durationOfAssetCache: Record<string, AudioChannelsAndDurationResultCache>;
|
||||
downloadDir: string;
|
||||
preEncode: string;
|
||||
audioMixing: string;
|
||||
complexFilter: string;
|
||||
audioPreprocessing: string;
|
||||
stitchFrames: string;
|
||||
assetDir: string;
|
||||
compositingDir: string;
|
||||
preventCleanup: () => void;
|
||||
allowCleanup: () => void;
|
||||
isPreventedFromCleanup: () => boolean;
|
||||
inlineAudioMixing: InlineAudioMixing;
|
||||
cleanupController: AbortController;
|
||||
};
|
||||
export type RenderAssetInfo = {
|
||||
assets: FrameAndAssets[];
|
||||
imageSequenceName: string;
|
||||
firstFrameIndex: number;
|
||||
downloadMap: DownloadMap;
|
||||
chunkLengthInSeconds: number;
|
||||
trimLeftOffset: number;
|
||||
trimRightOffset: number;
|
||||
forSeamlessAacConcatenation: boolean;
|
||||
};
|
||||
export declare const makeAndReturn: (dir: string, name: string) => string;
|
||||
export declare const makeDownloadMap: () => DownloadMap;
|
||||
export declare const cleanDownloadMap: (downloadMap: DownloadMap) => void;
|
||||
Generated
Vendored
+65
@@ -0,0 +1,65 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.cleanDownloadMap = exports.makeDownloadMap = exports.makeAndReturn = void 0;
|
||||
const node_fs_1 = require("node:fs");
|
||||
const node_path_1 = __importDefault(require("node:path"));
|
||||
const version_1 = require("remotion/version");
|
||||
const delete_directory_1 = require("../delete-directory");
|
||||
const offthread_video_server_1 = require("../offthread-video-server");
|
||||
const tmp_dir_1 = require("../tmp-dir");
|
||||
const inline_audio_mixing_1 = require("./inline-audio-mixing");
|
||||
const makeAndReturn = (dir, name) => {
|
||||
const p = node_path_1.default.join(dir, name);
|
||||
(0, node_fs_1.mkdirSync)(p);
|
||||
return p;
|
||||
};
|
||||
exports.makeAndReturn = makeAndReturn;
|
||||
const makeDownloadMap = () => {
|
||||
const dir = (0, tmp_dir_1.tmpDir)(`remotion-v${version_1.VERSION}-assets`);
|
||||
let prevented = false;
|
||||
return {
|
||||
isDownloadingMap: {},
|
||||
hasBeenDownloadedMap: {},
|
||||
listeners: {},
|
||||
durationOfAssetCache: {},
|
||||
id: String(Math.random()),
|
||||
assetDir: dir,
|
||||
downloadListeners: [],
|
||||
downloadDir: (0, exports.makeAndReturn)(dir, 'remotion-assets-dir'),
|
||||
complexFilter: (0, exports.makeAndReturn)(dir, 'remotion-complex-filter'),
|
||||
preEncode: (0, exports.makeAndReturn)(dir, 'pre-encode'),
|
||||
audioMixing: (0, exports.makeAndReturn)(dir, 'remotion-audio-mixing'),
|
||||
audioPreprocessing: (0, exports.makeAndReturn)(dir, 'remotion-audio-preprocessing'),
|
||||
stitchFrames: (0, exports.makeAndReturn)(dir, 'remotion-stitch-temp-dir'),
|
||||
compositingDir: (0, exports.makeAndReturn)(dir, 'remotion-compositing-temp-dir'),
|
||||
emitter: new offthread_video_server_1.OffthreadVideoServerEmitter(),
|
||||
preventCleanup: () => {
|
||||
prevented = true;
|
||||
},
|
||||
allowCleanup: () => {
|
||||
prevented = false;
|
||||
},
|
||||
isPreventedFromCleanup: () => {
|
||||
return prevented;
|
||||
},
|
||||
inlineAudioMixing: (0, inline_audio_mixing_1.makeInlineAudioMixing)(dir),
|
||||
cleanupController: new AbortController(),
|
||||
};
|
||||
};
|
||||
exports.makeDownloadMap = makeDownloadMap;
|
||||
const cleanDownloadMap = (downloadMap) => {
|
||||
if (downloadMap.isPreventedFromCleanup()) {
|
||||
return;
|
||||
}
|
||||
downloadMap.cleanupController.abort();
|
||||
(0, delete_directory_1.deleteDirectory)(downloadMap.downloadDir);
|
||||
(0, delete_directory_1.deleteDirectory)(downloadMap.complexFilter);
|
||||
(0, delete_directory_1.deleteDirectory)(downloadMap.compositingDir);
|
||||
downloadMap.inlineAudioMixing.cleanup();
|
||||
// Assets dir must be last since the others are contained
|
||||
(0, delete_directory_1.deleteDirectory)(downloadMap.assetDir);
|
||||
};
|
||||
exports.cleanDownloadMap = cleanDownloadMap;
|
||||
Generated
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
import type { AssetVolume } from './types';
|
||||
type FfmpegEval = 'once' | 'frame';
|
||||
type FfmpegVolumeExpression = {
|
||||
eval: FfmpegEval;
|
||||
value: string;
|
||||
};
|
||||
export declare const ffmpegVolumeExpression: ({ volume, fps, trimLeft, }: {
|
||||
volume: AssetVolume;
|
||||
trimLeft: number;
|
||||
fps: number;
|
||||
}) => FfmpegVolumeExpression;
|
||||
export {};
|
||||
skills/remotion-prompt-video/node_modules/@remotion/renderer/dist/assets/ffmpeg-volume-expression.js
Generated
Vendored
+96
@@ -0,0 +1,96 @@
|
||||
"use strict";
|
||||
// Example is here https://ffmpeg.org/ffmpeg-filters.html#volume
|
||||
// Allowed syntax is here: https://ffmpeg.org/ffmpeg-utils.html#Expression-Evaluation
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.ffmpegVolumeExpression = void 0;
|
||||
// If once, ffmpeg evaluates volume expression once.
|
||||
// If frame, it evaluates it for each frame
|
||||
const round_volume_to_avoid_stack_overflow_1 = require("./round-volume-to-avoid-stack-overflow");
|
||||
// In FFMPEG expressions, the current time is represented by 't'
|
||||
// the `n` for the timestamp variable is buggy
|
||||
const FFMPEG_TIME_VARIABLE = 't';
|
||||
const ffmpegIfOrElse = (condition, then, elseDo) => {
|
||||
return `if(${condition},${then},${elseDo})`;
|
||||
};
|
||||
const ffmpegIsOneOfFrames = ({ frames, trimLeft, fps, }) => {
|
||||
const consecutiveArrays = [];
|
||||
for (let i = 0; i < frames.length; i++) {
|
||||
const previousFrame = frames[i - 1];
|
||||
const frame = frames[i];
|
||||
if (previousFrame === undefined || frame !== previousFrame + 1) {
|
||||
consecutiveArrays.push([]);
|
||||
}
|
||||
consecutiveArrays[consecutiveArrays.length - 1].push(frame);
|
||||
}
|
||||
return consecutiveArrays
|
||||
.map((f) => {
|
||||
const firstFrame = f[0];
|
||||
const lastFrame = f[f.length - 1];
|
||||
const before = (firstFrame - 0.5) / fps;
|
||||
const after = (lastFrame + 0.5) / fps;
|
||||
return `between(${FFMPEG_TIME_VARIABLE},${(before + trimLeft).toFixed(4)},${(after + trimLeft).toFixed(4)})`;
|
||||
})
|
||||
.join('+');
|
||||
};
|
||||
const ffmpegBuildVolumeExpression = ({ arr, delay, fps, }) => {
|
||||
if (arr.length === 0) {
|
||||
throw new Error('Volume array expression should never have length 0');
|
||||
}
|
||||
if (arr.length === 1) {
|
||||
return String(arr[0][0]);
|
||||
}
|
||||
const [first, ...rest] = arr;
|
||||
const [volume, frames] = first;
|
||||
return ffmpegIfOrElse(ffmpegIsOneOfFrames({ frames, trimLeft: delay, fps }), String(volume), ffmpegBuildVolumeExpression({ arr: rest, delay, fps }));
|
||||
};
|
||||
const ffmpegVolumeExpression = ({ volume, fps, trimLeft, }) => {
|
||||
// If it's a static volume, we return it and tell
|
||||
// FFMPEG it only has to evaluate it once
|
||||
if (typeof volume === 'number') {
|
||||
return {
|
||||
eval: 'once',
|
||||
value: String(volume),
|
||||
};
|
||||
}
|
||||
if ([...new Set(volume)].length === 1) {
|
||||
return (0, exports.ffmpegVolumeExpression)({
|
||||
volume: volume[0],
|
||||
fps,
|
||||
trimLeft,
|
||||
});
|
||||
}
|
||||
// A 1 sec video with frames 0-29 would mean that
|
||||
// frame 29 corresponds to timestamp 0.966666...
|
||||
// but the audio is actually 1 sec long. For that reason we pad the last
|
||||
// timestamp.
|
||||
const paddedVolume = [...volume, volume[volume.length - 1]];
|
||||
// Otherwise, we construct an FFMPEG expression. First step:
|
||||
// Make a map of all possible volumes
|
||||
// {possibleVolume1} => [frame1, frame2]
|
||||
// {possibleVolume2} => [frame3, frame4]
|
||||
const volumeMap = {};
|
||||
paddedVolume.forEach((baseVolume, frame) => {
|
||||
// Adjust volume based on how many other tracks have not yet finished
|
||||
const actualVolume = (0, round_volume_to_avoid_stack_overflow_1.roundVolumeToAvoidStackOverflow)(baseVolume);
|
||||
if (!volumeMap[actualVolume]) {
|
||||
volumeMap[actualVolume] = [];
|
||||
}
|
||||
volumeMap[actualVolume].push(frame);
|
||||
});
|
||||
// Sort the map so that the most common volume is last
|
||||
// this is going to be the else statement so the expression is short
|
||||
const volumeArray = Object.keys(volumeMap)
|
||||
.map((key) => [Number(key), volumeMap[key]])
|
||||
.sort((a, b) => a[1].length - b[1].length);
|
||||
// Construct and tell FFMPEG it has to evaluate expression on each frame
|
||||
const expression = ffmpegBuildVolumeExpression({
|
||||
arr: volumeArray,
|
||||
delay: trimLeft,
|
||||
fps,
|
||||
});
|
||||
return {
|
||||
eval: 'frame',
|
||||
value: `'${expression}'`,
|
||||
};
|
||||
};
|
||||
exports.ffmpegVolumeExpression = ffmpegVolumeExpression;
|
||||
Generated
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
import type { AssetVolume, MediaAsset } from './types';
|
||||
export declare const flattenVolumeArray: (volume: AssetVolume) => AssetVolume;
|
||||
export declare const convertAssetToFlattenedVolume: (asset: MediaAsset) => MediaAsset;
|
||||
Generated
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
"use strict";
|
||||
// An input is an array with volume levels for each frame, like
|
||||
// [1, 1, 1, 1, 1, ...].
|
||||
// If all elements are the same, we flatten it to `1`, otherwise we leave it as an array.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.convertAssetToFlattenedVolume = exports.flattenVolumeArray = void 0;
|
||||
const flattenVolumeArray = (volume) => {
|
||||
if (typeof volume === 'number') {
|
||||
return volume;
|
||||
}
|
||||
if (volume.length === 0) {
|
||||
throw new TypeError('Volume array must have at least 1 number');
|
||||
}
|
||||
if (new Set(volume).size === 1) {
|
||||
return volume[0];
|
||||
}
|
||||
return volume;
|
||||
};
|
||||
exports.flattenVolumeArray = flattenVolumeArray;
|
||||
const convertAssetToFlattenedVolume = (asset) => {
|
||||
return {
|
||||
...asset,
|
||||
volume: (0, exports.flattenVolumeArray)(asset.volume),
|
||||
};
|
||||
};
|
||||
exports.convertAssetToFlattenedVolume = convertAssetToFlattenedVolume;
|
||||
Generated
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
import type { CancelSignal } from '../make-cancel-signal';
|
||||
import type { AudioChannelsAndDurationResultCache, DownloadMap } from './download-map';
|
||||
export declare const getAudioChannelsAndDurationWithoutCache: ({ src, indent, logLevel, binariesDirectory, cancelSignal, audioStreamIndex, }: {
|
||||
src: string;
|
||||
indent: boolean;
|
||||
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
|
||||
binariesDirectory: string | null;
|
||||
cancelSignal: CancelSignal | undefined;
|
||||
audioStreamIndex: number | undefined;
|
||||
}) => Promise<AudioChannelsAndDurationResultCache>;
|
||||
export declare const getAudioChannelsAndDuration: ({ downloadMap, src, indent, logLevel, binariesDirectory, cancelSignal, audioStreamIndex, }: {
|
||||
downloadMap: DownloadMap;
|
||||
src: string;
|
||||
indent: boolean;
|
||||
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
|
||||
binariesDirectory: string | null;
|
||||
cancelSignal: CancelSignal | undefined;
|
||||
audioStreamIndex: number | undefined;
|
||||
}) => Promise<AudioChannelsAndDurationResultCache>;
|
||||
Generated
Vendored
+78
@@ -0,0 +1,78 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getAudioChannelsAndDuration = exports.getAudioChannelsAndDurationWithoutCache = void 0;
|
||||
const call_ffmpeg_1 = require("../call-ffmpeg");
|
||||
const p_limit_1 = require("../p-limit");
|
||||
const limit = (0, p_limit_1.pLimit)(1);
|
||||
const getAudioChannelsAndDurationWithoutCache = async ({ src, indent, logLevel, binariesDirectory, cancelSignal, audioStreamIndex, }) => {
|
||||
const args = [
|
||||
['-v', 'error'],
|
||||
['-select_streams', audioStreamIndex ? `a:${audioStreamIndex}` : 'a:0'],
|
||||
[
|
||||
'-show_entries',
|
||||
'stream=channels:stream=start_time:format=duration:format=format_name',
|
||||
],
|
||||
['-of', 'default=nw=1'],
|
||||
[src],
|
||||
]
|
||||
.reduce((acc, val) => acc.concat(val), [])
|
||||
.filter(Boolean);
|
||||
try {
|
||||
const task = await (0, call_ffmpeg_1.callFf)({
|
||||
bin: 'ffprobe',
|
||||
args,
|
||||
indent,
|
||||
logLevel,
|
||||
binariesDirectory,
|
||||
cancelSignal,
|
||||
});
|
||||
const channels = task.stdout.match(/channels=([0-9]+)/);
|
||||
const duration = task.stdout.match(/duration=([0-9.]+)/);
|
||||
const startTime = task.stdout.match(/start_time=([0-9.]+)/);
|
||||
const container = task.stdout.match(/format_name=([a-zA-Z0-9.]+)/);
|
||||
const isMP3 = container ? container[1] === 'mp3' : false;
|
||||
const result = {
|
||||
channels: channels ? parseInt(channels[1], 10) : 0,
|
||||
duration: duration ? parseFloat(duration[1]) : null,
|
||||
// We ignore the start time for MP3 because that is an inherent encoder thing
|
||||
// not in the sense that we want
|
||||
startTime: startTime ? (isMP3 ? 0 : parseFloat(startTime[1])) : null,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
catch (err) {
|
||||
if (err.message.includes('This file cannot be read by `ffprobe`. Is it a valid multimedia file?')) {
|
||||
throw new Error('This file cannot be read by `ffprobe`. Is it a valid multimedia file?');
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
exports.getAudioChannelsAndDurationWithoutCache = getAudioChannelsAndDurationWithoutCache;
|
||||
async function getAudioChannelsAndDurationUnlimited({ downloadMap, src, indent, logLevel, binariesDirectory, cancelSignal, audioStreamIndex, }) {
|
||||
const cacheKey = audioStreamIndex ? `${src}-${audioStreamIndex}` : src;
|
||||
if (downloadMap.durationOfAssetCache[cacheKey]) {
|
||||
return downloadMap.durationOfAssetCache[cacheKey];
|
||||
}
|
||||
const result = await (0, exports.getAudioChannelsAndDurationWithoutCache)({
|
||||
src,
|
||||
indent,
|
||||
logLevel,
|
||||
binariesDirectory,
|
||||
cancelSignal,
|
||||
audioStreamIndex,
|
||||
});
|
||||
downloadMap.durationOfAssetCache[cacheKey] = result;
|
||||
return result;
|
||||
}
|
||||
const getAudioChannelsAndDuration = ({ downloadMap, src, indent, logLevel, binariesDirectory, cancelSignal, audioStreamIndex, }) => {
|
||||
return limit(() => getAudioChannelsAndDurationUnlimited({
|
||||
downloadMap,
|
||||
src,
|
||||
indent,
|
||||
logLevel,
|
||||
binariesDirectory,
|
||||
cancelSignal,
|
||||
audioStreamIndex,
|
||||
}));
|
||||
};
|
||||
exports.getAudioChannelsAndDuration = getAudioChannelsAndDuration;
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
import type { InlineAudioAsset } from 'remotion/no-react';
|
||||
import type { CancelSignal } from '../make-cancel-signal';
|
||||
export declare const makeInlineAudioMixing: (dir: string) => {
|
||||
cleanup: () => void;
|
||||
addAsset: ({ asset, fps, totalNumberOfFrames, firstFrame, trimLeftOffset, trimRightOffset, }: {
|
||||
asset: InlineAudioAsset;
|
||||
fps: number;
|
||||
totalNumberOfFrames: number;
|
||||
firstFrame: number;
|
||||
trimLeftOffset: number;
|
||||
trimRightOffset: number;
|
||||
}) => void;
|
||||
getListOfAssets: () => string[];
|
||||
finish: ({ binariesDirectory, indent, logLevel, cancelSignal, }: {
|
||||
indent: boolean;
|
||||
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
|
||||
binariesDirectory: string | null;
|
||||
cancelSignal: CancelSignal | undefined;
|
||||
}) => Promise<void>;
|
||||
};
|
||||
export type InlineAudioMixing = ReturnType<typeof makeInlineAudioMixing>;
|
||||
Generated
Vendored
+200
@@ -0,0 +1,200 @@
|
||||
"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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.makeInlineAudioMixing = void 0;
|
||||
const node_fs_1 = __importStar(require("node:fs"));
|
||||
const node_path_1 = __importDefault(require("node:path"));
|
||||
const delete_directory_1 = require("../delete-directory");
|
||||
const sample_rate_1 = require("../sample-rate");
|
||||
const apply_tone_frequency_1 = require("./apply-tone-frequency");
|
||||
const download_map_1 = require("./download-map");
|
||||
const numberTo32BiIntLittleEndian = (num) => {
|
||||
return new Uint8Array([
|
||||
num & 0xff,
|
||||
(num >> 8) & 0xff,
|
||||
(num >> 16) & 0xff,
|
||||
(num >> 24) & 0xff,
|
||||
]);
|
||||
};
|
||||
const numberTo16BitLittleEndian = (num) => {
|
||||
return new Uint8Array([num & 0xff, (num >> 8) & 0xff]);
|
||||
};
|
||||
/**
|
||||
* When multiplying seconds by sample rate, floating point errors can cause
|
||||
* results like 244799.99999999997 instead of 244800 (5.1 * 48000).
|
||||
* This snaps to the nearest integer when within tolerance,
|
||||
* preventing Math.floor/Math.ceil from rounding incorrectly.
|
||||
*/
|
||||
const correctFloatingPointError = (value) => {
|
||||
const rounded = Math.round(value);
|
||||
if (Math.abs(value - rounded) < 0.00001) {
|
||||
return rounded;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
const BIT_DEPTH = 16;
|
||||
const BYTES_PER_SAMPLE = BIT_DEPTH / 8;
|
||||
const NUMBER_OF_CHANNELS = 2;
|
||||
const makeInlineAudioMixing = (dir) => {
|
||||
const folderToAdd = (0, download_map_1.makeAndReturn)(dir, 'remotion-inline-audio-mixing');
|
||||
// asset id -> file descriptor
|
||||
const openFiles = {};
|
||||
const writtenHeaders = {};
|
||||
const toneFrequencies = {};
|
||||
const cleanup = () => {
|
||||
for (const fd of Object.values(openFiles)) {
|
||||
try {
|
||||
node_fs_1.default.closeSync(fd);
|
||||
}
|
||||
catch (_a) { }
|
||||
}
|
||||
(0, delete_directory_1.deleteDirectory)(folderToAdd);
|
||||
};
|
||||
const getListOfAssets = () => {
|
||||
return Object.keys(openFiles);
|
||||
};
|
||||
const getFilePath = (asset) => {
|
||||
return node_path_1.default.join(folderToAdd, `${asset.id}.wav`);
|
||||
};
|
||||
const ensureAsset = ({ asset, fps, totalNumberOfFrames, trimLeftOffset, trimRightOffset, }) => {
|
||||
const filePath = getFilePath(asset);
|
||||
if (!openFiles[filePath]) {
|
||||
openFiles[filePath] = node_fs_1.default.openSync(filePath, 'w');
|
||||
}
|
||||
if (writtenHeaders[filePath]) {
|
||||
return;
|
||||
}
|
||||
writtenHeaders[filePath] = true;
|
||||
const expectedDataSize = Math.round((totalNumberOfFrames / fps - trimLeftOffset + trimRightOffset) *
|
||||
NUMBER_OF_CHANNELS *
|
||||
sample_rate_1.DEFAULT_SAMPLE_RATE *
|
||||
BYTES_PER_SAMPLE);
|
||||
const expectedSize = 40 + expectedDataSize;
|
||||
const fd = openFiles[filePath];
|
||||
(0, node_fs_1.writeSync)(fd, new Uint8Array([0x52, 0x49, 0x46, 0x46]), 0, 4, 0); // "RIFF"
|
||||
(0, node_fs_1.writeSync)(fd, new Uint8Array(numberTo32BiIntLittleEndian(expectedSize)), 0, 4, 4); // Remaining size
|
||||
(0, node_fs_1.writeSync)(fd, new Uint8Array([0x57, 0x41, 0x56, 0x45]), 0, 4, 8); // "WAVE"
|
||||
(0, node_fs_1.writeSync)(fd, new Uint8Array([0x66, 0x6d, 0x74, 0x20]), 0, 4, 12); // "fmt "
|
||||
(0, node_fs_1.writeSync)(fd, new Uint8Array([BIT_DEPTH, 0x00, 0x00, 0x00]), 0, 4, 16); // fmt chunk size = 16
|
||||
(0, node_fs_1.writeSync)(fd, new Uint8Array([0x01, 0x00]), 0, 2, 20); // Audio format (PCM) = 1, set 3 if float32 would be true
|
||||
(0, node_fs_1.writeSync)(fd, new Uint8Array([NUMBER_OF_CHANNELS, 0x00]), 0, 2, 22); // Number of channels
|
||||
(0, node_fs_1.writeSync)(fd, new Uint8Array(numberTo32BiIntLittleEndian(sample_rate_1.DEFAULT_SAMPLE_RATE)), 0, 4, 24); // Sample rate
|
||||
(0, node_fs_1.writeSync)(fd, new Uint8Array(numberTo32BiIntLittleEndian(sample_rate_1.DEFAULT_SAMPLE_RATE * NUMBER_OF_CHANNELS * BYTES_PER_SAMPLE)), 0, 4, 28); // Byte rate
|
||||
(0, node_fs_1.writeSync)(fd, new Uint8Array(numberTo16BitLittleEndian(NUMBER_OF_CHANNELS * BYTES_PER_SAMPLE)), 0, 2, 32); // Block align
|
||||
(0, node_fs_1.writeSync)(fd, numberTo16BitLittleEndian(BIT_DEPTH), 0, 2, 34); // Bits per sample
|
||||
(0, node_fs_1.writeSync)(fd, new Uint8Array([0x64, 0x61, 0x74, 0x61]), 0, 4, 36); // "data"
|
||||
(0, node_fs_1.writeSync)(fd, new Uint8Array(numberTo32BiIntLittleEndian(expectedDataSize)), 0, 4, 40); // Remaining size
|
||||
};
|
||||
const finish = async ({ binariesDirectory, indent, logLevel, cancelSignal, }) => {
|
||||
for (const fd of Object.keys(openFiles)) {
|
||||
const frequency = toneFrequencies[fd];
|
||||
if (frequency !== 1) {
|
||||
const tmpFile = fd.replace(/.wav$/, '-tmp.wav');
|
||||
await (0, apply_tone_frequency_1.applyToneFrequencyUsingFfmpeg)({
|
||||
input: fd,
|
||||
output: tmpFile,
|
||||
toneFrequency: frequency,
|
||||
indent,
|
||||
logLevel,
|
||||
binariesDirectory,
|
||||
cancelSignal,
|
||||
});
|
||||
node_fs_1.default.renameSync(tmpFile, fd);
|
||||
}
|
||||
}
|
||||
};
|
||||
const addAsset = ({ asset, fps, totalNumberOfFrames, firstFrame, trimLeftOffset, trimRightOffset, }) => {
|
||||
ensureAsset({
|
||||
asset,
|
||||
fps,
|
||||
totalNumberOfFrames,
|
||||
trimLeftOffset,
|
||||
trimRightOffset,
|
||||
});
|
||||
const filePath = getFilePath(asset);
|
||||
if (toneFrequencies[filePath] !== undefined &&
|
||||
toneFrequencies[filePath] !== asset.toneFrequency) {
|
||||
throw new Error(`toneFrequency must be the same across the entire audio, got ${asset.toneFrequency}, but before it was ${toneFrequencies[filePath]}`);
|
||||
}
|
||||
const fileDescriptor = openFiles[filePath];
|
||||
toneFrequencies[filePath] = asset.toneFrequency;
|
||||
let arr = new Int16Array(asset.audio);
|
||||
const isFirst = asset.frame === firstFrame;
|
||||
const isLast = asset.frame === totalNumberOfFrames + firstFrame - 1;
|
||||
const samplesToShaveFromStart = trimLeftOffset * sample_rate_1.DEFAULT_SAMPLE_RATE;
|
||||
const samplesToShaveFromEnd = trimRightOffset * sample_rate_1.DEFAULT_SAMPLE_RATE;
|
||||
// Higher tolerance is needed for floating point videos
|
||||
// Rendering https://github.com/remotion-dev/remotion/pull/5920 in native frame rate
|
||||
// could hit this case
|
||||
if (isFirst) {
|
||||
arr = arr.slice(Math.floor(correctFloatingPointError(samplesToShaveFromStart)) *
|
||||
NUMBER_OF_CHANNELS);
|
||||
}
|
||||
if (isLast) {
|
||||
arr = arr.slice(0, arr.length +
|
||||
Math.ceil(correctFloatingPointError(samplesToShaveFromEnd)) *
|
||||
NUMBER_OF_CHANNELS);
|
||||
}
|
||||
const positionInSeconds = (asset.frame - firstFrame) / fps - (isFirst ? 0 : trimLeftOffset);
|
||||
// Always rounding down to ensure there are no gaps when the samples don't align
|
||||
// In @remotion/media, we also round down the sample start timestamp and round up the end timestamp
|
||||
// This might lead to overlapping, hopefully aligning perfectly!
|
||||
// Test case: https://github.com/remotion-dev/remotion/issues/5758
|
||||
const position = Math.floor(correctFloatingPointError(positionInSeconds * sample_rate_1.DEFAULT_SAMPLE_RATE)) *
|
||||
NUMBER_OF_CHANNELS *
|
||||
BYTES_PER_SAMPLE;
|
||||
(0, node_fs_1.writeSync)(
|
||||
// fs
|
||||
fileDescriptor,
|
||||
// data
|
||||
arr,
|
||||
// offset of data
|
||||
0,
|
||||
// length
|
||||
arr.byteLength,
|
||||
// position
|
||||
44 + position);
|
||||
};
|
||||
return {
|
||||
cleanup,
|
||||
addAsset,
|
||||
getListOfAssets,
|
||||
finish,
|
||||
};
|
||||
};
|
||||
exports.makeInlineAudioMixing = makeInlineAudioMixing;
|
||||
Generated
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
import http from 'node:http';
|
||||
type NodeRequestAndResponse = {
|
||||
request: http.ClientRequest;
|
||||
response: http.IncomingMessage;
|
||||
};
|
||||
export declare const readFile: (url: string, redirectsSoFar?: number) => Promise<NodeRequestAndResponse>;
|
||||
export {};
|
||||
Generated
Vendored
+97
@@ -0,0 +1,97 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.readFile = void 0;
|
||||
const https_1 = __importDefault(require("https"));
|
||||
const node_http_1 = __importDefault(require("node:http"));
|
||||
const redirect_status_codes_1 = require("../redirect-status-codes");
|
||||
const truthy_1 = require("../truthy");
|
||||
const getClient = (url) => {
|
||||
if (url.startsWith('https://')) {
|
||||
return https_1.default.get;
|
||||
}
|
||||
if (url.startsWith('http://')) {
|
||||
return node_http_1.default.get;
|
||||
}
|
||||
throw new Error(`Can only download URLs starting with http:// or https://, got "${url}"`);
|
||||
};
|
||||
const readFileWithoutRedirect = (url) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const client = getClient(url);
|
||||
const req = client(url,
|
||||
// Bun 1.1.16 does not support the `headers` option
|
||||
typeof Bun === 'undefined'
|
||||
? {
|
||||
headers: {
|
||||
'user-agent': 'Mozilla/5.0 (@remotion/renderer - https://remotion.dev)',
|
||||
},
|
||||
}
|
||||
: {}, (res) => {
|
||||
resolve({ request: req, response: res });
|
||||
});
|
||||
req.on('error', (err) => {
|
||||
req.destroy();
|
||||
return reject(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
const readFile = async (url, redirectsSoFar = 0) => {
|
||||
if (redirectsSoFar > 10) {
|
||||
throw new Error(`Too many redirects while downloading ${url}`);
|
||||
}
|
||||
const { request, response } = await readFileWithoutRedirect(url);
|
||||
if (redirect_status_codes_1.redirectStatusCodes.includes(response.statusCode)) {
|
||||
if (!response.headers.location) {
|
||||
throw new Error(`Received a status code ${response.statusCode} but no "Location" header while calling ${response.headers.location}`);
|
||||
}
|
||||
const { origin } = new URL(url);
|
||||
const redirectUrl = new URL(response.headers.location, origin).toString();
|
||||
request.destroy();
|
||||
response.destroy();
|
||||
return (0, exports.readFile)(redirectUrl, redirectsSoFar + 1);
|
||||
}
|
||||
if (response.statusCode >= 400) {
|
||||
const body = await tryToObtainBody(response);
|
||||
request.destroy();
|
||||
response.destroy();
|
||||
throw new Error([
|
||||
`Received a status code of ${response.statusCode} while downloading file ${url}.`,
|
||||
body ? `The response body was:` : null,
|
||||
body ? `---` : null,
|
||||
body ? body : null,
|
||||
body ? `---` : null,
|
||||
]
|
||||
.filter(truthy_1.truthy)
|
||||
.join('\n'));
|
||||
}
|
||||
return { request, response };
|
||||
};
|
||||
exports.readFile = readFile;
|
||||
const tryToObtainBody = async (file) => {
|
||||
const success = new Promise((resolve) => {
|
||||
let data = '';
|
||||
file.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
file.on('end', () => {
|
||||
resolve(data);
|
||||
});
|
||||
// OK even when getting an error, this is just a best effort
|
||||
file.on('error', () => resolve(data));
|
||||
});
|
||||
let timeout = null;
|
||||
const body = await Promise.race([
|
||||
success,
|
||||
new Promise((resolve) => {
|
||||
timeout = setTimeout(() => {
|
||||
resolve(null);
|
||||
}, 5000);
|
||||
}),
|
||||
]);
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
return body;
|
||||
};
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const roundVolumeToAvoidStackOverflow: (volume: number) => number;
|
||||
Generated
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
"use strict";
|
||||
// FFMPEG allows expressions to have a maximum depth of 100.
|
||||
// As defined here: https://github.com/FFmpeg/FFmpeg/blob/2014b0135293c41d261757bfa1aaba51653bab8e/libavutil/eval.c#L706
|
||||
// With one depth added by the if/else statement,
|
||||
// this means we can have a maximum of 99 different volumes.
|
||||
// Therefore we round the volumes (which can only be between 0 and 1)
|
||||
// so that there are only 99 possible values.
|
||||
// We then subtract 1 again because FFMPEG is not precise and queries out of range
|
||||
// values, for which we have to provide a default
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.roundVolumeToAvoidStackOverflow = void 0;
|
||||
const MAX_FFMPEG_STACK_DEPTH = 98;
|
||||
const roundVolumeToAvoidStackOverflow = (volume) => {
|
||||
return Number((Math.round(volume * (MAX_FFMPEG_STACK_DEPTH - 1)) /
|
||||
(MAX_FFMPEG_STACK_DEPTH - 1)).toFixed(3));
|
||||
};
|
||||
exports.roundVolumeToAvoidStackOverflow = roundVolumeToAvoidStackOverflow;
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const sanitizeFilename: (input: string) => string;
|
||||
Generated
Vendored
+58
@@ -0,0 +1,58 @@
|
||||
"use strict";
|
||||
// Inlined from https://github.com/parshap/node-sanitize-filename/blob/master/index.js
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.sanitizeFilename = void 0;
|
||||
const truncate_utf8_bytes_1 = require("./truncate-utf8-bytes");
|
||||
/**
|
||||
* Replaces characters in strings that are illegal/unsafe for filenames.
|
||||
* Unsafe characters are either removed or replaced by a substitute set
|
||||
* in the optional `options` object.
|
||||
*
|
||||
* Illegal Characters on Various Operating Systems
|
||||
* / ? < > \ : * | "
|
||||
* https://kb.acronis.com/content/39790
|
||||
*
|
||||
* Unicode Control codes
|
||||
* C0 0x00-0x1f & C1 (0x80-0x9f)
|
||||
* http://en.wikipedia.org/wiki/C0_and_C1_control_codes
|
||||
*
|
||||
* Reserved filenames on Unix-based systems (".", "..")
|
||||
* Reserved filenames in Windows ("CON", "PRN", "AUX", "NUL", "COM1",
|
||||
* "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
|
||||
* "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", and
|
||||
* "LPT9") case-insesitively and with or without filename extensions.
|
||||
*
|
||||
* Capped at 255 characters in length.
|
||||
* http://unix.stackexchange.com/questions/32795/what-is-the-maximum-allowed-filename-and-folder-size-with-ecryptfs
|
||||
*
|
||||
* @param {String} input Original filename
|
||||
* @param {Object} options {replacement: String | Function }
|
||||
* @return {String} Sanitized filename
|
||||
*/
|
||||
const illegalRe = /[/?<>\\:*|"]/g;
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const controlRe = /[\x00-\x1f\x80-\x9f]/g;
|
||||
const reservedRe = /^\.+$/;
|
||||
const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
||||
const windowsTrailingRe = /[. ]+$/;
|
||||
function sanitize(input, replacement) {
|
||||
if (typeof input !== 'string') {
|
||||
throw new Error('Input must be string');
|
||||
}
|
||||
const sanitized = input
|
||||
.replace(illegalRe, replacement)
|
||||
.replace(controlRe, replacement)
|
||||
.replace(reservedRe, replacement)
|
||||
.replace(windowsReservedRe, replacement)
|
||||
.replace(windowsTrailingRe, replacement);
|
||||
return (0, truncate_utf8_bytes_1.truncateUtf8Bytes)(sanitized, 255);
|
||||
}
|
||||
const sanitizeFilename = (input) => {
|
||||
const replacement = '';
|
||||
const output = sanitize(input, replacement);
|
||||
if (replacement === '') {
|
||||
return output;
|
||||
}
|
||||
return sanitize(output, '');
|
||||
};
|
||||
exports.sanitizeFilename = sanitizeFilename;
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare const sanitizeFilePath: (pathToSanitize: string) => string;
|
||||
Generated
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.sanitizeFilePath = void 0;
|
||||
const node_path_1 = __importDefault(require("node:path"));
|
||||
const sanitize_filename_1 = require("./sanitize-filename");
|
||||
const pathSeparators = /[/\\]/;
|
||||
const sanitizeFilePath = (pathToSanitize) => {
|
||||
return pathToSanitize
|
||||
.split(pathSeparators)
|
||||
.map((s) => (0, sanitize_filename_1.sanitizeFilename)(s))
|
||||
.join(node_path_1.default.sep);
|
||||
};
|
||||
exports.sanitizeFilePath = sanitizeFilePath;
|
||||
Generated
Vendored
+1
@@ -0,0 +1 @@
|
||||
export declare function truncateUtf8Bytes(string: string, byteLength: number): string;
|
||||
Generated
Vendored
+36
@@ -0,0 +1,36 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.truncateUtf8Bytes = truncateUtf8Bytes;
|
||||
function isHighSurrogate(codePoint) {
|
||||
return codePoint >= 0xd800 && codePoint <= 0xdbff;
|
||||
}
|
||||
function isLowSurrogate(codePoint) {
|
||||
return codePoint >= 0xdc00 && codePoint <= 0xdfff;
|
||||
}
|
||||
const getLength = Buffer.byteLength.bind(Buffer);
|
||||
function truncateUtf8Bytes(string, byteLength) {
|
||||
if (typeof string !== 'string') {
|
||||
throw new Error('Input must be string');
|
||||
}
|
||||
const charLength = string.length;
|
||||
let curByteLength = 0;
|
||||
let codePoint;
|
||||
let segment;
|
||||
for (let i = 0; i < charLength; i += 1) {
|
||||
codePoint = string.charCodeAt(i);
|
||||
segment = string[i];
|
||||
if (isHighSurrogate(codePoint) &&
|
||||
isLowSurrogate(string.charCodeAt(i + 1))) {
|
||||
i += 1;
|
||||
segment += string[i];
|
||||
}
|
||||
curByteLength += getLength(segment);
|
||||
if (curByteLength === byteLength) {
|
||||
return string.slice(0, i + 1);
|
||||
}
|
||||
if (curByteLength > byteLength) {
|
||||
return string.slice(0, i - segment.length + 1);
|
||||
}
|
||||
}
|
||||
return string;
|
||||
}
|
||||
Generated
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
import type { AudioOrVideoAsset } from 'remotion/no-react';
|
||||
export type UnsafeAsset = Omit<AudioOrVideoAsset, 'frame' | 'id' | 'volume' | 'mediaFrame' | 'audioStartFrom'> & {
|
||||
startInVideo: number;
|
||||
duration: number | null;
|
||||
trimLeft: number;
|
||||
volume: number[];
|
||||
id: string;
|
||||
playbackRate: number;
|
||||
toneFrequency: number;
|
||||
audioStreamIndex: number;
|
||||
};
|
||||
export type AssetVolume = number | number[];
|
||||
export type MediaAsset = Omit<UnsafeAsset, 'duration' | 'volume'> & {
|
||||
duration: number;
|
||||
volume: AssetVolume;
|
||||
};
|
||||
export declare const uncompressMediaAsset: (allRenderAssets: AudioOrVideoAsset[], assetToUncompress: AudioOrVideoAsset) => AudioOrVideoAsset;
|
||||
export type Assets = MediaAsset[];
|
||||
Generated
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.uncompressMediaAsset = void 0;
|
||||
const uncompressMediaAsset = (allRenderAssets, assetToUncompress) => {
|
||||
const isCompressed = assetToUncompress.src.match(/same-as-(.*)-([0-9.]+)$/);
|
||||
if (!isCompressed) {
|
||||
return assetToUncompress;
|
||||
}
|
||||
const [, id, frame] = isCompressed;
|
||||
const assetToFill = allRenderAssets.find((a) => a.id === id && String(a.frame) === frame);
|
||||
if (!assetToFill) {
|
||||
console.log('List of assets:');
|
||||
console.log(allRenderAssets);
|
||||
throw new TypeError(`Cannot uncompress asset, asset list seems corrupt. Please file a bug in the Remotion repo with the debug information above.`);
|
||||
}
|
||||
return {
|
||||
...assetToUncompress,
|
||||
src: assetToFill.src,
|
||||
};
|
||||
};
|
||||
exports.uncompressMediaAsset = uncompressMediaAsset;
|
||||
Reference in New Issue
Block a user