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
JavaScript
;
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,
});
}