Miraiclip SDK
Export

Server (Node)

The same export from Node, plus a CLI.

@miraiclip/server-export exports a project document from Node. It launches headless Chrome, loads the document into a page, and runs the same exportProject the browser uses.

npm install @miraiclip/server-export
import { exportProjectFile } from "@miraiclip/server-export";
import { readFile } from "node:fs/promises";

const doc = JSON.parse(await readFile("project.json", "utf8"));

const { filePath, bytesWritten } = await exportProjectFile(doc, {
  format: "mp4",
  quality: "high",
  out: "render/final.mp4",
  assets: { interview: "./media/interview.mp4" },
  onProgress: (p) => console.log(p.phase, p.framesDone, "/", p.totalFrames),
});

The document is what project.toJSON() returns. Server export renders built-in clip kinds only: custom clip kinds need their factories, which can't cross into the page.

Options

OptionDefaultWhat it does
formatrequired"mp4" or "webm"
quality"standard""draft", "standard", "high" or { videoBitrate }
fpsproject fpsOutput frame rate (a number)
width, heightproject sizeOutput size
rangewhole composition{ startUs, endUs }
outnoneFile path to stream into. Parent folders are created
assetsnoneAsset id → local file path
assetsDirprocess.cwd()Base folder for relative asset src values
audioChunkSeconds60Audio mix chunk length
signalnoneRejects with ServerExportAbortedError
onProgressnone{ phase, framesDone, totalFrames, audioMixedUs?, audioTotalUs?, bytesWritten? }
browsersee The browser{ executablePath?, args?, swiftshader? }

Result

With outWithout out
{ filePath, bytesWritten }. The file streamed to disk as it was encoded{ bytes }. The whole file in memory

Use out for anything long. Without it, the file crosses from the page to Node in one piece, which briefly costs about twice its size in memory. A failed streamed export deletes the partial file.

bytesWritten in progress events is only set with out. Output is written in chunks of about 16 MiB, so short exports may report 0 until the end. Use framesDone / totalFrames for completion.

Cancel

import { ServerExportAbortedError, exportProjectFile } from "@miraiclip/server-export";
import type { ProjectDocument } from "@miraiclip/core";

declare const doc: ProjectDocument;

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

try {
  await exportProjectFile(doc, { format: "webm", out: "out.webm", signal: controller.signal });
} catch (error) {
  if (!(error instanceof ServerExportAbortedError)) throw error;
}

Assets

Each asset's media must be readable on the server. Per asset, in order:

RuleExample
An assets entry for the asset idassets: { intro: "/data/intro.mp4" }
An http(s): or data: src is fetched as-issrc: "https://cdn.example.com/intro.mp4"
Any other src is a path relative to assetsDirsrc: "media/intro.mp4"

A local file that doesn't exist fails the export before the browser starts.

The browser

The package depends on playwright-core and downloads no browser. It launches the first of:

  1. browser.executablePath
  2. The MIRAICLIP_BROWSER environment variable
  3. The installed Google Chrome
BrowserMP4 (H.264 + AAC)WebM (VP9 + Opus)
Google ChromeYesYes
Chromium (including Playwright's download and most Docker images)No: fails the codec check with a clear errorYes

On a server without Chrome, npx playwright install chrome installs it.

browser optionDefaultWhat it does
executablePathnonePath to any Chrome or Chromium binary
argsnoneExtra Chrome flags, added after the defaults
swiftshadertrue on Linux, false elsewhereRender WebGL in software. Servers rarely have a GPU

Warm sessions

exportProjectFile launches a browser on every call. createExportSession keeps one browser open, so each export skips the launch. Calls on one session run one at a time, in order. A failed call doesn't break the session.

import { createExportSession } from "@miraiclip/server-export";
import type { ProjectDocument } from "@miraiclip/core";

declare const docA: ProjectDocument;
declare const docB: ProjectDocument;

const session = await createExportSession({ assetsDir: "./media" });
try {
  await session.exportFile(docA, { format: "mp4", out: "out/a.mp4" });
  await session.exportFile(docB, { format: "mp4", out: "out/b.mp4" });
} finally {
  await session.close();
}
FunctionSession optionsPer call
createExportSession(options?)assets, assetsDir, browserexportFile(doc, options): every exportProjectFile option except those three
createRenderSession(options?)assets, assetsDir, browserrenderStill(doc, { timeUs, width?, height? }): a PNG. See Stills

For many videos from one template, Batch rendering runs rows through export sessions for you.

CLI

npx miraiclip-export project.json --out final.mp4 --quality high --asset interview=./media/interview.mp4

It prints progress to stderr and <file> (<MB> MB, <seconds>s) to stdout. Ctrl-C cancels the export.

FlagDefaultWhat it does
<project.json>requiredThe project document
--out <file>, -o <file>requiredOutput path
--format mp4|webmfrom the --out extension: .webm → WebM, anything else → MP4Container
--quality draft|standard|highstandardQuality preset
--fps <n>project fpsOutput frame rate
--width <px>, --height <px>project sizeOutput size
--start <s>, --end <s>whole compositionRange in seconds. Give both or neither
--asset <id>=<path>noneMap an asset id to a local file. Repeatable
--assets-dir <dir>the project file's folderBase folder for relative asset paths
--browser <path>see The browserChrome or Chromium binary
--quietoffNo progress output

The CLI has no --help flag. An unknown flag exits with unknown flag ….

On this page