314 lines
13 KiB
JavaScript
314 lines
13 KiB
JavaScript
"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;
|