Files
flatrender/server/nexrender-job-builder.ts
T

174 lines
3.9 KiB
TypeScript

import type { RenderScene, RenderSettings } from "../src/lib/render-schemas";
import { RESOLUTION_DIMENSIONS } from "../src/lib/render-schemas";
export interface NexrenderAsset {
type: string;
layerName?: string;
composition?: string;
property?: string;
value?: string | number;
src?: string;
width?: number;
height?: number;
time?: number;
[key: string]: unknown;
}
export interface NexrenderJob {
template: {
src: string;
composition: string;
frameStart?: number;
frameEnd?: number;
};
assets: NexrenderAsset[];
actions?: {
postrender?: Array<Record<string, unknown>>;
};
onRenderProgress?: string;
metadata?: Record<string, unknown>;
}
function layerToAsset(
layer: RenderScene["layers"][number],
scene: RenderScene,
sceneIndex: number
): NexrenderAsset | null {
const time = sceneIndex * scene.duration;
switch (layer.type) {
case "text": {
const text =
typeof layer.props.text === "string" ? layer.props.text : "Text";
return {
type: "data",
layerName: `Scene${sceneIndex + 1}_Text`,
composition: scene.name,
property: "Source Text",
value: text,
time,
};
}
case "image": {
const src = typeof layer.props.src === "string" ? layer.props.src : "";
if (!src) return null;
return {
type: "image",
layerName: `Scene${sceneIndex + 1}_Image`,
composition: scene.name,
src,
width: layer.width,
height: layer.height,
time,
};
}
case "video": {
const src = typeof layer.props.src === "string" ? layer.props.src : "";
if (!src) return null;
return {
type: "video",
layerName: `Scene${sceneIndex + 1}_Video`,
composition: scene.name,
src,
time,
};
}
case "shape":
return {
type: "data",
layerName: `Scene${sceneIndex + 1}_Shape`,
composition: scene.name,
property: "Opacity",
value: Math.round(layer.opacity * 100),
time,
};
default:
return null;
}
}
export function buildNexrenderJob(
scenes: RenderScene[],
settings: RenderSettings,
jobId: string,
outputPath: string
): NexrenderJob {
const templateSrc =
process.env.NEXRENDER_TEMPLATE_SRC ??
"file:///templates/creatorstudio-base.aep";
const composition =
process.env.NEXRENDER_COMPOSITION ?? "CreatorStudio_Main";
const { width, height } = RESOLUTION_DIMENSIONS[settings.resolution];
const totalDuration = scenes.reduce((sum, scene) => sum + scene.duration, 0);
const frameEnd = Math.ceil(totalDuration * settings.fps);
const assets: NexrenderAsset[] = [
{
type: "data",
layerName: "Settings",
composition,
property: "Width",
value: width,
},
{
type: "data",
layerName: "Settings",
composition,
property: "Height",
value: height,
},
{
type: "data",
layerName: "Settings",
composition,
property: "Frame Rate",
value: settings.fps,
},
];
scenes.forEach((scene, sceneIndex) => {
assets.push({
type: "data",
layerName: `Scene${sceneIndex + 1}`,
composition,
property: "Duration",
value: scene.duration,
time: sceneIndex * scene.duration,
});
const sortedLayers = [...scene.layers].sort(
(a, b) => a.zIndex - b.zIndex
);
sortedLayers.forEach((layer) => {
const asset = layerToAsset(layer, scene, sceneIndex);
if (asset) assets.push(asset);
});
});
return {
template: {
src: templateSrc,
composition,
frameStart: 0,
frameEnd,
},
assets,
actions: {
postrender: [
{
module: "@nexrender/action-copy",
output: outputPath,
},
],
},
metadata: {
jobId,
resolution: settings.resolution,
fps: settings.fps,
format: settings.format,
},
};
}