"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.cancelJob = exports.removeJob = exports.addJob = exports.getRenderQueue = void 0; const studio_server_1 = require("@remotion/studio-server"); const node_path_1 = __importDefault(require("node:path")); const chalk_1 = require("../chalk"); const log_1 = require("../log"); const print_error_1 = require("../print-error"); const progress_types_1 = require("../progress-types"); const process_still_1 = require("./process-still"); const process_video_1 = require("./process-video"); let jobQueue = []; const updateJob = (id, updater) => { jobQueue = jobQueue.map((j) => { if (id === j.id) { return updater(j); } return j; }); notifyClientsOfJobUpdate(); }; const getRenderQueue = () => { return jobQueue.map((j) => { const { cleanup, ...rest } = j; return rest; }); }; exports.getRenderQueue = getRenderQueue; const notifyClientsOfJobUpdate = () => { studio_server_1.StudioServerInternals.waitForLiveEventsListener().then((listener) => { listener.sendEventToClient({ type: 'render-queue-updated', queue: (0, exports.getRenderQueue)(), }); }); }; const processJob = async ({ job, remotionRoot, entryPoint, onProgress, addCleanupCallback, logLevel, }) => { if (job.type === 'still') { await (0, process_still_1.processStill)({ job, remotionRoot, entryPoint, onProgress, addCleanupCallback, }); return; } if (job.type === 'video' || job.type === 'sequence') { await (0, process_video_1.processVideoJob)({ job, remotionRoot, entryPoint, onProgress, addCleanupCallback, logLevel, }); return; } throw new Error(`Unknown job ${JSON.stringify(job)}`); }; const addJob = ({ job, entryPoint, remotionRoot, logLevel, }) => { jobQueue.push(job); processJobIfPossible({ entryPoint, remotionRoot, logLevel }); notifyClientsOfJobUpdate(); }; exports.addJob = addJob; const removeJob = (jobId) => { jobQueue = jobQueue.filter((job) => { if (job.id === jobId) { job.cleanup.forEach((c) => { c(); }); return false; } return true; }); notifyClientsOfJobUpdate(); }; exports.removeJob = removeJob; const cancelJob = (jobId) => { for (const job of jobQueue) { if (job.id === jobId) { if (job.status !== 'running') { throw new Error('Job is not running'); } job.cancelToken.cancel(); break; } } }; exports.cancelJob = cancelJob; const processJobIfPossible = async ({ remotionRoot, entryPoint, logLevel, }) => { const runningJob = jobQueue.find((q) => { return q.status === 'running'; }); if (runningJob) { return; } const nextJob = jobQueue.find((q) => { return q.status === 'idle'; }); if (!nextJob) { return; } const jobCleanups = []; try { updateJob(nextJob.id, (job) => { return { ...job, status: 'running', progress: { value: 0, message: 'Starting job...', ...(0, progress_types_1.initialAggregateRenderProgress)(), }, }; }); const startTime = Date.now(); log_1.Log.info({ indent: false, logLevel }, chalk_1.chalk.gray('╭─ Starting render ')); let lastProgress = null; await processJob({ job: nextJob, entryPoint, remotionRoot, onProgress: (progress) => { updateJob(nextJob.id, (job) => { lastProgress = progress; // Ignore late callbacks of progress updates after cancelling if (job.status === 'failed' || job.status === 'done') { return job; } if (job.type === 'still') { return { ...job, status: 'running', progress, }; } if (job.type === 'video') { return { ...job, status: 'running', progress, }; } if (job.type === 'sequence') { return { ...job, status: 'running', progress, }; } throw new Error('Unknown job type'); }); }, addCleanupCallback: (label, cleanup) => { jobCleanups.push({ label, job: cleanup }); }, logLevel, }); log_1.Log.info({ indent: false, logLevel }, chalk_1.chalk.gray('╰─ Done in ' + (Date.now() - startTime) + 'ms.')); const { unwatch } = studio_server_1.StudioServerInternals.installFileWatcher({ file: node_path_1.default.resolve(remotionRoot, nextJob.outName), onChange: (type) => { if (type === 'created') { updateJob(nextJob.id, (job) => ({ ...job, deletedOutputLocation: false, })); } if (type === 'deleted') { updateJob(nextJob.id, (job) => ({ ...job, deletedOutputLocation: true, })); } }, }); updateJob(nextJob.id, (job) => { if (!lastProgress) { throw new Error('expected progress'); } return { ...job, status: 'done', cleanup: [...job.cleanup, unwatch], progress: { message: 'Done', value: 1, ...lastProgress }, }; }); } catch (err) { log_1.Log.error({ indent: false, logLevel }, chalk_1.chalk.gray('\n╰─ '), chalk_1.chalk.red('Failed to render')); updateJob(nextJob.id, (job) => { return { ...job, status: 'failed', error: { message: err.message, stack: err.stack, }, }; }); await (0, print_error_1.printError)(err, logLevel); studio_server_1.StudioServerInternals.waitForLiveEventsListener().then((listener) => { listener.sendEventToClient({ type: 'render-job-failed', compositionId: nextJob.compositionId, error: err, }); }); } finally { await Promise.all(jobCleanups.map(({ job, label }) => { log_1.Log.verbose({ indent: false, logLevel }, 'Cleanup: ' + label); return job(); })); } processJobIfPossible({ remotionRoot, entryPoint, logLevel }); };