UNPKG

clumsy-graphics

Version:

a tool for rapidly developing animations where frames are described using svg elements à la react 🙃

136 lines (135 loc) 5.86 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.renderAnimationModule = void 0; const child_process_1 = __importDefault(require("child_process")); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const worker_threads_1 = require("worker_threads"); const decodeData_1 = require("../helpers/decodeData"); const getAnimationModule_1 = require("../helpers/getAnimationModule"); const getAnimationModuleBundle_1 = require("../helpers/getAnimationModuleBundle"); const FrameRendererWorkerMessage_1 = require("./models/FrameRendererWorkerMessage"); async function renderAnimationModule(api) { const { animationModulePath, animationMp4OutputPath, numberOfFrameRendererWorkers, suppressWorkerStdout, } = api; const { animationModuleBundle } = await (0, getAnimationModuleBundle_1.getAnimationModuleBundle)({ animationModulePath, }); const animationModule = await (0, getAnimationModule_1.getAnimationModule)({ animationModuleBundle, }); const { tempFramesDirectoryPath } = getTempFramesDirectoryPath({ animationModule, }); try { setupTempFramesDirectory({ tempFramesDirectoryPath, }); await renderAnimationFrames({ numberOfFrameRendererWorkers, suppressWorkerStdout, tempFramesDirectoryPath, animationModuleBundle, animationModule, }); composeAnimationMp4({ animationMp4OutputPath, frameRate: animationModule.animationSettings.frameRate, constantRateFactor: animationModule.animationSettings.constantRateFactor, framePathPattern: `${tempFramesDirectoryPath}/${animationModule.moduleName}_%d.png`, }); } finally { cleanupTempFramesDirectory({ tempFramesDirectoryPath, }); } } exports.renderAnimationModule = renderAnimationModule; function getTempFramesDirectoryPath(api) { const { animationModule } = api; const tempFramesDirectoryPath = path_1.default.resolve(__dirname, `./${animationModule.moduleName}-frames_${Math.ceil(Math.random() * 10000)}`); return { tempFramesDirectoryPath }; } function setupTempFramesDirectory(api) { const { tempFramesDirectoryPath } = api; fs_1.default.mkdirSync(tempFramesDirectoryPath, { recursive: true, }); } async function renderAnimationFrames(api) { const { numberOfFrameRendererWorkers, animationModuleBundle, suppressWorkerStdout, animationModule, tempFramesDirectoryPath, } = api; console.log('rendering frames...'); await new Promise((resolve, reject) => { let framesRenderedCount = 0; let nextFrameIndex = 0; new Array(numberOfFrameRendererWorkers) .fill(undefined) .map(() => new worker_threads_1.Worker(path_1.default.resolve(__dirname, './frameRendererWorker.js'), { workerData: { animationModuleBundle, }, stdout: suppressWorkerStdout, })) .forEach((someFrameRendererWorker) => { someFrameRendererWorker.on('message', async (someFrameRendererWorkerMessageData) => { const someFrameRendererWorkerMessage = await (0, decodeData_1.decodeData)({ targetCodec: FrameRendererWorkerMessage_1.FrameRendererWorkerMessageCodec, inputData: someFrameRendererWorkerMessageData, }); switch (someFrameRendererWorkerMessage.messageType) { // @ts-expect-error case 'workerRenderedFrame': framesRenderedCount = framesRenderedCount + 1; case 'workerInitialized': if (nextFrameIndex < animationModule.frameCount) { const renderAnimationFrameMessage = { messageType: 'renderAnimationFrame', messagePayload: { frameIndex: nextFrameIndex, framePngOutputPath: path_1.default.join(tempFramesDirectoryPath, `./${animationModule.moduleName}_${nextFrameIndex}.png`), }, }; someFrameRendererWorker.postMessage(renderAnimationFrameMessage); nextFrameIndex = nextFrameIndex + 1; } else { someFrameRendererWorker.terminate(); } if (framesRenderedCount === animationModule.frameCount) { resolve(); } break; case 'workerRenderError': reject(someFrameRendererWorkerMessage.messagePayload.renderError); break; } }); }); }); } function composeAnimationMp4(api) { const { frameRate, framePathPattern, constantRateFactor, animationMp4OutputPath, } = api; console.log('encoding animation...'); child_process_1.default.execSync(` ffmpeg \ -y \ -f image2 \ -framerate ${frameRate} \ -i ${framePathPattern} \ -codec libx264 \ -preset veryslow \ -crf ${constantRateFactor} \ -pix_fmt yuv420p \ ${path_1.default.resolve(animationMp4OutputPath)} `); } function cleanupTempFramesDirectory(api) { const { tempFramesDirectoryPath } = api; fs_1.default.rmSync(tempFramesDirectoryPath, { force: true, recursive: true, }); }