Miraiclip SDK
Templates

Batch rendering

Many videos from CSV or NDJSON rows.

renderTemplateBatch hydrates the template once per data row and exports each result to a file. It runs in Node and uses server export: one headless Chrome per worker, kept open across rows.

npm install @miraiclip/templates @miraiclip/server-export
import { parseTemplate, rowsFromCsv } from "@miraiclip/templates";
import { renderTemplateBatch } from "@miraiclip/templates/render";
import { readFile } from "node:fs/promises";

const template = parseTemplate(JSON.parse(await readFile("promo.miraiclip-template.json", "utf8")));
const rows = rowsFromCsv(template, await readFile("leads.csv", "utf8"));

const { rows: results, failed } = await renderTemplateBatch(template, rows, {
  out: "out/{index}-{name}.mp4",
  format: "mp4",
  quality: "high",
  assetsDir: "./media",
  concurrency: 2,
  onProgress: ({ rowsDone, totalRows, row }) =>
    console.log(`[${rowsDone}/${totalRows}]`, row.ok ? row.filePath : row.error),
});

if (failed.length > 0) process.exitCode = 1;

@miraiclip/templates/render is a separate entry point, so browser bundles that import @miraiclip/templates never load Playwright. @miraiclip/server-export is a peer dependency.

Options

OptionDefaultWhat it does
outrequiredOutput path per row: a pattern or a function (below)
formatrequired"mp4" or "webm"
quality, fps, width, height, range, audioChunkSecondsas in server exportApplied to every row
assets, assetsDir, browseras in server exportWhere media comes from; which Chrome to launch
concurrency1Browsers working the rows in parallel. Never more than the number of rows
stopOnErrorfalseStop starting new rows after the first failure
onProgressnoneCalled after each row with { rowsDone, totalRows, row }
signalnoneAbortSignal. Stops the batch; see Cancel

Output paths

A string pattern substitutes {field} and {index}:

PlaceholderBecomes
{index}The row's position, starting at 0
{field}That field's value from the row data. Characters / \ : * ? " < > | and whitespace become -
A field the row doesn't set, or an object value (asset { src })Left as-is, e.g. {accent}

Defaults aren't used in paths, and rows can map to the same path. Include {index} to keep names unique. For full control, pass a function:

import { renderTemplateBatch } from "@miraiclip/templates/render";
import type { Template, TemplateData } from "@miraiclip/templates";

declare const template: Template;
declare const rows: TemplateData[];

await renderTemplateBatch(template, rows, {
  format: "webm",
  out: (data, index) => `out/${String(index).padStart(4, "0")}-${String(data["name"]).toLowerCase()}.webm`,
});

Parent folders are created.

Results

The promise resolves with every row, in input order, plus the failed ones:

FieldMeaning
rowsOne BatchRow per input row
failedThe rows with ok: false. Empty means every row rendered
BatchRow fieldSet whenMeaning
index, data, okalwaysPosition, the input row, success
filePath, bytesWrittenokThe file written and its size
issuesthe data was invalid[{ path, message }] from tryHydrate
error!okOne line: invalid data: data.name: required field is missing, the export error, or not attempted (stopOnError) / not attempted (aborted)

A row with invalid data is never rendered. A failed row doesn't stop the others unless stopOnError is set. With stopOnError, rows already running still finish.

Cancel

import { renderTemplateBatch } from "@miraiclip/templates/render";
import type { Template, TemplateData } from "@miraiclip/templates";

declare const template: Template;
declare const rows: TemplateData[];

const controller = new AbortController();
process.on("SIGINT", () => controller.abort());

const { failed } = await renderTemplateBatch(template, rows, {
  format: "mp4",
  out: "out/{index}.mp4",
  signal: controller.signal,
});
// aborted rows are in `failed`; the promise still resolves

Aborting cancels the exports in progress and leaves the remaining rows unattempted. The browsers are closed when the batch ends, whether it succeeds, fails or is cancelled.

Notes

TopicDetail
MP4Needs Google Chrome (H.264). Chromium renders WebM only. See The browser
concurrencyEach unit launches its own Chrome, so memory use grows with it
Asset fieldsA row's asset value is resolved like any src: an http(s): URL, or a path under assetsDir
Your own loopcreateExportSession from @miraiclip/server-export gives you the same warm browser, if you need custom scheduling

From the command line: CLI.

On this page