@miraiclip/core
Headless, command-driven engine for building video editors — state, commands, history, patches, events.
npm install @miraiclip/core| Version | 0.5.8 |
| Runs in | Browser, worker, Node, edge |
| Exports | 161 |
| Guides | Project & time, Commands, History & transactions |
Functions
animationCommands
The commands that give clip exactly recipe: clear its visual keyframes
(volume automation stays), then set the recipe's. Dispatch them in one
transaction for a single undo step. An empty recipe removes the animation.
function animationCommands(clip: Clip, recipe: AnimationRecipe): BuiltinCommand[]Guides: Keyframes & animation
applyCommands
Apply a batch of commands as ONE transaction: all-or-nothing (a failure rolls back every command already applied — the engine's transaction rollback), one undo step on success, and the failing index reported so an agent can fix exactly the command that broke.
function applyCommands(project: Project, commands: readonly Command[], options?: {
label?: string;
}): ApplyCommandsResultGuides: MCP server, Tools from the command catalog, Collaboration, History & transactions
applyJsonPatches
Apply RFC-6902 add/replace/remove operations (the subset this engine emits)
to a plain JSON document, returning a new document. The input is not
mutated. Use this to replay a peer's patches events onto a local copy of
the document for sync/collaboration.
function applyJsonPatches<T>(doc: T, ops: readonly JsonPatchOp[]): TGuides: Collaboration, Events & patches
captionClipsFromAsrWords
ASR word timestamps → caption clips with TRUE word timing (karaoke). Words group into clips on silence gaps and a max-words cap.
function captionClipsFromAsrWords(words: readonly AsrWord[], options: CaptionImportOptions & AsrGroupingOptions): BuiltinCommand[]Guides: Captions
captionClipsFromSubtitles
SRT/VTT → one caption clip per cue (words evenly split across the cue). Dispatch the result in one transaction.
function captionClipsFromSubtitles(text: string, options: CaptionImportOptions): BuiltinCommand[]Guides: Captions
captionsToSrt
Caption clips → SubRip (.srt), one cue per clip, in timeline order.
function captionsToSrt(clips: readonly CaptionCueSource[]): stringGuides: Captions
captionsToText
Caption clips → plain transcript text, one line per clip.
function captionsToText(clips: readonly CaptionCueSource[]): stringGuides: Captions
captionsToVtt
Caption clips → WebVTT (.vtt), one cue per clip, in timeline order.
function captionsToVtt(clips: readonly CaptionCueSource[]): stringGuides: Captions
clipHeadroomUs
Source footage past a clip's end ("out") or before its start ("in"). Generated clips are unlimited.
function clipHeadroomUs(doc: ProjectDocument, clip: Clip, side: "in" | "out"): numberGuides: Timeline, Clips, Transitions
clipKindRegistration
function clipKindRegistration(kind: string): ClipKindRegistration | undefinedGuides: Custom clip kinds
commandCatalog
The machine-readable command catalog: one JSON Schema per command type. Suitable for handing to an LLM as tool definitions.
function commandCatalog(extra?: Record<string, z.ZodType>): Record<string, unknown>Guides: Editing assistant, Tools from the command catalog, Properties panel, Commands
commandTypeForTool
Reverse of toolNameForCommand, resolved against the known types.
function commandTypeForTool(toolName: string, commandTypes: Iterable<string>): string | undefinedGuides: Tools from the command catalog
createProject
function createProject(init: ProjectSettings | ProjectDocument, options?: CreateProjectOptions): ProjectGuides: Editing assistant, Asset import, Collaboration, Commands
creditsFor
Credit lines for the assets the project uses whose license asks for attribution (or that carry an attribution anyway), de-duplicated, in a stable order — ready for an end card, a description box, or a sidecar file.
function creditsFor(doc: ProjectDocument): string[]Guides: Assets, Audio, Personalized video
cubicBezierProgress
CSS-convention cubic bézier: control points map input progress x∈[0,1] to eased progress y. Solve x(t)=progress for t (Newton with bisection fallback), then evaluate y(t).
function cubicBezierProgress(x1: number, y1: number, x2: number, y2: number, progress: number): numberdefaultEffectParams
A kind's default params — what effect/add fills in when params are omitted.
function defaultEffectParams(kind: string): Record<string, number | string> | undefinedGuides: Effects
describeAnimation
Plain-language summary of a recipe ("Fade in · Pulse · Slide down out").
function describeAnimation(r: AnimationRecipe): stringGuides: Keyframes & animation
describeProject
A compact, deterministic summary of the document, written for LLM prompts: everything an agent needs to place commands (ids, kinds, time ranges in the same microsecond unit commands take), nothing it doesn't.
function describeProject(doc: ProjectDocument, options?: DescribeProjectOptions): stringGuides: Editing assistant, MCP server, Tools from the command catalog, Roadmap
effectParamsSchema
function effectParamsSchema(kind: string): z.ZodType | undefinedGuides: Custom effects
evaluateClipAt
Convenience allocating variant of evaluateClipInto.
function evaluateClipAt(clip: Clip, clipTimeUs: Us): EvaluatedClipGuides: Properties panel, Keyframes & animation
evaluateClipInto
Evaluate a clip's animated properties at a clip-relative time into out
(alloc-free — call per tick with a reused object). Static clip values are
the fallback for properties without keyframes.
function evaluateClipInto(clip: Clip, clipTimeUs: Us, out: EvaluatedClip): EvaluatedClipGuides: Keyframes & animation
evaluateKeyframes
Evaluate one property's keyframe list at a clip-relative time. Keyframes
must be in stored order (the keyframe/set command maintains it). Pass
clipDurationUs when the list may hold end-anchored keyframes. Before
the first keyframe the first value holds; after the last, the last value
holds; between, the LEFT keyframe's easing shapes the segment.
function evaluateKeyframes(keyframes: readonly Keyframe[], timeUs: Us, fallback: number, clipDurationUs?: Us): numberGuides: Keyframes & animation
findCuts
Every cut on the project's video tracks, top track first, in time order.
function findCuts(doc: ProjectDocument, options?: {
minTransitionUs?: Us;
}): Cut[]Guides: Timeline, Transitions, Custom clip kinds, Headless editor logic
fitAnimation
In/Out lengths that fit the clip (they share it, leaving a sliver between).
function fitAnimation(recipe: AnimationRecipe, clipUs: Us): AnimationRecipeGuides: Keyframes & animation
formatFrameRate
Display form: "30", "29.97", "23.976".
function formatFrameRate(rate: FrameRateLike): stringframeCount
Frames needed to cover durationUs (the frames that start before it ends).
function frameCount(durationUs: Us, rate: FrameRateLike): numberGuides: Project & time
frameDurationUs
Duration of one frame in µs (fractional at NTSC rates — use frameToUs for positions).
function frameDurationUs(rate: FrameRateLike): numberGuides: Selection & canvas editing, Timeline, Project & time
frameToUs
Timeline position where the given frame starts: the first whole µs inside it.
function frameToUs(frame: number, rate: FrameRateLike): UsGuides: Selection & canvas editing, Timeline, Project & time
fromJsonPointer
function fromJsonPointer(pointer: string): string[]getEffectInfo
Editor/UI metadata for a built-in effect kind (undefined for custom kinds).
function getEffectInfo(kind: string): EffectInfo | undefinedGuides: Properties panel, Effects, Custom effects
isBuiltinClipKind
function isBuiltinClipKind(kind: string): booleankeyframeTimeUs
A keyframe's time from the clip's visible start, given the clip's duration.
function keyframeTimeUs(keyframe: Keyframe, clipDurationUs: Us): UslicenseReport
License check for the assets the project uses. commercial: true reports
non-commercial assets; assets without license info are reported only when
they came from a provider (plain uploads are the user's own business).
function licenseReport(doc: ProjectDocument, options?: {
commercial?: boolean;
}): LicenseIssue[]Guides: Assets, Audio, Personalized video
parseSubtitles
Parse SubRip (.srt) or WebVTT (.vtt) text into cues. Tolerant subset: numeric counters, WEBVTT headers, NOTE/STYLE blocks, and cue settings are ignored; HTML-ish tags inside cue text are stripped.
function parseSubtitles(text: string): CaptionCue[]Guides: Captions
planAnimation
Every keyframe a recipe produces on a clip, plus the properties it owns.
function planAnimation(recipe: AnimationRecipe, base: Base, clipUs: Us): {
keyframes: PlannedKeyframe[];
props: Set<Prop>;
}projectFrameRate
The project's exact frame rate: settings.frameRate when set, else read from settings.fps.
function projectFrameRate(settings: Pick<ProjectSettings, "fps" | "frameRate">): FrameRateGuides: Selection & canvas editing, Timeline, Project & time
readAnimation
Recognize the recipe that produced a clip's keyframes. Each In / Out candidate (preset × easing, its length read off the keyframes) must reproduce a subset of them on its own; the surviving combinations, with each loop, are checked against the whole set. A clip moved on the canvas after animating still reads, because bases come from the keyframes.
function readAnimation(clip: Clip): ReadAnimationGuides: Keyframes & animation
registerClipKind
function registerClipKind(kind: string, registration: ClipKindRegistration): voidGuides: Custom clip kinds
registerEffectKind
Register a custom effect kind's param schema (renderer registers its filter separately).
function registerEffectKind(kind: string, paramsSchema: z.ZodType): voidGuides: Custom effects
registerTransitionKind
function registerTransitionKind(kind: string, paramsSchema: z.ZodType): voidGuides: Custom transitions
resolveEasing
Accepts a preset name or an explicit easing; returns the stored form.
function resolveEasing(input: EasingPreset | Easing | undefined): EasingGuides: Keyframes & animation
resolveKeyframes
Keyframes with every time measured from the clip's visible start, sorted.
End-anchored keyframes are placed using clipDurationUs; when a clip is
shorter than its In + Out animations the two interleave by time (ties keep
the start-anchored one first). Returns the input array itself when nothing
is end-anchored.
function resolveKeyframes(keyframes: readonly Keyframe[], clipDurationUs: Us): readonly Keyframe[]Guides: Keyframes & animation
retimeWords
New text for a caption clip, re-timed: when the word count is unchanged
each word keeps its original timing (a typo fix keeps karaoke sync);
otherwise the words split the clip's span evenly. Returns clip-relative
words for clip/set-property { words }; empty text returns [].
function retimeWords(previous: readonly CaptionWord[], text: string, durationUs: Us): CaptionWord[]Guides: Captions
secondsToUs
function secondsToUs(seconds: number): UsGuides: Project & time
snapToFrame
Snap a position to the nearest frame start (halfway rounds to the later frame).
function snapToFrame(us: Us, rate: FrameRateLike): UsGuides: Timeline, Project & time, Headless editor logic
toFrameRate
The exact rate for a number or ratio. NTSC rates written as decimals
(23.976, 23.98, 29.97, 59.94, 119.88…) become base·1000/1001; whole
numbers become n/1; other decimals are read to 1/1000 (12.5 → 25/2).
Ratios are reduced. Throws a RangeError for non-positive or non-finite input.
function toFrameRate(rate: FrameRateLike): FrameRatetoJsonPatches
Convert Immer patches to RFC-6902 JSON Patch operations.
function toJsonPatches(patches: readonly Patch[]): JsonPatchOp[]toJsonPointer
function toJsonPointer(path: readonly (string | number)[]): stringtoolNameForCommand
"clip/add" → "clip_add" — the sanitized name used in tool definitions.
function toolNameForCommand(type: string): stringGuides: Tools from the command catalog
toToolDefinitions
The command catalog as ready-to-send LLM tool definitions. Pass the result
of project.commandCatalog() so registered custom commands are included.
function toToolDefinitions(catalog: Record<string, unknown>, options?: ToToolDefinitionsOptions): unknown[]Guides: Tools from the command catalog, Roadmap
transitionParamsSchema
function transitionParamsSchema(kind: string): z.ZodType | undefinedGuides: Custom transitions
tryDispatch
Dispatch one command, returning a structured result instead of throwing on command failures. Non-command errors (bugs) still throw.
function tryDispatch(project: Project, command: Command): CommandResultGuides: MCP server, Tools from the command catalog, Properties panel, Commands
usedAssets
Assets referenced by at least one clip (fonts count when a clip names their family).
function usedAssets(doc: ProjectDocument): Asset[]Guides: Assets
usToFrame
Index of the frame containing the given timeline position.
function usToFrame(us: Us, rate: FrameRateLike): numberGuides: Selection & canvas editing, Timeline, Project & time
usToSeconds
function usToSeconds(us: Us): numberGuides: Project & time
usToTimecode
Format µs as "HH:MM:SS:FF" timecode. Frames count from the exact rate, with
the nominal rate (30 for 29.97) as frames per second — non-drop-frame, which
runs about 3.6 s per hour behind the clock at 29.97. Pass { dropFrame: true }
for "HH:MM:SS;FF" drop-frame timecode at 29.97 / 59.94.
function usToTimecode(us: Us, rate: FrameRateLike, options?: TimecodeOptions): stringGuides: Timeline, Project & time
wordsFromCue
Fallback word timing: split a cue's span evenly across its words.
function wordsFromCue(cue: CaptionCue): CaptionWord[]Classes
CommandRejectedError
The command was well-formed but cannot be applied to the current state (unknown entity, track/clip kind mismatch, …). State was not touched.
Extends Error.
new CommandRejectedError(commandType: string, code: string, message: string)| Member | Type | Description |
|---|---|---|
commandType | string | |
code | string |
Guides: Properties panel, Timeline, Commands, Tracks
CommandValidationError
The command's payload failed schema validation. State was not touched.
Extends Error.
new CommandValidationError(commandType: string, error: ZodError)| Member | Type | Description |
|---|---|---|
commandType | string | |
issues | ZodError["issues"] |
Emitter
| Member | Type | Description |
|---|---|---|
on | (event: K, listener: Listener<Events[K]>) => () => void | |
off | (event: K, listener: Listener<Events[K]>) => void | |
emit | (event: K, payload: Events[K]) => void |
UnknownCommandError
Dispatched a command type that is not registered.
Extends Error.
new UnknownCommandError(commandType: string)| Member | Type | Description |
|---|---|---|
commandType | string |
Guides: Commands
Constants
ANIMATION_DEFAULT_SLOT_US
const ANIMATION_DEFAULT_SLOT_US: 600000ANIMATION_EASINGS
const ANIMATION_EASINGS: {
id: AnimationEasing;
label: string;
}[]Guides: Keyframes & animation
ANIMATION_MAX_SLOT_US
const ANIMATION_MAX_SLOT_US: 3000000ANIMATION_MIN_SLOT_US
const ANIMATION_MIN_SLOT_US: 200000ANIMATION_PRESETS
const ANIMATION_PRESETS: readonly AnimationPreset[]Guides: Keyframes & animation
animationPreset
A preset by id ("in:pop", "loop:pulse", …).
const animationPreset: (id: string) => AnimationPreset | undefinedGuides: Keyframes & animation
builtinEffectParamSchemas
Kind registries — the extension seam. Built-in effect, transition, and
clip kinds are pre-registered here with their Zod param schemas, which is
what puts them in the AI command catalog and validates their commands.
register*Kind lets consumers add custom kinds (the renderer's factory
registries are the rendering half of the same seam).
const builtinEffectParamSchemas: {
readonly colorAdjust: z.ZodObject<{
brightness: z.ZodDefault<z.ZodNumber>;
contrast: z.ZodDefault<z.ZodNumber>;
saturation: z.ZodDefault<z.ZodNumber>;
hue: z.ZodDefault<z.ZodNumber>;
}, z.core.$strip>;
readonly blur: z.ZodObject<{
amount: z.ZodDefault<z.ZodNumber>;
}, z.core.$strip>;
readonly chromaKey: z.ZodObject<{
color: z.ZodDefault<z.ZodString>;
similarity: z.ZodDefault<z.ZodNumber>;
smoothness: z.ZodDefault<z.ZodNumber>;
spill: z.ZodDefault<z.ZodNumber>;
}, z.core.$strip>;
}builtinPayloadSchemas
const builtinPayloadSchemas: {
readonly "project/set-settings": z.ZodObject<{
width: z.ZodOptional<z.ZodNumber>;
height: z.ZodOptional<z.ZodNumber>;
fps: z.ZodOptional<z.ZodNumber>;
frameRate: z.ZodOptional<z.ZodObject<{
num: z.ZodNumber;
den: z.ZodNumber;
}, z.core.$strip>>;
name: z.ZodOptional<z.ZodString>;
}, z.core.$strip>;
readonly "asset/add": z.ZodObject<{
id: z.ZodString;
kind: z.ZodEnum<{
video: "video";
audio: "audio";
image: "image";
font: "font";
}>;
…Guides: Properties panel
builtinTransitionParamSchemas
const builtinTransitionParamSchemas: {
readonly crossDissolve: z.ZodObject<{}, z.core.$strip>;
readonly dipToBlack: z.ZodObject<{}, z.core.$strip>;
readonly dipToWhite: z.ZodObject<{}, z.core.$strip>;
readonly wipe: z.ZodObject<{
direction: z.ZodDefault<z.ZodEnum<{
left: "left";
right: "right";
up: "up";
down: "down";
}>>;
}, z.core.$strip>;
readonly slide: z.ZodObject<{
direction: z.ZodDefault<z.ZodEnum<{
left: "left";
right: "right";
up: "up";
down: "down";
}>>;
}, z.core.$s …DEFAULT_TRANSFORM
const DEFAULT_TRANSFORM: TransformEASING_PRESETS
Named presets, stored expanded — CSS cubic-bezier() control points.
const EASING_PRESETS: Record<EasingPreset, Easing>EFFECT_CATALOG
Every built-in effect kind, grouped by category (catalog order = picker order).
const EFFECT_CATALOG: readonly EffectInfo[]Guides: Effects, Custom effects
EFFECT_CATEGORIES
const EFFECT_CATEGORIES: readonly {
id: EffectCategory;
label: string;
}[]Guides: Effects
isAudioClip
const isAudioClip: (clip: Clip) => clip is AudioClipGuides: Clips
isCaptionClip
const isCaptionClip: (clip: Clip) => clip is CaptionClipisHtmlClip
const isHtmlClip: (clip: Clip) => clip is HtmlClipGuides: Clips
isImageClip
const isImageClip: (clip: Clip) => clip is ImageClipGuides: Clips
isTextClip
const isTextClip: (clip: Clip) => clip is TextClipGuides: Clips
isVideoClip
const isVideoClip: (clip: Clip) => clip is VideoClipGuides: Clips
TRACK_ACCEPTS
Which clip kinds a track kind accepts (custom kinds declare theirs at registration).
const TRACK_ACCEPTS: Record<TrackKind, readonly ClipKind[]>transformSchema
const transformSchema: z.ZodObject<{
x: z.ZodNumber;
y: z.ZodNumber;
scale: z.ZodNumber;
rotation: z.ZodNumber;
opacity: z.ZodNumber;
}, z.core.$strip>TYPOGRAPHY_DEFAULTS
What an absent typography field means. Renderers and editor UIs read these
rather than hard-coding them; the schema deliberately has no .default()s
for these fields, so parsed documents never gain keys nobody set.
const TYPOGRAPHY_DEFAULTS: {
readonly fontWeight: 400;
readonly fontStyle: "normal";
readonly letterSpacing: 0;
readonly textAlign: "left";
/** Caption line height multiple (text clips default to the font's natural line height). */
readonly captionLineHeight: 1.3;
}Guides: Text & typography
US_PER_SECOND
const US_PER_SECOND: 1000000Interfaces
AnimationPreset
| Member | Type | Description |
|---|---|---|
id | string | |
label | string | |
slot | AnimationSlot | |
build | (base: Base, lengthUs: Us, easing: AnimationEasing) => Track | Build the slot's keyframes. In: ends at base. Out: starts at base. Loop: spans lengthUs. |
fixed? | boolean | Loops only: fixed timing, no duration / easing choice. |
AnimationRecipe
| Member | Type | Description |
|---|---|---|
in? | AnimationSlotChoice | |
loop? | AnimationSlotChoice | |
out? | AnimationSlotChoice |
AnimationSlotChoice
| Member | Type | Description |
|---|---|---|
preset | string | |
durationUs | Us | |
easing | AnimationEasing |
AsrGroupingOptions
| Member | Type | Description |
|---|---|---|
maxGapUs? | Us | Start a new caption clip when the gap between words exceeds this (default 600ms). |
maxWordsPerGroup? | number | Maximum words per caption clip (default 8). |
AsrWord
One ASR word, in seconds — the shape Whisper-style pipelines emit.
| Member | Type | Description |
|---|---|---|
text | string | |
startS | number | |
endS | number |
Asset
| Member | Type | Description |
|---|---|---|
id | string | |
kind | AssetKind | |
src | string | |
name? | string | Display name (e.g. a stock track's title). |
source? | AssetSource | Provenance — set by importers; absent for plain uploads. |
license? | AssetLicense | |
attribution? | string | The credit line to show when the license requires it, e.g. "“Calm Piano” by Jane Doe (CC BY 4.0)". |
durationUs? | Us | Intrinsic duration of the media, if applicable (video/audio). |
width? | number | |
height? | number | |
fps? | number | |
family? | string | Font assets: the CSS font-family name clips reference. |
weight? | FontWeight | Font assets: the face's weight descriptor (100–900; default 400). |
style? | FontStyle | Font assets: the face's style descriptor (default "normal"). |
weightRange? | [FontWeight, FontWeight] | Font assets: a VARIABLE font's weight axis range, e.g. [100, 900]. The face is declared over the whole range, so every weight in it renders from the font's own axis instead of a synthesized bold. Takes precedence over weight. |
AssetLicense
The terms an asset may be used under.
| Member | Type | Description |
|---|---|---|
id | string | SPDX-style id, e.g. "CC0-1.0", "CC-BY-4.0", "CC-BY-NC-4.0", or a provider's own name. |
url? | string | |
commercial | boolean | May the asset be used in commercial work? |
attributionRequired | boolean | Must the work credit the author (see Asset.attribution)? |
Guides: Audio
AssetSource
Where an asset came from (a stock library, a generator, a recording, an upload).
| Member | Type | Description |
|---|---|---|
provider | string | Provider id, e.g. "openverse", "freesound", "recording", "upload". |
id | string | The provider's own id for the item (empty for uploads/recordings). |
url? | string | Human-facing page for the item (license and credits live there). |
AudioClip
Extends ClipBase, AudioFades.
| Member | Type | Description |
|---|---|---|
kind | "audio" | |
assetId | string | |
trimStartUs | Us | |
volume | number |
AudioFades
Linear fades at a sound-making clip's edges (absent = none). Clamped to the clip's length when rendered.
| Member | Type | Description |
|---|---|---|
fadeInUs? | Us | |
fadeOutUs? | Us |
CaptionClip
Reels-style karaoke caption: word-level timing with an active-word style.
Extends ClipBase.
| Member | Type | Description |
|---|---|---|
kind | "caption" | |
words | CaptionWord[] | |
style | CaptionStyle |
CaptionCue
Caption import — pure text parsing that produces clip/add commands, so
importing subtitles is just a transaction of ordinary commands (undoable,
replayable, and visible to the AI command catalog like everything else).
Two sources: SRT/VTT cues (line-level timing; words get an even split as the fallback) and ASR word timestamps (Whisper-shape; real karaoke timing).
| Member | Type | Description |
|---|---|---|
startUs | Us | |
endUs | Us | |
text | string |
CaptionCueSource
The caption-clip fields subtitle export reads (any CaptionClip fits).
| Member | Type | Description |
|---|---|---|
startUs | Us | |
durationUs | Us | |
words | readonly CaptionWord[] |
CaptionImportOptions
| Member | Type | Description |
|---|---|---|
trackId | string | |
style? | Partial<CaptionStyle> | Merged over the default caption style. |
idPrefix? | string | Deterministic clip ids: ${idPrefix}-<n> (default "caption"). |
CaptionStyle
Caption lines are always centered, so captions take no textAlign.
Extends Typography.
| Member | Type | Description |
|---|---|---|
preset | "plain" | "highlight" | "karaoke" | "pop" | "reveal" | How words react to timing: plain (static), highlight (active word in highlightColor), karaoke (every started word lit), pop (active word enlarged + lit), reveal (words appear as they are spoken). |
fontFamily | string | |
fontSizeFrac | number | Font size as a fraction of composition height (resolution-independent). |
color | string | |
highlightColor | string | |
backgroundColor? | string | Optional box behind the caption block. |
display? | "block" | "word" | word shows only the active word (word-by-word captions); default block (the whole clip). |
textTransform? | "uppercase" | "lowercase" | Case transform applied when drawing (the words keep their original text). |
strokeColor? | string | Outline color; width is strokeWidthFrac × font size (default 0.08 when a color is set). |
strokeWidthFrac? | number | |
shadowColor? | string | Drop shadow / glow color. Blur and downward offset are fractions of font size (defaults 0.15 / 0.06). An offset of 0 is a glow. |
shadowBlurFrac? | number | |
shadowOffsetFrac? | number | |
activeBackgroundColor? | string | Rounded box behind each emphasized word (highlight / karaoke / pop). |
CaptionWord
One word of a caption, timed relative to the clip's start.
| Member | Type | Description |
|---|---|---|
text | string | |
startUs | Us | |
durationUs | Us |
ClipBase
| Member | Type | Description |
|---|---|---|
id | string | |
trackId | string | |
startUs | Us | Timeline placement. |
durationUs | Us | |
transform | Transform | |
animations? | Partial<Record<AnimatableProperty, Keyframe[]>> | Keyframes per animated property; a property without keyframes uses the clip's static value. |
effects? | EffectInstance[] | Effect stack, applied pre-transform in array order. |
ClipKindRegistration
| Member | Type | Description |
|---|---|---|
propsSchema | z.ZodType | |
trackKinds | readonly TrackKind[] |
Guides: Custom clip kinds
Command
| Member | Type | Description |
|---|---|---|
type | string | |
payload | unknown |
CommandDefinition
| Member | Type | Description |
|---|---|---|
type | string | |
schema | S | |
handler | CommandHandler<z.output<S>> |
CommandFailure
| Member | Type | Description |
|---|---|---|
kind | "unknown-command" | "invalid-payload" | "rejected" | What went wrong, as a category an agent can branch on. |
commandType | string | |
message | string | Human-readable summary (same text the thrown error carries). |
code? | string | rejected only: the engine's rejection code (e.g. "unknown-clip"). |
issues? | { path: string; message: string; }[] | invalid-payload only: one entry per schema violation. |
validTypes? | string[] | unknown-command only: every type the project accepts. |
CreateProjectOptions
| Member | Type | Description |
|---|---|---|
historyLimit? | number | Maximum number of history entries kept (default 500). |
CustomClip
A clip of a registered custom kind (see registerClipKind). Its payload
lives under props, validated by the kind's registered schema.
Extends ClipBase.
| Member | Type | Description |
|---|---|---|
kind | string | |
props | Record<string, unknown> |
Cut
Cuts: where one clip ends exactly where the next starts on the same video
track — the only places transition/add accepts. A transition is centered
on the cut, so it borrows half its length from footage past the outgoing
clip's end and half from before the incoming clip's start ("handles");
maxTransitionUs is what those handles (and both clips' lengths) allow.
Editor panels and AI tools share this, so "every cut" means the same thing everywhere.
| Member | Type | Description |
|---|---|---|
trackId | string | |
fromClipId | string | |
toClipId | string | |
atUs | Us | Timeline position of the cut. |
transition? | Transition | The transition on this cut, if any. |
maxTransitionUs | Us | Longest transition the media allows here (0 when a side has no spare footage). |
short? | "from" | "to" | "both" | Which side lacks spare footage, when one does. |
Guides: Transitions
DescribeProjectOptions
| Member | Type | Description |
|---|---|---|
maxClipsPerTrack? | number | Elide the middle of very long tracks (default 50 clips per track). |
EffectInfo
| Member | Type | Description |
|---|---|---|
kind | string | |
label | string | |
category | EffectCategory | |
params | readonly EffectParamInfo[] |
Guides: Effects
EffectInstance
One effect in a clip's stack. kind resolves through the effect registry
(built-ins: see EFFECT_CATALOG); params are validated against
the kind's schema at command time. Length-denoting params are normalized
to composition units — absolute pixels would render differently between a
scaled preview and a full-resolution export.
| Member | Type | Description |
|---|---|---|
id | string | |
kind | string | |
enabled | boolean | |
params | Record<string, unknown> |
EphemeralState
Ephemeral session state: lives in the store, excluded from undo history and from serialization.
| Member | Type | Description |
|---|---|---|
playheadUs | Us | |
selection | string[] |
EvaluatedClip
Flat evaluated state — reusable output object keeps evaluation alloc-free.
| Member | Type | Description |
|---|---|---|
x | number | |
y | number | |
scale | number | |
rotation | number | |
opacity | number | |
volume | number |
FrameRate
An exact frame rate as a ratio of positive integers: NTSC 29.97 is { num: 30000, den: 1001 }.
| Member | Type | Description |
|---|---|---|
num | number | |
den | number |
HtmlClip
HTML rendered into the composition: the template (with {{param}}
placeholders) is rasterized via SVG foreignObject at widthPx×heightPx
(default: the composition size) and composited like any other clip —
keyframes, effects, and transitions all apply to the raster.
Extends ClipBase.
| Member | Type | Description |
|---|---|---|
kind | "html" | |
template | string | HTML markup; {{name}} placeholders substitute from params. |
params | Record<string, HtmlParamValue> | |
widthPx? | number | Raster size in composition pixels (default: the composition size). |
heightPx? | number | |
animated? | boolean | Re-rasterize every frame at the clip's local time, so CSS animations inside the template play (and seek, and export) frame-exactly. The renderer pauses every animation and sets its delay to calc(var(--d, 0s) - var(--t)), with --t = seconds into the clip and --T = the clip's animation length on the root element: write @keyframes + animation as usual and stagger with style="--d:.4s". Static (false/absent) clips raster once per param change. |
animationOffsetUs? | number | Animated clips: animation time at the clip's start (default 0). A split advances the right half's offset so the animation continues across the cut instead of restarting. |
ImageClip
Extends ClipBase.
| Member | Type | Description |
|---|---|---|
kind | "image" | |
assetId | string |
JsonPatchOp
RFC-6902 JSON Patch operation (the subset Immer produces).
| Member | Type | Description |
|---|---|---|
op | "add" | "replace" | "remove" | |
path | string | RFC-6901 JSON Pointer, e.g. "/doc/clips/clip-1/startUs". |
value? | unknown |
Keyframe
| Member | Type | Description |
|---|---|---|
timeUs | Us | Clip-relative time. By default measured from the clip's visible start (0 = start; never moves with trims). With anchor: "end" it is measured BACK from the clip's visible end (0 = the last instant), so Out animations stay glued to the end when the clip is trimmed or resized. |
anchor? | "end" | Omitted = "start". |
value | number | |
easing | Easing | The curve from THIS keyframe to the next one. |
LicenseIssue
| Member | Type | Description |
|---|---|---|
assetId | string | |
kind | "non-commercial" | "unknown-license" | "missing-attribution" | |
message | string |
PlannedKeyframe
| Member | Type | Description |
|---|---|---|
property | Prop | |
timeUs | Us | |
value | number | |
easing | Easing | |
anchor? | "end" |
Project
| Member | Type | Description |
|---|---|---|
getState | () => ProjectState | Read the current state. Never mutate it — dispatch commands instead. |
subscribe | <T>(selector: (state: ProjectState) => T, listener: (value: T, previous: T) => void) => () => void | Subscribe to a slice of state. Returns an unsubscribe function. |
store | StoreApi<ProjectState> | The underlying Zustand store, for framework adapters. |
events | Emitter<ProjectEvents> | |
dispatch | (command: BuiltinCommand | Command) => void | Dispatch a command. Throws typed errors; on error, state is untouched. |
transaction | (fn: () => void, label?: string) => void | Run several dispatches as one undoable history entry (atomic: all or nothing). |
registerCommand | <S extends z.ZodType>(definition: CommandDefinition<S>) => void | Register a custom command type. |
commandCatalog | () => Record<string, unknown> | JSON Schema per command type — hand this to an LLM as its tool catalog. |
undo | () => boolean | |
redo | () => boolean | |
canUndo | () => boolean | |
canRedo | () => boolean | |
clearHistory | () => void | |
setPlayhead | (positionUs: Us) => void | Ephemeral (not undoable, not serialized). |
setSelection | (ids: string[]) => void | |
toJSON | () => ProjectDocument | Serialize the document. Feed the result back to createProject to restore. |
Guides: MCP server, Collaboration, Stills
ProjectDocument
The document: everything that is part of the composition, serialized and covered by undo history.
| Member | Type | Description |
|---|---|---|
schemaVersion | 1 | |
settings | ProjectSettings | |
assets | Record<string, Asset> | |
tracks | Record<string, Track> | |
trackOrder | string[] | Render order: index 0 is the bottom layer. |
clips | Record<string, Clip> | |
transitions | Record<string, Transition> | Transitions between adjacent clips, keyed by id. |
Guides: Collaboration, Stills, Defining templates
ProjectEvents
Extends Record<string, unknown>.
| Member | Type | Description |
|---|---|---|
patches | { patches: JsonPatchOp[]; inverse: JsonPatchOp[]; source: PatchSource; } | Emitted on every document change, as RFC-6902 patches relative to the document root. |
history | { kind: "commit" | "undo" | "redo"; label?: string; } | |
playhead | { positionUs: Us; } | |
selection | { ids: string[]; } |
ProjectSettings
| Member | Type | Description |
|---|---|---|
width | number | |
height | number | |
fps | number | Frames per second. A decimal NTSC rate (29.97, 23.976, 59.94) is read as its exact /1001 ratio; read the exact rate with projectFrameRate(settings). |
frameRate? | FrameRate | The exact rate, kept when it isn't a whole number (fps then mirrors num/den). project/set-settings keeps the two in sync. |
name? | string |
ProjectState
Extends EphemeralState.
| Member | Type | Description |
|---|---|---|
doc | ProjectDocument |
ReadAnimation
| Member | Type | Description |
|---|---|---|
recipe | AnimationRecipe | |
custom | boolean | Keyframes exist that no preset explains. |
stale? | boolean | The loop's keyframes no longer span the clip (it was trimmed): re-apply to refresh. |
TextClip
Extends ClipBase, Typography.
| Member | Type | Description |
|---|---|---|
kind | "text" | |
text | string | |
fontFamily | string | |
fontSizePx | number | |
color | string | |
textAlign? | TextAlign | Alignment of lines within the text block (default "left"). Independent of the clip's anchor/transform. |
TimecodeOptions
| Member | Type | Description |
|---|---|---|
dropFrame? | boolean | SMPTE drop-frame timecode ("HH:MM:SS;FF"): skips frame numbers 0 and 1 (0–3 at 59.94) at each minute except every tenth, so the timecode tracks the clock at 29.97 / 59.94. Only for those rates; ignored otherwise. |
ToToolDefinitionsOptions
| Member | Type | Description |
|---|---|---|
style? | "anthropic" | "openai" | Wire shape: "anthropic" (default) → { name, description, input_schema }; "openai" → { type: "function", function: { name, description, parameters } }. |
mode? | "per-command" | "dispatch" | "per-command" (default): one tool per command type — best when the host allows ~25+ tools. "dispatch": ONE miraiclip_dispatch tool taking { type, payload } — for tool-count-constrained hosts (MCP servers); pair it with handing the agent the catalog for payload schemas. |
descriptions? | Record<string, string> | Extra or overriding descriptions, keyed by command type (custom commands). |
Track
| Member | Type | Description |
|---|---|---|
id | string | |
kind | TrackKind | |
name | string | |
muted | boolean | |
solo | boolean | |
locked | boolean | |
hidden? | boolean | Visual tracks: when true, the track's clips aren't drawn (preview, export, stills). Audio is unaffected — use muted. |
Transform
| Member | Type | Description |
|---|---|---|
x | number | Normalized center position: 0.5/0.5 = canvas center. |
y | number | |
scale | number | |
rotation | number | Degrees. |
opacity | number | 0..1 |
Transition
| Member | Type | Description |
|---|---|---|
id | string | |
trackId | string | |
fromClipId | string | The clip ending at the cut. |
toClipId | string | The clip starting at the cut. |
kind | string | Registry-resolved kind (built-ins: crossDissolve, dipToBlack, dipToWhite, wipe, slide). |
durationUs | Us | |
alignment | "centered" | Reserved: only "centered" in v4. |
params | Record<string, unknown> |
Typography
Optional typography shared by text clips and caption styles. Absent fields
mean the default (see TYPOGRAPHY_DEFAULTS) — which is exactly how clips
rendered before these fields existed. Documents only carry what was set, so
older projects load, render, and serialize unchanged.
| Member | Type | Description |
|---|---|---|
fontWeight? | FontWeight | 100–900 (default 400). |
fontStyle? | FontStyle | Default "normal". |
lineHeight? | number | Line box height as a multiple of font size. Absent = today's rendering: the font's natural line height for text clips, 1.3 for captions. |
letterSpacing? | number | Extra space between characters in em — scales with font size (default 0). |
VideoClip
Extends ClipBase, AudioFades.
| Member | Type | Description |
|---|---|---|
kind | "video" | |
assetId | string | |
trimStartUs | Us | Source trim: offset into the asset where playback starts. |
volume | number |
Types
AnimatableProperty
type AnimatableProperty = "x" | "y" | "scale" | "rotation" | "opacity" | "volume";AnimationEasing
type AnimationEasing = "smooth" | "linear" | "snappy";AnimationSlot
Animation presets, built on ordinary keyframes so they play in preview and every export with no special support.
A clip's animation is a RECIPE of up to three slots: In (from the start),
Loop (between In and Out) and Out (into the end, end-anchored so it follows
trims). Applying a recipe regenerates the clip's visual keyframes. The
recipe isn't stored anywhere: readAnimation recognizes it from the
keyframes (presets are deterministic), so it survives save / reload and
undo. Hand-made keyframes that match no preset read as "custom".
Editor panels and AI tools share this module, so "pop in" means the same keyframes wherever it's applied.
type AnimationSlot = "in" | "out" | "loop";ApplyCommandsResult
type ApplyCommandsResult = {
ok: true;
applied: number;
} | {
ok: false;
applied: 0;
failedIndex: number;
error: CommandFailure;
};AssetKind
type AssetKind = "video" | "audio" | "image" | "font";BuiltinClip
type BuiltinClip = VideoClip | AudioClip | ImageClip | TextClip | CaptionClip | HtmlClip;BuiltinCommand
A built-in command as dispatched.
type BuiltinCommand = {
[T in BuiltinCommandType]: {
type: T;
payload: BuiltinCommandPayload<T>;
};
}[BuiltinCommandType];BuiltinCommandPayload
type BuiltinCommandPayload<T extends BuiltinCommandType> = z.input<(typeof builtinPayloadSchemas)[T]>;BuiltinCommandType
type BuiltinCommandType = keyof typeof builtinPayloadSchemas;Clip
type Clip = BuiltinClip | CustomClip;ClipKind
type ClipKind = "video" | "audio" | "image" | "text" | "caption" | "html";CommandHandler
Applies a validated payload to a draft of the document.
type CommandHandler<P = unknown> = (doc: Draft<ProjectDocument>, payload: P) => void;CommandResult
type CommandResult = {
ok: true;
} | {
ok: false;
error: CommandFailure;
};Easing
Easings are STORED as cubic-bézier control points (CSS convention: progress x → eased y) — named presets are input sugar that expands at command time. "hold" steps to the next keyframe with no interpolation.
type Easing = {
kind: "bezier";
x1: number;
y1: number;
x2: number;
y2: number;
} | {
kind: "hold";
};EasingPreset
type EasingPreset = "linear" | "hold" | "easeIn" | "easeOut" | "easeInOut";EffectCategory
The built-in effect library: every built-in effect kind's name, category and params, as plain data. One declaration feeds three consumers:
- validation — each entry's param schema is derived from it (see registry.ts),
so
effect/add/effect/updatevalidate and fill defaults; - the AI command catalog — kinds and ranges reach LLM tools;
- editor UIs — labels, categories, slider ranges and display hints for
property panels and effect pickers (
EFFECT_CATALOG,getEffectInfo).
The renderer draws every kind listed here (its shaders are keyed by kind).
Length params (length: true) are fractions of COMPOSITION HEIGHT — never
pixels — so a scaled preview and a full-resolution export look identical.
type EffectCategory = "color" | "film" | "stylize" | "glitch" | "light" | "distort" | "frame";EffectParamFormat
How a UI should display a number param's value.
type EffectParamFormat = "percent" | "int" | "deg" | "stops" | "decimal";EffectParamInfo
type EffectParamInfo = {
key: string;
label: string;
type: "number";
min: number;
max: number;
step: number;
default: number;
format?: EffectParamFormat;
length?: true;
} | {
key: string;
label: string;
type: "color"; default: string;
};Guides: Effects
FontStyle
type FontStyle = "normal" | "italic";FontWeight
CSS font weight: 100–900 in steps of 100 (validated by the command schemas).
type FontWeight = number;FrameRateLike
Anything the frame helpers accept: a plain number (29.97 → 30000/1001) or an exact ratio.
type FrameRateLike = number | FrameRate;HtmlParamValue
A template parameter value — plain data, so html clips serialize and cross process boundaries.
type HtmlParamValue = string | number | boolean;Listener
Minimal typed event emitter.
type Listener<T> = (payload: T) => void;PatchSource
type PatchSource = "dispatch" | "undo" | "redo" | "transaction-rollback";TextAlign
type TextAlign = "left" | "center" | "right";TrackKind
type TrackKind = "video" | "audio";Us
Microseconds — the timeline unit. 1 second = 1_000_000 µs.
type Us = number;