UNPKG

@remotion/renderer

Version:

Render Remotion videos using Node.js or Bun

136 lines (135 loc) 5.46 kB
"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 incorrectContentLengthToken = 'Download finished with'; const downloadFileWithoutRetries = ({ onProgress, url, to: toFn }) => { return new Promise((resolve, reject) => { let rejected = false; let resolved = false; let timeout; const resolveAndFlag = (val) => { resolved = true; resolve(val); if (timeout) { clearTimeout(timeout); } }; const rejectAndFlag = (err) => { 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; (0, read_file_1.readFile)(url) .then(({ response, request }) => { var _a, _b; closeConnection = () => { request.destroy(); response.destroy(); }; 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 === '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;