Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ClipAsm

ClipAsm turns a small text program into a video. You describe sources and edits, check the program without opening media, and render an MP4 with FFmpeg.

ClipAsm is pre-release software. The language and command line may change as maintainers simplify the project.

Learn ClipAsm in order

After setup, the learning chapters follow one evolving project. Each chapter starts from the previous checkpoint. It introduces an idea only when the edit needs it. Each chapter ends with a valid result that you can inspect. This path teaches ClipAsm’s core video-composition workflow.

  1. Get ClipAsm running and render the included starter.
  2. Go from one image to a sequence.
  3. Name and reference a clip.
  4. Transform one scene.
  5. Add a flash between scenes.
  6. Change a named scene after assembly.
  7. Reuse a scene style across source files.

If you only want to evaluate ClipAsm first, try it in the browser. The playground contains a complete project and uploads nothing.

How-to guides

Use these guides when you have a concrete task:

Understand ClipAsm

These pages explain the underlying model without using the learning project:

Examples and reference

Use the example catalog for small runnable programs. For exact information, use:

When ClipAsm reports a diagnostic code, the quickest explanation is usually:

clipasm explain E_UNKNOWN_PROGRAM

Contributing

The repository contains contributor architecture and maintenance documents. They are outside this user guide. Start with the repository’s contribution workflow. Report possible vulnerabilities through the security policy.

Try ClipAsm

The playground below contains a complete three-scene project. In a few minutes you can check the source, make one edit, and render the result. Nothing is uploaded.

clipasm 1

config {
    video {
        width = 320
        height = 180
        fps = 24
        color = sdr_bt709
    }
    output = "generated/scenic-sequence.mp4"
}

clip {
    image("assets/morning.png", 1500ms, contain)
    image("assets/meadow.png", 1500ms, contain)
    image("assets/evening.png", 1500ms, contain)
} as pictures

$pictures

1. Validate the original

Select Validate, or press Ctrl+Enter. The source passes with 108 frames: three 1.5-second scenes at 24 frames per second.

2. Change one scene

Change the meadow duration from 1500ms to 1s.

3. Validate the change

Select Validate, or press Ctrl+Enter. The timeline is now four seconds, or 96 frames.

4. Render the video

Select Render video.

5. Check the video

When rendering finishes, play the preview. Confirm that the middle scene is shorter. Reset restores the original source and project files.

Project files

The images appear under Virtual project files. You can preview, rename, replace, or delete them. Everything stays in your browser.

Browser limits

The playground supports still-image and video-file sources together with the native operations reachable from them. It does not support imports, standalone Audio-file sources, or external programs.

It accepts one source file up to 256 KiB. Each asset can be up to 128 MiB, with a 256 MiB total limit. Browser rendering uses a single-threaded WebAssembly FFmpeg runtime with a bounded work budget. Use the installed CLI for larger projects.

Continue with Get ClipAsm running to create a local project and use the complete native feature set.

The browser downloads the renderer only when you select Render video. The renderer is separate GPL-licensed software. See the browser runtime notices.

1. Get ClipAsm running

This chapter installs the CLI, creates a standalone project, and renders the included video. The following chapters will build a new source file one concept at a time.

Before you start

Install Rust 1.95 or newer. Rendering also requires ffmpeg and ffprobe on PATH.

rustc --version
cargo --version
ffmpeg -version
ffprobe -version

The exact output differs by system. rustc must report version 1.95 or newer.

1. Install the CLI

cargo install clipasm --locked

2. Create a project

clipasm init hello-video

3. Enter the project

cd hello-video

The new project contains:

.gitignore
README.md
clipasm.toml
main.clipasm
assets/
  morning.png
  meadow.png
  evening.png

These are ordinary files you control. init does not run Git, inspect media, render, or contact the network, and ClipAsm does not rewrite the project later.

4. Render the starter

clipasm render

ClipAsm checks the source, opens the three images, verifies the required media tools, and writes:

generated/scenic-sequence.mp4
generated/scenic-sequence.mp4.manifest.json

5. Check the video

Open the MP4 with your usual file manager or media player. You should see the morning, meadow, and evening scenes in that order.

You have confirmed that the CLI and media tools work. Leave main.clipasm unchanged. It remains a useful finished example while you build the same idea from an empty file.

Next, go from one image to a sequence.

2. From one image to a sequence

In this chapter you will start with one image, then grow it into a 4.5-second sequence. ClipAsm cannot publish three images as one Video. The diagnostic will reveal ClipAsm’s stack model and motivate concat.

Continue your project

Complete Get ClipAsm running first. Stay in the hello-video directory so the starter images remain available.

Create learning.clipasm. This is the file you will develop through the rest of the learning chapters. Leave the generated main.clipasm starter unchanged for comparison.

1. Create a one-image video source

Start with:

clipasm 1

config {
    video {
        width = 320
        height = 180
        fps = 24
    }
    output = "generated/learning.mp4"
}

image("assets/morning.png", 1500ms, contain)

clipasm 1 selects the language version. The video configuration establishes the frame dimensions and frame rate. contain keeps the whole image visible inside that frame. image creates one Video value lasting 1.5 seconds.

2. Validate the one-image source

clipasm validate learning.clipasm

Validation reports 36 frames.

3. Render the one-image Video

clipasm render learning.clipasm

4. Check the one-image Video

Open generated/learning.mp4. Confirm that the morning image appears for 1.5 seconds.

5. Add two more images

Replace the final image call with these three calls:

image("assets/morning.png", 1500ms, contain)
image("assets/meadow.png", 1500ms, contain)
image("assets/evening.png", 1500ms, contain)

6. Validate the three-image source

clipasm validate learning.clipasm

ClipAsm reports E_ENTRYPOINT_OUTPUT_COUNT: three Video values remain, but a source file with output must leave exactly one Video to publish.

7. Explain the diagnostic

When an unfamiliar diagnostic includes a code, ask ClipAsm for its explanation:

clipasm explain E_ENTRYPOINT_OUTPUT_COUNT

8. Examine the stack

Each call leaves its result after the values produced earlier:

image morning  -> [morning]
image meadow   -> [morning, meadow]
image evening  -> [morning, meadow, evening]

This ordered collection is the stack. Each image is valid. The program needs an operation that consumes three Videos and returns one.

9. Add concat

Add concat:

image("assets/morning.png", 1500ms, contain)
image("assets/meadow.png", 1500ms, contain)
image("assets/evening.png", 1500ms, contain)
concat

concat consumes the accessible Videos in stack order and leaves their combined result:

[morning, meadow, evening] -> concat -> [sequence]

10. Validate the sequence

clipasm validate learning.clipasm

Validation now reports 108 frames.

11. Render the sequence

clipasm render learning.clipasm

12. Check the sequence

Reopen generated/learning.mp4. The three 1.5-second scenes play in morning, meadow, evening order.

You now know why statement order matters. A call can consume stack values without receiving them as explicit arguments.

Next, name and reference a clip.

3. Name and reference a clip

Your three-image sequence publishes correctly, but it exists only as values immediately consumed by concat. In this chapter you will package those statements as one clip, give it an identity, and reference it later.

Continue editing learning.clipasm from From one image to a sequence.

1. Replace concat with clip

Replace the four executable statements with:

clip {
    image("assets/morning.png", 1500ms, contain)
    image("assets/meadow.png", 1500ms, contain)
    image("assets/evening.png", 1500ms, contain)
}

A clip collects the Video values left by its body and concatenates them in order. The body therefore does not need its own concat.

2. Validate the clip

Validate this version:

clipasm validate learning.clipasm

Expect E_ENTRYPOINT_OUTPUT_COUNT again. This time the diagnostic says that zero Videos remain, not three. The clip form removes its temporary result from the outer stack, so merely creating a clip does not publish it.

3. Give the clip a name

Add as pictures after the closing brace:

clip {
    image("assets/morning.png", 1500ms, contain)
    image("assets/meadow.png", 1500ms, contain)
    image("assets/evening.png", 1500ms, contain)
} as pictures

as pictures preserves the composed Video under an immutable graph name. It still does not place a Video on the outer stack.

4. Reference the named value

Add a reference after the clip:

clip {
    image("assets/morning.png", 1500ms, contain)
    image("assets/meadow.png", 1500ms, contain)
    image("assets/evening.png", 1500ms, contain)
} as pictures

$pictures

$pictures places an occurrence of the named Video at that point in the program. It does not move or change the underlying value.

5. Validate the reference

Validate:

clipasm validate learning.clipasm

The file again leaves one 108-frame Video ready to publish.

You used clip to make one composition and as to name it. You used $pictures to place it on the stack where needed.

Next, transform one scene.

4. Transform one scene

The sequence is structurally complete. Now you will add movement to the meadow without changing the other scenes or the total duration.

Continue editing learning.clipasm from Name and reference a clip.

1. Add the effect where it belongs

Place zoom_in(4%) immediately after the meadow image:

clip {
    image("assets/morning.png", 1500ms, contain)
    image("assets/meadow.png", 1500ms, contain)
    zoom_in(4%)
    image("assets/evening.png", 1500ms, contain)
} as pictures

$pictures

zoom_in needs one Video. This call has no explicit Video input. It consumes the nearest Video on the stack, which is the meadow. The call leaves the transformed Video in its place:

[morning, meadow] -> zoom_in -> [morning, zoomed meadow]

The following image call creates the evening scene. The clip therefore still combines three scenes in the original order.

2. Validate the source

clipasm validate learning.clipasm

3. Render the video

clipasm render learning.clipasm

Validation still reports 108 frames because zoom_in preserves duration.

4. Check the result

Open generated/learning.mp4. Confirm that only the meadow moves.

Calls that omit Video or Audio inputs bind matching Video or Audio values from the stack. Their position is therefore part of the program’s meaning.

Next, add a flash between scenes.

5. Add a flash between scenes

You now want a flash between the morning and meadow while keeping the cut to evening unchanged. The transition needs those first two scenes as separate Video values. Individual named clips now become useful.

Continue editing learning.clipasm from Transform one scene.

1. Name each scene

Replace the pictures clip and its reference with three scene clips:

clip {
    image("assets/morning.png", 1500ms, contain)
} as morning

clip {
    image("assets/meadow.png", 1500ms, contain)
    zoom_in(4%)
} as meadow

clip {
    image("assets/evening.png", 1500ms, contain)
} as evening

Each clip stays off the outer stack until a reference places it there. The earlier pictures grouping worked when the whole sequence moved together. Individual scene clips are useful when an operation needs two scenes separately.

2. Assemble the scenes

Append:

$morning
$meadow
$evening
concat

This is the familiar stack sequence: three references leave three Videos, and concat returns one.

3. Validate the assembly

Validate before adding the transition:

clipasm validate learning.clipasm

The result remains 108 frames.

4. Add the transition

Insert flash_cut(200ms) immediately after $meadow:

$morning
$meadow
flash_cut(200ms)
$evening
concat

flash_cut needs a before Video and an after Video. With those inputs omitted, it consumes the two nearest Videos: morning first, then meadow.

$morning  -> [morning]
$meadow   -> [morning, meadow]
flash_cut -> [morning-to-meadow]
$evening  -> [morning-to-meadow, evening]
concat    -> [finished video]

The code references evening only after flash_cut, so evening is not a transition input. One ordinary cut follows the flash transition.

5. Validate the transition

clipasm validate learning.clipasm

Validation still reports 108 frames because flash_cut places its inputs sequentially.

6. Render the result

clipasm render learning.clipasm

7. Check the transition

Open generated/learning.mp4. Confirm that one white flash appears between morning and meadow. Evening follows the transition.

You used named clips to address scenes independently. A fixed-input program then consumed the correct stack values in order.

Next, change a named scene after assembly.

6. Change a named scene after assembly

The edit is now assembled, but its structure is still useful. In this chapter you will name the finished edit and select its evening placement. You will then apply an effect without writing a fixed time range.

Continue editing learning.clipasm from Add a flash between scenes.

1. Save the assembled edit

Replace the final five statements with a named clip:

clip {
    $morning
    $meadow
    flash_cut(200ms)
    $evening
} as edit

$edit

The body produces the morning-to-meadow transition and the evening clip. clip concatenates those values into one Video. The bare $evening reference also gives that occurrence the placement name evening inside edit.

2. Validate the assembled edit

Validate:

clipasm validate learning.clipasm

The result remains one 108-frame Video.

3. Select the evening placement

Add during after $edit:

$edit
during($edit::evening) {
    zoom_in(2%)
}

$edit::evening is the exact range occupied by the named evening occurrence. Unlike 3s..4500ms, the selector continues to identify that scene if an earlier scene changes duration.

during consumes the complete edit. Its body starts with the selected evening slice on the body stack, so zoom_in can consume that slice normally. The during call then splices the body’s result into the original timeline.

4. Validate the revised edit

clipasm validate learning.clipasm

Validation still reports 108 frames.

5. Render the revised edit

clipasm render learning.clipasm

6. Check the timeline edit

Open generated/learning.mp4. The meadow retains its original movement. The evening now has a subtler zoom.

Names have uses beyond reuse. When named values reach a composition, they can become stable placement paths for later timeline edits.

Next, reuse a scene style across source files.

7. Reuse a scene style across source files

The edit applies the same kind of zoom in two places with different amounts. You will extract that familiar operation behind a small callable interface, then use it twice without changing the rendered result.

Continue in the same project. Use learning.clipasm from Change a named scene after assembly.

1. Create the program directory

Create a programs directory.

2. Define the reusable operation

Create programs/scene_motion.clipasm:

clipasm 1

input video: Video
param by: Number = 4%

zoom_in($video, $by)

This source file defines one program. input video declares its Video input, and param by declares a Number parameter with a default. Its final Video returns to the caller.

3. Import the program

In learning.clipasm, add the import after config and before the clip declarations:

import "programs/scene_motion.clipasm" as scene_motion

The path is relative to the file containing the import. scene_motion is the local call name.

4. Replace the meadow operation

In the meadow clip, replace:

zoom_in(4%)

with:

scene_motion

The omitted by parameter uses the program’s 4% default.

5. Replace the evening operation

Inside the final during body, replace zoom_in(2%) with:

scene_motion(2%)

This call overrides the default for the subtler evening movement. Each call consumes the nearest Video from its current stack and passes it into a separate invocation of scene_motion.

6. Validate the complete source package

clipasm validate learning.clipasm

Validation checks both source files and still reports 108 frames.

7. Render the reusable program

clipasm render learning.clipasm

Rendering produces the same 4.5-second edit as before.

8. Change the shared style

In programs/scene_motion.clipasm, change the default:

param by: Number = 6%

9. Validate the style change

clipasm validate learning.clipasm

10. Render the style change

clipasm render learning.clipasm

11. Check the style change

The meadow now uses the stronger 6% default. The evening remains at its explicit 2% override. One interface controls the shared style without removing local choices.

Complete checkpoint

Your finished programs/scene_motion.clipasm should be:

clipasm 1

input video: Video
param by: Number = 6%

zoom_in($video, $by)

Your finished learning.clipasm should be:

clipasm 1

config {
    video {
        width = 320
        height = 180
        fps = 24
    }
    output = "generated/learning.mp4"
}

import "programs/scene_motion.clipasm" as scene_motion

clip {
    image("assets/morning.png", 1500ms, contain)
} as morning

clip {
    image("assets/meadow.png", 1500ms, contain)
    scene_motion
} as meadow

clip {
    image("assets/evening.png", 1500ms, contain)
} as evening

clip {
    $morning
    $meadow
    flash_cut(200ms)
    $evening
} as edit

$edit
during($edit::evening) {
    scene_motion(2%)
}

You have now developed one project through stack composition, named clips, transforms, and a transition. You also used placement-based editing and an imported source program.

Continue with the How-to guides when you have a specific task, or use the Language reference for exact rules.

Check a program before rendering

Use validate for a fast source check while editing. It checks the complete source package without opening media, running FFmpeg or FFprobe, or executing an external program.

Before you start

Run the steps from an initialized project containing main.clipasm. In a repository checkout, substitute examples/scenic-sequence.clipasm.

1. Validate the source

clipasm validate

A successful validation confirms that ClipAsm can parse the package and resolve imports and calls. ClipAsm also binds stack inputs, checks types, and calculates durations from authored data.

It does not confirm that media files exist or that rendering tools are installed. A video-file source may therefore validate with a duration that will resolve later during rendering.

2. Explain the first diagnostic

When validation fails, start at the first reported source location. If the diagnostic includes a code, get its longer explanation:

clipasm explain E_ENTRYPOINT_OUTPUT_COUNT

3. Correct the source

Edit the source at the first reported location.

4. Validate the corrected source

clipasm validate

Continue when validation reports a successful frame count or a duration that will resolve during preflight.

5. Render the checked program

clipasm render

Rendering repeats the source checks. It then opens reachable media, verifies the required tools, and creates the output. validate is useful while you edit, but render does not require it.

See From source to published video for the phase model, Inspect compiled JSON for tooling data, and Troubleshooting for common failures.

Inspect compiled JSON

Use clipasm inspect when you need to debug compiled graph structure or feed ClipAsm’s supported inspection format into another tool. It is not part of the normal edit-and-render loop.

Before you start

Start with a source file that passes clipasm validate. The examples below use an initialized project’s main.clipasm.

1. Inspect standard output

clipasm inspect main.clipasm

The command writes compiled JSON to standard output without opening media, running FFmpeg or FFprobe, or executing an external program.

2. Create a directory for inspection files

mkdir -p local

3. Write a new JSON file

clipasm inspect main.clipasm --output local/compiled.json

The destination must not already exist. Choose a new path or remove the old debugging file before you repeat the command. inspect never overwrites a file.

4. Check the contract version

The document describes project settings, compiled operations, inputs, ordered outputs, named values, and source origins. It also describes known frame or sample counts and the configured publication path.

Inspection JSON is not .clipasm source, a render plan, or a preview. Read its format_version before consuming it in software, and reject versions your tool does not support.

See Compiled inspection JSON for the exact supported contract.

Supply root inputs and parameters

A root source program can ask its caller for Video or Audio inputs and scalar parameters. This guide uses examples/root-bindings.clipasm, which declares one Video, one time range, and one repeat count. You will bind all three and render a two-second MP4.

Before you start

Use a ClipAsm source checkout. Run the commands from its repository root. The committed examples/assets/gentle-motion.mkv supplies the Video. Rendering also requires FFmpeg and FFprobe.

1. Read the declarations

input video: Video
param range: TimeRange
param count: Integer

trim($video, $range)
repeat($count)

None of the declarations has a default. Every compiling command must supply all three values.

2. Bind every required value

$ clipasm validate examples/root-bindings.clipasm
> --video-input video=examples/assets/gentle-motion.mkv
> --arg range=500ms..1500ms
> --arg count=2
valid: 4 semantic value(s), 48 frame(s)

Use:

  • --video-input NAME=PATH for a declared Video
  • --audio-input NAME=PATH for a declared Audio
  • --arg NAME=VALUE for a scalar parameter

Names are case-sensitive and must match the source declarations. Repeat an option when a program declares several values of that kind.

If a binding is missing or misspelled, note the reported diagnostic code. Run clipasm explain <CODE>. Then correct the command and validate again.

3. Render to an explicit output

The example has no configured output, so provide one:

clipasm render examples/root-bindings.clipasm \
  --video-input video=examples/assets/gentle-motion.mkv \
  --arg range=500ms..1500ms \
  --arg count=2 \
  --output root-bindings.mp4

repeat repeats the selected one-second range twice. The result is a two-second Video. CLI-supplied paths resolve from the current working directory. Paths written in a .clipasm file resolve from the directory containing that file.

4. Check the output

Open root-bindings.mp4 in the repository root. Confirm that it lasts two seconds. Use explicit --output because the example does not set config.output.

See Root bindings for all accepted options and Source programs and imports for the broader program model.

Import and call a source program

Use a source program when an operation needs a reusable callable interface. This guide creates a polish program and imports it under a local name. You will then render its result without completing the ordered learning chapters.

Before you start

Create a project so the starter image is available. Then enter the project:

clipasm init imported-video
cd imported-video

Create a programs directory inside the project.

1. Define the program

Create programs/polish.clipasm:

clipasm 1

input video: Video
param by: Number = 6%

zoom_in($video, $by)

The file accepts one Video and an optional zoom amount. Its final Video returns to the caller.

2. Create the composition

Create composition.clipasm in the project root:

clipasm 1

config {
    video {
        width = 320
        height = 180
        fps = 24
    }
    output = "generated/imported-program.mp4"
}

import "programs/polish.clipasm" as polish

image("assets/morning.png", 2s, contain)
polish(10%)

The import path is relative to composition.clipasm. polish is a local call name. The call consumes the Video on the stack. It also overrides the program’s default by parameter.

3. Validate the package

clipasm validate composition.clipasm

Validation checks both source files and reports 48 frames without opening the PNG.

4. Render the result

clipasm render composition.clipasm

5. Check the result

Open generated/imported-program.mp4. Confirm that the morning image zooms for two seconds.

See Imports for exact path, alias, isolation, and cycle rules.

Add or replace a soundtrack

Use set_audio to attach standalone Audio to a Video or replace Audio the Video already carries.

Before you start

Create an initialized project. Add these media files:

assets/scene.mp4
assets/soundtrack.wav

The Video and Audio may have different durations. The resulting Video keeps the Video timeline. set_audio trims longer Audio and pads shorter Audio to match the Video duration.

1. Create the source file

Create soundtrack.clipasm:

clipasm 1

config {
    video {
        width = 1920
        height = 1080
        fps = 30
    }
    output = "generated/with-soundtrack.mp4"
}

video("assets/scene.mp4", contain)
audio("assets/soundtrack.wav")
set_audio

The first two calls leave one Video and one Audio value. set_audio binds each input by its exact type. It replaces the Video’s Audio and leaves one Video.

2. Validate the structure

clipasm validate soundtrack.clipasm

Validation checks the source without opening either media file. File-backed durations may remain deferred until rendering.

3. Render the video

clipasm render soundtrack.clipasm

4. Check the soundtrack

Open generated/with-soundtrack.mp4. Confirm that its picture comes from scene.mp4 and its sound comes from soundtrack.wav.

See set_audio for its exact contract and Stack binding for mixed Video and Audio inputs.

Review and run an external program

An external program exposes an ordinary typed ClipAsm interface but performs its work in another executable. Treat it like any other native program you are considering for execution.

Warning: rendering a reachable external program executes it with your user permissions. ClipAsm does not sandbox it, impose a timeout, or prevent file, network, or process access.

This guide uses examples/external-brighten.clipasm. Run commands from the repository root. The result is a two-second brightened MP4.

Before you start

Use a ClipAsm source checkout with Python 3, FFmpeg, and FFprobe on PATH. External programs are advanced, trusted integrations. Do not render code that you have not reviewed.

1. Review the project-controlled code

Open these project files before rendering:

  • examples/external-brighten.clipasm, the wrapper
  • examples/programs/brighten/program.clipasm, the external declaration
  • examples/programs/brighten/brighten.py, the executed script

The declaration chooses python3 and passes the script as a declared file argument. It promises that the output keeps the input Video’s exact duration and audio state. The script receives a versioned JSON request on standard input. It uses the FFmpeg path that ClipAsm provides. The request also provides the exact Video pixel/color encoding and Audio sample encoding required for the output artifact. ClipAsm probes those fields after the process exits.

The python3, FFmpeg, and FFprobe binaries are also executable dependencies.

2. Confirm the executable dependencies

Confirm that the command lookup in your environment resolves to installations you trust.

3. Validate the ClipAsm source

clipasm validate examples/external-brighten.clipasm

4. Inspect the compiled program

clipasm inspect examples/external-brighten.clipasm

These commands check the package and typed call. They do not locate or execute Python, the script, or FFmpeg. They cannot tell you whether the code is safe.

5. Render only after review

clipasm render examples/external-brighten.clipasm

Before execution, ClipAsm resolves and hashes the executable, declared File arguments, and File-valued parameters. It hashes reached dependencies again, sends the request, and verifies the produced media before accepting it. The example writes examples/external-brighten.mp4.

6. Check the rendered video

Open examples/external-brighten.mp4. Confirm that it is a two-second brightened version of the input. External code can still read undeclared state. A successful render does not make an unreviewed program safe or reproducible.

See External implementations for the declaration and External programs and the trust boundary for the security and reproducibility model.

Troubleshooting

Every ordinary ClipAsm error includes a diagnostic code. Explain the code with clipasm explain <CODE> for a concise explanation. You can also search the diagnostic reference for the complete catalog. The sections below follow common symptoms. The diagnostic reference contains the full advice and retry guidance for each code.

Diagnostic workflow

  1. Run clipasm validate SOURCE to separate source and binding problems from media, tool, and execution problems.
  2. If validation fails, inspect the first reported source location.
  3. If the diagnostic has a code, run clipasm explain <CODE>.
  4. Correct the source or binding problem.
  5. Run clipasm validate SOURCE again.
  6. When validation succeeds, run render. Rendering repeats source checks and then reports reachable media, tool, external-process, cache, or publication problems.
  7. Use inspect only when the compiled graph or JSON integration is itself the question. Rendering does not require inspect.

The source does not validate

Run:

clipasm validate path/to/program.clipasm

Start at the first reported source location. Common causes include invalid declaration order, an unknown program or argument, and a missing stack input. Other causes include a type mismatch, an invalid import, or an output-name dependency cycle.

Validation checks the complete linked package, including imported programs that the root does not call. An unused import can therefore make validation fail. Correct or remove the invalid imported source rather than expecting reachability to hide it.

Consult the language reference for exact syntax and the stack-binding reference for binding rules. The parsing and source, imports and declarations, and types and stack diagnostic sections group the corresponding failures.

A root input or parameter is missing

Every command that compiles the root source requires root declarations without defaults. Supply the required input and param values:

clipasm validate path/to/program.clipasm \
  --video-input video=path/to/input.mp4 \
  --arg count=2

Binding names are case-sensitive and must match the declarations. Repeat --video-input, --audio-input, and --arg for multiple bindings. CLI media and File paths resolve from the current working directory.

See Supply root inputs and parameters.

Validation defers a duration

A message that duration resolves during preflight is not an error. Compilation does not open authored media, so a file-backed source may not yet have an exact frame or sample count.

Render the program when you are ready for ClipAsm to resolve and probe reachable media:

clipasm render path/to/program.clipasm

A media file cannot be found

Check which component authored the path:

  • Paths in a .clipasm file resolve from that source file’s directory.
  • Import paths resolve from the importing source file.
  • CLI media and File bindings resolve from the working directory.
  • An output override resolves from the working directory.

Imported programs keep their own path base. Moving only the root source or changing the working directory does not rebase paths in an imported source file.

See preflight and media diagnostics when the reported code concerns an unreadable or unsuitable asset.

FFmpeg or FFprobe is unavailable

validate and inspect do not require media tools. Rendering requires both ffmpeg and ffprobe on PATH:

ffmpeg -version
ffprobe -version

If ClipAsm cannot find installed commands, check your environment. Make sure that PATH includes the corresponding executables.

See preflight and media diagnostics for tool discovery and capability failures.

FFmpeg lacks a required capability

ClipAsm checks the encoders, muxers, and filters required by the reachable work needed for the output. Install an FFmpeg build that provides the named capability. Alternatively, remove the operation that requires that capability.

Capabilities needed only by unreachable operations do not reject the render. External programs are responsible for any additional FFmpeg features they invoke themselves.

Rendering has no output path

The root source can declare config.output, or the caller can provide an override:

clipasm render path/to/program.clipasm \
  --output local/result.mp4

The destination must use the .mp4 extension. ClipAsm also requires exactly one publishable Video among the root program’s ordered outputs.

ClipAsm rejects the output or manifest destination

ClipAsm transactionally replaces existing regular MP4 and manifest files while preserving them if publication fails. It rejects unsafe destination collisions. Choose a different output path if a reachable input asset occupies either destination. Do the same for an external executable or an incompatible filesystem object.

Do not point output at a source asset. Publication writes both the MP4 and <output>.manifest.json.

See rendering and publication diagnostics for the reported destination or publication code.

An external program fails or hangs

External programs are trusted native code. ClipAsm does not sandbox them or set an execution timeout. Review the external declaration, executable, scripts, and declared file arguments before rendering.

Run validate and inspect first. These commands do not execute the external process. If rendering fails, reproduce the problem with the smallest trusted project. Then inspect the process’s reported failure. Use the operating system’s normal controls to stop a process that hangs.

See Review and run an external program and External programs and the trust boundary. The external-program diagnostics section explains protocol and process failures.

ClipAsm does not reuse a cached artifact

Cache reuse requires matching semantic, prepared, tool, and artifact identities. Changes to source meaning or media bytes can produce a cache miss. Changes to declared external files or project settings can also produce a miss. FFmpeg and FFprobe build changes can have the same result.

A cache miss is not a correctness failure. ClipAsm renders the missing artifact and stores a verified replacement. Do not edit cache artifacts or sidecars by hand.

For a cache lock or filesystem error, use the cache and filesystem diagnostics section to determine whether retrying is appropriate.

Inspection output differs from your expectations

inspect prints compiled JSON. It does not print source code, a render plan, or a rendered preview. Focus on graph relationships such as nodes, outputs, and named_values. Source metadata, hashes, and format details can change with the internal serialization.

Use the pipeline explanation to distinguish compiled semantics from preflight and rendering.

ClipAsm reports an internal diagnostic

An internal-contract diagnostic usually means user input exposed a ClipAsm defect rather than a source mistake. Preserve the diagnostic code, ClipAsm version, safe reproduction steps, and the original output. Do not delete caches or generated state unless that code’s explanation specifically recommends it.

Report a minimal reproduction through the repository’s issue tracker, but do not post private source, media, credentials, or sensitive paths. Use the private security reporting route below when the failure may have security impact.

Reporting a possible security issue

Do not post exploit details or sensitive inputs in a public issue. Follow the repository’s security policy.

From source to published video

Three commands expose the main stages of ClipAsm:

CommandOpens media?Runs tools or external programs?Main result
validateNoNosource and type check
inspectNoNocompiled JSON
renderYes, when reachableYes, when requiredMP4 and manifest

This separation lets you catch source problems quickly and lets unused media stay unopened.

1. Read and check the source

ClipAsm parses the root .clipasm file and its imports, then checks every linked source program. It resolves calls, arguments, types, stack inputs, names, and ordered outputs.

This stage does not open authored media. Durations written directly in source can already be exact. Durations that depend on a media file remain unknown until rendering.

An unused imported program must still be valid source because the compiler checks the complete linked package.

2. Prepare reachable media and tools

During render, preflight starts from the one Video selected for publication and follows only the work needed to produce it. It resolves paths, hashes source assets, probes media, checks required FFmpeg capabilities, and locates reachable external executables.

This means an unused import can contain an unused missing media file without blocking rendering, as long as the imported source itself is valid.

3. Execute, verify, and publish

ClipAsm reuses verified cached artifacts when possible and executes the missing work in dependency order. With fused materialization, compatible FFmpeg operations that lead to one materialized endpoint share one filter graph. Branches may join that region when they divide a Video’s picture and Audio without duplicating either physical stream. Cache hits, duplicated streams, temporal joins, external programs, input-scoped FFmpeg behavior, and branches that require different materialized endpoints remain artifact boundaries. Keeping temporal-join inputs separate prevents fused preprocessing on a later branch from being buffered for the length of an earlier one. Cache retention and intermediate materialization are separate settings. External programs reached here run as trusted native code.

The renderer checks produced media before it enters the cache or replaces the published output. A successful render writes the MP4 and a sibling manifest.

Rendering requires exactly one Video output. Additional Audio outputs may exist, but ClipAsm does not publish them separately.

Terms used in reference pages

  • compiled program: the checked media-independent result used by inspect
  • preflight: the media and tool resolution that render performs
  • prepared plan: the exact reachable work after preflight
  • publication: verification and final replacement of the MP4 and manifest

See the command-line reference for exact command behavior.

Color and linear-light processing

ClipAsm currently has one project color profile:

config {
    video {
        color = sdr_bt709
    }
}

The setting defaults to sdr_bt709, but writing it makes the project intent visible. It is one profile instead of four independent switches. Primaries, transfer, matrix, and range describe different parts of a signal, but arbitrary combinations are not necessarily meaningful or supported.

The SDR BT.709 contract

Project Video uses BT.709 primaries, BT.709 transfer, BT.709 non-constant- luminance Y’CbCr coefficients, and limited range. ClipAsm stores working Video as 10-bit 4:4:4 FFV1 with signed-16-bit FLAC Audio. It publishes 8-bit 4:2:0 H.264 with left-positioned chroma. Both forms carry explicit color metadata, and ClipAsm verifies it after every render step.

Pixel format and color meaning are separate. yuv420p describes component layout and depth. It does not by itself say whether samples are BT.709, BT.2020, full range, limited range, SDR, or HDR.

Source rules

Still images have an authored convention. Opaque untagged RGB images are sRGB. JPEG Y’CbCr is interpreted as full-range BT.601 with centered chroma before it is converted. ClipAsm currently rejects alpha and embedded ICC profiles because correct support needs explicit compositing and ICC conversion policies.

Video files do not have a safe equivalent default. A video(...) source must state BT.709 primaries, transfer, matrix, range, and chroma location when its pixel format is subsampled. Missing metadata is rejected. ClipAsm does not guess from frame size, codec, or file extension.

PQ (smpte2084), HLG (arib-std-b67), and HDR mastering metadata are rejected under the SDR profile. Converting BT.2020 coordinates into BT.709 coordinates is not tone mapping. A future HDR-to-SDR policy must define reference white, nominal or mastering peak, target display, tone-mapping operator, and metadata handling before it can produce predictable results.

Display-linear pixel math

Encoded BT.709 samples are not proportional to displayed light. Averaging two encoded code values therefore does not produce an optical midpoint. ClipAsm converts picture data to full-range floating-point linear BT.709 RGB before:

  • source fitting and resize interpolation;
  • zoom_in perspective interpolation;
  • the white fade in flash_cut;
  • Video crossfade blending.

It then converts the result back to the canonical 10-bit working signal. Trim, repeat, concat, and other routing-only operations preserve the canonical samples without a conversion round trip.

“Linear” here is display-linear. For mastered BT.709 Video, zimg applies its BT.1886-style display EOTF. This is not the inverse BT.709 camera OETF and is not scene-linear radiance. ClipAsm fixes nominal peak luminance at 100 cd/m² and disables zimg’s approximate-gamma option so this behavior stays stable and identity-bearing.

Standards basis

  • ITU-R BT.709 defines the HDTV primaries, signal transfer, and Y’CbCr coefficients.
  • ITU-R BT.1886 defines the reference SDR display EOTF.
  • ITU-R BT.2100 defines PQ and HLG HDR television systems.
  • ITU-T H.273 defines independent code points for primaries, transfer, matrix coefficients, and range-related video signal metadata.

See From source to published video for the surrounding pipeline and Files and configuration for authored settings.

Stack ownership and visibility

The learning chapters show the everyday rule: a call with omitted Video or Audio inputs consumes matching values produced nearby. This page explains what happens when bodies and nested compositions create more than one stack frame.

Values and occurrences are different

A Video or Audio value is an immutable graph result. A statement places an occurrence of that value on a stack. A program consumes occurrences and returns new values.

Referencing a name creates another usable occurrence without copying or moving the underlying graph:

image("title.png", 1s) as title
$title
$title
concat

concat can consume both references. The original named value continues to identify the same immutable result.

Bodies create ownership boundaries

Each source-program invocation and program body owns the occurrences created directly in its stack frame. Ownership prevents an inner operation from accidentally consuming unrelated values created by a caller or enclosing body.

Most direct built-ins and imported programs use owned access: omitted inputs may come only from the current owned frame. join and during use visible access because their bodies commonly need the values those programs provide.

Explicit access is local

@owned restricts one block or call to the current ownership frame. @visible allows one call to search enclosing visible frames until it reaches an owned boundary:

@owned {
    image("inside.png", 1s)
    @visible concat
}

The owned block stops the visible concat from reaching Videos outside the block. Access annotations apply only to the form that they prefix. They do not change every nested operation.

Use explicit access only when a nested composition genuinely needs different visibility. Ordinary linear compositions should rely on each program’s documented default.

Names do not create lexical graph scope

Stack ownership and name visibility are separate. A graph name created in a nested body remains available throughout the containing source-program invocation. Temporary body-input names such as $before, $after, and $timeline exist only while that body is active.

A bare { ... } stack block groups work. It returns every child-stack value left inside it. The block is not a lexical name scope.

clip { ... } combines one timeline type and removes its temporary outer occurrence. An optional name remains available for later references.

See Stack binding for exact selection rules and Composition forms for clip, stack blocks, names, and references.

Source programs and imports

Every .clipasm file defines one callable source program. An import makes that program available under a local alias. It does not paste the file’s text into the caller.

One file, one interface

A source program may declare Video or Audio inputs and scalar parameters. It returns the values left by its body. The root file may also configure the project and publication output.

Imported files cannot set root project or output configuration.

Imports create local aliases

import "programs/polish.clipasm" as polish

The path is relative to the file that contains the import. Each import requires an alias that is local to that file. It cannot replace a built-in name. ClipAsm rejects import cycles and recursive source-program calls.

Call the imported program like a built-in:

video("assets/scene.mp4")
polish(10%)

ClipAsm isolates calls

Each call gets its own local stack, inputs, parameters, and names. Those names do not leak back to the caller. Only the program’s final ordered values return. Calling the same imported program twice therefore creates two independent invocations.

Paths keep their source

A relative path keeps the source-file base from its authoring location:

  • an import path resolves from the importing file
  • a media or default File path resolves from the file that contains it
  • a value supplied by a caller keeps the caller’s path base
  • a CLI-supplied path resolves from the current working directory

This allows a reusable imported program to keep assets beside its own source.

ClipAsm checks the complete package

Validation checks every linked imported source program, even when the root does not call it. Rendering later opens only media and tools reachable from the Video that ClipAsm publishes.

Follow Import and call a source program for the complete task workflow. Chapter 7, Reuse a scene style across source files, introduces imports within the learning project. See Imports for exact syntax.

External programs and the trust boundary

An external program looks like an ordinary typed ClipAsm program to its caller, but rendering delegates one operation to another executable.

Warning: a reachable external program runs with your user permissions. Importing or validating it does not execute it. Rendering does.

What validation checks

Validation checks the declaration, inputs, parameters, defaults, and ordinary call behavior. It records the external operation without locating or executing the executable.

The current interface supports fixed Video or Audio inputs, Integer, File, or Keyword parameters, and exactly one Video output. The declaration identifies the input whose duration and audio state the output preserves.

What rendering checks

Before execution, ClipAsm locates and hashes the executable. It also locates and hashes declared File arguments and File-valued parameters. ClipAsm sends a versioned JSON request over standard input. It passes the executable and arguments separately instead of building a shell command.

A zero exit status is not enough: ClipAsm probes the produced artifact and checks it against the declared Video result before accepting it.

An external implementation must complete its declared output before the direct process exits. ClipAsm contains the invocation in a dedicated process group on Unix. On Windows, ClipAsm uses a Job Object. ClipAsm terminates remaining managed descendants when the direct process finishes. Work that deliberately escapes the managed process group or Job Object is outside the protocol contract.

What ClipAsm cannot make safe

ClipAsm does not sandbox the process, limit its runtime, or prevent access to the filesystem, network, environment, or other processes. It cannot discover hidden inputs such as:

  • environment variables
  • clocks or random state
  • network responses
  • imported modules
  • undeclared files

Persistent caching assumes an external implementation is deterministic for everything that ClipAsm identifies. This includes executable and declared File bytes, semantic_version, arguments, parameters, project settings, and input artifact bytes. Repeating the same identified invocation must produce equivalent output.

Clock time, randomness, network responses, mutable environment state, and undeclared files violate that contract. They can make the external node and its cached descendants stale or inconsistent.

Authors must declare File dependencies where supported. They must update semantic_version whenever other output-affecting behavior changes.

If an implementation cannot satisfy this deterministic contract, do not rely on persistent reuse. Remove the project’s .clipasm/ state before each render. Use this method until ClipAsm provides an explicit nonpersistent execution policy.

Hashing reduces accidental drift but does not create an immutable snapshot. A file can still change between the final hash and the external process reading it.

Review and trust every declaration, executable, script, and file argument before rendering. See Review and run an external program for a safe workflow and External implementations for exact declaration rules.

Runnable examples

The repository’s examples/ directory contains small programs for development and experimentation. Run the commands below from the repository root. validate checks source only. render also requires FFmpeg and FFprobe.

For a standalone project without a repository checkout, use clipasm init and follow Get ClipAsm running.

Example catalog

ExampleShowsExpected renderExtra requirementRelated page
examples/scenic-sequence.clipasmclip, image sources, a name, and a reference4.5 secondsChapter 3: Name and reference a clip
examples/learning-journey.clipasmthe completed learning path4.5 secondsLearn ClipAsm
examples/crossfade.clipasmexact crossfade overlap3.5 secondscrossfade reference
examples/gentle-motion-edit.clipasmduring and zoom_in2 secondsduring reference
examples/reusable-composition.clipasmnamed clips, references, and stack-bound flash_cut3 secondsTransition chapter
examples/imported-program.clipasmimporting a ClipAsm source program2 secondsImport how-to
examples/external-brighten.clipasmtrusted external implementation2 secondsPython 3 and a code reviewExternal-program guide
examples/root-bindings.clipasmroot Video input and required parameters2 secondsCLI bindings and --outputRoot-bindings guide

Validate or render an example

Most examples use the same two-command pattern:

clipasm validate examples/scenic-sequence.clipasm
clipasm render examples/scenic-sequence.clipasm

Git ignores generated outputs, manifests, and caches.

Root bindings

This example requires one Video input and two scalar parameters:

clipasm validate examples/root-bindings.clipasm \
  --video-input video=examples/assets/gentle-motion.mkv \
  --arg range=500ms..1500ms \
  --arg count=2

clipasm render examples/root-bindings.clipasm \
  --video-input video=examples/assets/gentle-motion.mkv \
  --arg range=500ms..1500ms \
  --arg count=2 \
  --output root-bindings.mp4

External program

examples/external-brighten.clipasm may execute Python and FFmpeg during rendering. Review the declaration and script before running it:

clipasm validate examples/external-brighten.clipasm
clipasm inspect examples/external-brighten.clipasm
clipasm render examples/external-brighten.clipasm

External programs are trusted native code. Read Review and run an external program first.

Language reference

Every source file uses the .clipasm extension and begins with clipasm 1. Choose the topic you need:

TopicPage
file layout, project settings, inputs, parameters, and outputFiles and configuration
exact numbers, durations, expressions, and scalar aliasesScalar values and expressions
calls, optional syntax, bodies, and generic type selectionStatements and calls
implicit inputs, explicit graph arguments, ownership, and visibilityStack binding
clip, stack blocks, graph names, and referencesComposition forms
placement paths, marker ranges, and timeline coordinatesTimeline selectors and ranges
source imports and trusted external implementationsImports and external programs
built-in call signatures and examplesBuilt-in programs
CLI source and root bindingsCommand-line reference

The formal grammar is the normative EBNF for language version 1. Learning chapters and guides explain common workflows. These reference pages own exact authored behavior.

Files and configuration

A source file uses the .clipasm extension and begins with:

clipasm 1

Declarations come next. Executable statements start after the declarations. Declarations cannot appear later in the file.

Layout

The lexer ignores spaces, tabs, and indentation. Newlines separate statements and configuration fields. There is no semicolon syntax.

A block containing one statement may fit on one line:

clip { image("title.png", 2s) } as title

Multiple statements require newlines:

clip {
    image("title.png", 2s)
    zoom_in(8%)
} as title

This is invalid because the two statements have no separator:

clip { image("title.png", 2s) zoom_in(8%) }

The parser accepts newlines inside parentheses around comma-separated arguments. Comments begin with # and continue to the end of the line.

Configuration and declarations

clipasm 1

config {
    video {
        width = 1920
        height = 1080
        fps = 30000/1001
        color = sdr_bt709
    }
    audio {
        sample_rate = 48000
    }
    output = "generated/final.mp4"
}

input source: Video
param title: File = "assets/title.png"
param duration: Duration = 2s
param amount: Number = 8%
param count: Integer
param range: TimeRange
param fit: Keyword(cover, contain, stretch) = contain

Graph input types are Video and Audio. Scalar parameter types are Number, Integer, File, Duration, TimeRange, and a declared Keyword(...) set. Another program or the CLI must supply parameters that have no defaults.

Only the root file may set project media configuration or an output path. Omitted fields use width = 1280, height = 720, fps = 30, color = sdr_bt709, and sample_rate = 48000. Project audio is stereo, and publication is MP4 only. Color is one closed profile instead of independent primaries, transfer, matrix, and range settings, so authored configuration cannot create an incoherent signal description.

Frame rate is an exact positive rational. fps = 30 means exactly 30 frames per second. fps = 30000/1001 is approximately 29.97 frames per second. This explicit non-integer rate produces different frame counts for many durations. It is not the default.

Scalar values and expressions

ClipAsm evaluates numbers and durations exactly rather than with binary floating point.

Numbers and integers

ClipAsm represents Number values as reduced rational values. Integer literals, decimals, percentages, arithmetic, and scalar references remain exact:

param by: Number = 8%
param count: Integer = 6 / 2

image("title.png", 1s)
zoom_in($by)
repeat($count)

Operators use this precedence from loosest to tightest:

  1. the TimeRange operator ..
  2. addition and subtraction
  3. multiplication and division
  4. unary + and -
  5. postfix %, ms, s, and f
  6. primary values and parenthesized expressions

Postfix operators may repeat. % divides a Number by 100, so 800%%, 8%, 0.08, and 2 / 25 are the same exact value and have the same semantic identity.

Integer is the refinement of Number whose exact reduced denominator is one. Constraints apply after evaluation:

repeat(6 / 2) # valid: evaluates to Integer 3
repeat(5 / 2) # error: evaluates to 2.5, exactly 5/2

Durations

ms, s, and f require an Integer result and construct Duration. They bind to the immediately preceding expression:

image("short.png", (6 / 2)ms) # 3ms
image("card.png", 15f)         # exactly 15 project video frames
image("bad.png", (5 / 2)ms)   # error: ms requires Integer
image("bad.png", 5 / 2ms)     # error: Number / Duration is undefined

ClipAsm resolves f on the configured project video frame grid. It is useful for machine-generated edits. Boundaries remain exact even when the nanosecond authoring grid cannot represent one frame:

config { video { fps = 30 } }

image("card.png", 15f)
trim(3f..15f)
flash_cut(3f)

ClipAsm uses a project-frame range directly for Video. For Audio, each frame boundary maps to the corresponding boundary on the configured project sample grid. At 30 fps and 48 kHz, 3f..8f maps exactly to samples 4800..12800. Cumulative boundaries do not drift.

Duration is distinct from Number. Both unit families support unary signs, addition, and subtraction, but one expression cannot mix wall-clock and project-frame values:

image("long.png", 100s - 100ms)
offset = -5f
image("exact.png", $offset + 20f)
during((1s + 500ms)..3s) { repeat(2) }
image("bad.png", 1s + 3f) # error: Duration families differ

Intermediate scalar results can be negative. At a program parameter boundary, wall-clock Duration must be nonnegative. It must also have an exact representation on ClipAsm’s nanosecond authoring grid. Project-frame Duration must be a nonnegative integer within the supported frame count. Both endpoints of a range must use the same unit family.

Either family may offset a timeline coordinate. Project-frame offsets remain on the frame grid until the final Video frame or Audio sample boundary is resolved:

trim(
    range=($edit::start + 3f)..($edit::end - 3f),
)

See the normative grammar for the complete syntax.

Scalar aliases

Immutable scalar aliases name inferred scalar expressions without adding a value to the media stack:

length = 500ms
count = 6 / 2

image("card.png", $length)
repeat($count)

Each program body is a scalar scope. Aliases in that body may refer forward to one another. Aliases from enclosing bodies remain visible. A nested alias does not escape its body. Sibling bodies may reuse the same name.

An alias cannot shadow a visible alias. It also cannot collide with a program input, parameter, or named graph value.

When the compiler checks aliases

The compiler checks the structure of every alias in a body. References must resolve, operators must type-check, and the compiler rejects dependency cycles. These checks also apply to unused aliases. Exact evaluation occurs only when a scalar use reaches the alias. Errors such as unused division by zero do not occur until use. The same rule applies to mixed timeline roots, out-of-bounds coordinates, and destination parameter failures.

Timeline selectors in aliases may capture lexical body inputs. They do not borrow a contextual timeline root from a later invocation.

See Timeline selectors and ranges for placement selectors, timeline coordinates, and marker arithmetic.

Statements and calls

A statement calls a program, reads a named value, creates a scalar alias, or runs a structural block. This page covers program-call syntax.

Call shape

The complete statement form is:

@access name<Type>(arguments) { body } as output

Each part is optional only when the target allows it:

image("title.png", 2s, contain)
concat<Audio>
@visible repeat(2)
during(1s..3s) {
    zoom_in(2%)
}
operation as result
operation as (first, second)
operation as (first, _, third)
  • @owned or @visible chooses stack access.
  • <Video> or <Audio> selects a generic type.
  • (arguments) supplies graph or scalar inputs.
  • { body } supplies a body to a body program or language form.
  • as output binds one or several output positions.

Output bindings

An output name creates a graph reference and a timeline placement label for its position:

operation as result
operation as (first, second)

Use _ to leave a position unnamed:

operation as (first, _, third)

The wildcard still occupies an output position, so binding arity remains exact. It creates no $_ reference and no _ timeline placement. It also does not discard the value: the unnamed output remains on the stack for later calls. Multiple _ slots are allowed. For a single-output statement, as _ is the explicit wildcard form and has the same stack effect as omitting as.

Omitting empty syntax

At statement position, a zero-argument call may omit ():

concat
repeat(2)

Inside an argument expression, a program call must keep its parentheses:

set_audio(video=video("picture.mp4"), audio=audio("sound.wav"))

An unparenthesized identifier inside an argument is a scalar atom, not a call.

For a construct that accepts a body, omitted braces mean an empty body. These forms are equivalent:

join
join()
join {}
join() {}

Normal input and body-output requirements still apply. Bare join still needs two matching timelines. A direct program that does not accept a body rejects braces.

Generic type selection

The compiler usually infers whether a generic call uses Video or Audio. Write an explicit type when both are accessible or when deliberate selection improves clarity:

concat<Video>
drop<Audio>

See Stack binding for arguments and access modifiers. See Composition forms for clip, blocks, and names. See Built-in programs for exact call shapes.

Stack binding

A call can receive Video or Audio values explicitly through arguments or implicitly from the accessible stack.

Scalar arguments

Positional scalar values bind parameters in declaration order. Named scalar arguments use =:

image("title.png", duration=2s, fit=contain)

Positional graph expressions

A graph-producing positional expression behaves like a preceding statement in the current stack frame:

flash_cut(
    image("before.png", 2s),
    image("after.png", 2s),
    160ms,
)

is equivalent to:

image("before.png", 2s)
image("after.png", 2s)
flash_cut(160ms)

ClipAsm evaluates the expressions in source order.

Implicit stack inputs

If a call omits graph inputs, ClipAsm selects accessible values by exact type. For fixed inputs, it works from the program’s last input to its first and takes the nearest matching occurrence for each.

A variadic program such as concat consumes every accessible value of the selected Video or Audio type in physical stack order. Values of another type stay where they are.

Use <Video> or <Audio> when both generic choices are possible.

Named graph inputs

ClipAsm evaluates a named graph input in an isolated input body:

set_audio(
    video=video("picture.mp4"),
    audio=audio("sound.wav"),
)

It supplies that input directly and does not consume a value from the caller’s stack. A named graph input must produce exactly one value of the required type.

Do not mix positional graph expressions and named graph inputs in one call. Named scalar arguments may still accompany positional graph expressions.

Ownership and visibility

@owned allows a call to consume only occurrences created by the current body. @visible may also reach occurrences created by enclosing bodies, stopping at the nearest owned boundary.

Most direct built-ins and imported programs default to owned access. join and during default to visible access. The setting applies to one invocation and does not automatically propagate into its body.

@owned {
    image("inside.png", 1s)
    @visible concat
}

The owned block prevents the inner visible call from reaching values outside the block.

See Stack values, ownership, and visibility for an example-led explanation.

Composition forms

ClipAsm lets you compose work in three related ways:

  • Callable programs create or transform values.
  • A stack block groups statements and returns the values left by that group.
  • clip is shorthand for building one reusable timeline value.

From an author’s perspective, all three are composition tools. Only callable programs have registered names and call signatures. The clipasm programs command lists built-ins such as image, concat, and during. It does not list clip or a bare { ... } block.

clip

Use clip when a group of statements should become one named Video or Audio:

clip {
    image("title.png", 2s)
    zoom_in(8%)
} as opening

$opening

The body must leave one or more values of one timeline type. ClipAsm concatenates them in order, assigns the optional name to the result, then removes the temporary outer-stack occurrence. The name remains available through $opening.

The equivalent explicit form is:

@owned {
    image("title.png", 2s)
    zoom_in(8%)
    @owned concat
} as opening
@owned drop

The compiler performs that expansion in memory. Diagnostics still refer to the authored clip, not the generated concat or drop.

clip accepts no scalar arguments. Use a type argument such as clip<Audio> when the compiler cannot infer the result type.

Stack blocks

A stack block groups statements and returns every value produced by the block that remains on its child stack, in order:

{
    video("picture.mp4")
    audio("sound.wav")
} as (picture, sound)

Unlike clip, a stack block does not combine the returned values and does not remove them. It can therefore return zero, one, or several values.

A plain block permits explicitly visible operations inside it to reach outward. Use @owned { ... } when the block must create an ownership boundary. Programs inside a block still use their own default access rules.

A stack block is structural. It is not a callable program and does not create a lexical scope for graph names.

Names and references

image("title.png", 2s) as title
$title

as name requires exactly one output. as (first, second) names an exact ordered multi-output result. Naming does not consume or move a value.

Graph names are immutable and unique within one source-program invocation. Names created in nested bodies or blocks remain available in the containing source program. The compiler permits forward references when it can resolve their dependencies. Cycles are errors.

Body-input names such as $before, $after, and $timeline exist only while that body is active. They temporarily shadow an outer graph name with the same name. Scalar aliases follow separate lexical-scope rules.

Choosing a form

Use a normal program call for one known operation. Use clip for a reusable single timeline assembled from several statements. Use a stack block when you need explicit grouping, multiple outputs, or precise ownership behavior.

Timeline selectors and ranges

Composed Video and Audio timelines expose native-grid placement markers. Video boundaries are exact project frames. Audio boundaries are exact project samples. Explicit names on values that reach a final concatenation become placement names:

clip {
    image("title.png", 1s) as intro
    image("credits.png", 2s) as credits
} as edit

$edit
during($edit::credits) {
    zoom_in(2%)
}

Selector paths

Authors can nest selector paths, such as $edit::chapter::interview::start. A placement selector without a final boundary denotes its complete closed-open range. Terminal ::start, ::middle, and ::end select exact coordinates and remain reserved as boundary words. A placement with one of those spellings requires an additional boundary component. For example, use $edit::middle::start..$edit::middle::end. Bare $edit::middle always remains the midpoint of $edit.

A uniquely placed bare reference contributes its reference name implicitly. Identity-preserving programs such as zoom_in retain that marker. When an operation has already bound its timeline, a selector may omit leading ancestors when the remaining suffix identifies one addressable descendant. For example, $interview::start or $chapter::interview::start may stand for a longer path under the bound root.

Multiple matches are ambiguous and require more leading names or the owning timeline. The compiler searches the shared view DAG without expanding every reused occurrence. Explicitly rooted selectors remain exact paths.

Names define the path

Selector structure follows names rather than operation history. Anonymous composition layers are transparent, so these forms expose the same direct a and b placements:

image("a.png", 1s) as a
image("b.png", 1s) as b
concat
concat as edit
image("a.png", 1s) as a
image("b.png", 1s) as b
join { concat } as edit

Both accept $edit::a and $edit::b. Anonymous one-input concatenation, stack-block boundaries, and associative regrouping do not add path components. Naming an occurrence does create a boundary:

image("a.png", 1s) as a
image("b.png", 1s) as b
concat as pair
image("c.png", 1s) as c
concat as edit

trim(value=$edit, range=$edit::pair::a)

Here $edit::a is invalid because the authored pair boundary cannot be skipped. A name continues to denote the exact view captured at its declaration, even when later anonymous composition wraps that occurrence.

At one parent level, a placement spelling is addressable only when exactly one occurrence has that spelling. Explicit as labels, inferred bare-reference labels, and operation-created labels do not shadow one another. Any duplicate spelling is ambiguous and needs a distinct explicit name.

Trimming and replacing ranges

Use marker ranges with the timeline that owns their root. join preserves the exact views of untouched inputs and exposes named values created by its body as placements in the joined result. trim and during accept rooted marker ranges for both Video and Audio. trim preserves child placements only when their complete closed-open region is provably inside the selected range. It rebases their starts to the trimmed timeline. The compiler omits partially surviving or symbolically uncertain placements.

A trimmed occurrence keeps its own placement label when later composed.

Audio uses the same selector, contextual-suffix, and interval-replacement rules:

audio("intro.wav") as intro
audio("song.wav") as song
join as mix

during(timeline=$mix, range=$mix::song) {
    repeat(2)
}

during splices timeline layouts as well as media. Base placements fully before the replaced range keep their coordinates. Placements fully after it shift by the replacement-duration delta. Placements that intersect the replaced range, or whose side the compiler cannot prove symbolically, do not survive.

The result exposes the inserted body as ::replacement and retains its nested layout. The during result contract reserves that spelling. If a base placement named replacement survives the edit, compilation reports E_TIMELINE_PLACEMENT_CONFLICT. It does not shadow either occurrence. during permits a base placement with that name when the selected range removes it.

Transition regions

Transitions expose operation-owned regions. flash_cut provides sequential ::before and ::after regions. crossfade provides ::before, ::after, and the shared ::overlap region:

image("before.png", 2s)
image("after.png", 2s)
crossfade(500ms) as transition

trim(range=$transition::overlap)

The before and after regions retain their nested placement layouts, so a path such as $transition::before::title remains addressable. Their ranges overlap in a crossfade and remain sequential in a flash cut. All normal boundaries, including ::middle, apply to these regions.

Coordinate arithmetic

Timeline coordinates use exact rational arithmetic. Coordinates with the same root support addition and subtraction. Number may scale them. Either Duration unit family may offset them:

during(
    50% * ($edit::intro::start + $edit::credits::start)
        ..($edit::credits::end - 3f)
) {
    zoom_in(2%)
}

Intermediate coordinates may be negative or beyond the owning timeline. The compiler checks native-grid alignment, ordering, and final bounds when the expression becomes a TimeRange. ::middle is therefore a valid exact rational coordinate between frames or samples. An unaligned value reports the applicable frame- or sample-alignment error.

Video and Audio trim retain marker expressions with boundaries that depend on unprobed media. Preflight resolves them after it determines the referenced source domains. The prepared operation contains an ordinary exact frame or sample range. during uses the same deferred native-range model. It lowers to existing slice and concat primitives.

A Video during body receives the selected extent symbolically. An image without an explicit duration inherits that media-dependent extent. Preflight resolves it to a concrete frame count. Audio during does not reinterpret a sample extent as a Video frame request.

Reusing selectors with aliases

Aliases make long marker expressions reusable:

credits_lead_in = $edit::credits::start - 500ms
credits_end = $edit::credits::end

during($credits_lead_in..$credits_end) {
    zoom_in(2%)
}

Timeline selectors inside aliases require their explicit root. A timeline-anchored call supports contextual suffix lookup such as $interview::start or $chapter::interview::start. An alias should use the complete rooted path. This keeps its meaning independent of later uses.

Diagnostic layout

Timeline selector diagnostics print the compiler’s actual rooted occurrence layout. Each child includes its root-relative closed-open range. The tree is the canonical selector layout, not a record of anonymous operation wrappers.

The compiler marks genuinely unnamed leaves and ambiguous labels as not directly addressable. Mixed-root arithmetic shows both roots. For a marker range used with the wrong input, the diagnostic shows the marker root beside the bound input layouts. Diagnostic trees show at most 64 occurrences and 12 nesting levels.

Imports and external programs

Imports make another .clipasm file callable under a local name. The imported file can use ClipAsm statements or declare a trusted external executable.

Imports

import "programs/polish.clipasm" as polish
import "programs/brighten.clipasm" as brighten

An import declaration requires an alias. The path resolves from the file containing the import. Aliases are local. Imports do not export them again. Aliases cannot shadow built-in programs. Import cycles are errors.

Each imported source file defines one callable program with its own local stack, inputs, parameters, and names. Callers use the same syntax regardless of its implementation:

video("assets/scene.mp4")
polish(8%)

External implementations

A source file may declare an external implementation instead of an executable ClipAsm body:

clipasm 1

input video: Video
param amount: Integer = 15

external {
    executable = "python3"
    arguments = [file("brighten.py")]
    semantic_version = 1
    preserve = video
}

Fields

  • executable is either a source-relative path or a bare name found through the platform command lookup.
  • arguments is an ordered list of literal strings and file("...") values.
  • semantic_version is a positive author-controlled version for output meaning.
  • preserve names the Video input whose exact duration and meaningful-audio state the single Video output must retain.

A file("...") argument resolves from the external source file. ClipAsm hashes the executable, declared File arguments, and File-valued parameters during preflight and checks them again before execution. It passes the executable and argument vector separately rather than constructing a shell command.

External implementations participate in persistent memoization. For the same executable and declared File bytes, semantic_version, arguments, parameters, project settings, and input artifact bytes, an implementation must produce equivalent output. Hidden state such as clocks, randomness, network responses, environment variables, or undeclared files violates this contract. Increment semantic_version whenever output meaning changes without changing an identified file.

External programs currently support fixed Video or Audio inputs. They support Integer, File, or Keyword parameters and exactly one Video output. ClipAsm applies defaults before execution.

An external implementation file cannot also contain executable statements or imports. Put composition in a separate wrapper file and import the external program there.

Validation remains media- and process-free. Rendering sends a versioned JSON request over standard input and verifies the produced media afterward. An external Video implementation must emit the exact working Video and Audio encodings stated in the request; attaching color tags without converting samples does not satisfy that contract. An implementation must complete the work needed for its declared output before the direct process exits. ClipAsm terminates remaining descendants in the managed process group on Unix or Job Object on Windows. Work that deliberately escapes that managed group or job is outside the invocation contract.

ClipAsm does not sandbox external programs. Read External programs and the trust boundary before you run one.

ClipAsm language grammar

This page is the normative EBNF grammar for ClipAsm language version 1. The language reference defines semantic constraints that context-free grammar cannot express. These constraints include declaration uniqueness, program signatures, scalar types, stack behavior, and required arguments.

The notation uses [...] for an optional form, {...} for zero or more repetitions, and | for alternatives. Literal source characters appear in quotes.

Lexical grammar

letter           = "A"…"Z" | "a"…"z" ;
digit            = "0"…"9" ;
source-character = ? any Unicode scalar value ? ;
string-character = source-character - ( '"' | "\\" | newline ) ;

identifier       = (letter | "_"),
                   { letter | digit | "_" | "-" } ;

number           = digit, { digit },
                   [ ".", digit, { digit } ] ;

string           = '"', { string-character | escape }, '"' ;
escape           = '\\"' | "\\\\" | "\\n" | "\\r" | "\\t" ;

newline          = "\n" ;
horizontal-space = " " | "\t" | "\r" ;
comment          = "#", { source-character - newline }, [ newline ] ;

The lexer ignores horizontal space and comments. Newlines remain tokens because they separate declarations, statements, and configuration fields. Keywords such as config, param, and as use the identifier lexical form. Their grammar position determines their meaning.

File and declarations

source-file         = { newline },
                      version-declaration, statement-end,
                      { declaration, statement-end },
                      { statement, { newline } } ;

version-declaration = "clipasm", "1" ;

declaration         = config-declaration
                    | import-declaration
                    | external-declaration
                    | input-declaration
                    | parameter-declaration ;

config-declaration  = "config", "{", { newline },
                      { config-field, statement-end },
                      "}" ;

config-field        = video-config
                    | audio-config
                    | "output", "=", string ;

video-config        = "video", "{", { newline },
                      { video-field, statement-end },
                      "}" ;

video-field         = "width", "=", number
                    | "height", "=", number
                    | "fps", "=", number, [ "/", number ] ;

audio-config        = "audio", "{", { newline },
                      { "sample_rate", "=", number, statement-end },
                      "}" ;

import-declaration  = "import", string, "as", identifier ;

external-declaration = "external", "{", { newline },
                       { external-field, statement-end },
                       "}" ;

external-field      = "executable", "=", string
                    | "arguments", "=", external-arguments
                    | "semantic_version", "=", number
                    | "preserve", "=", identifier ;

external-arguments  = "[", { newline },
                      [ external-argument,
                        { ",", { newline }, external-argument },
                        [ ",", { newline } ] ],
                      "]" ;

external-argument   = string | "file", "(", string, ")" ;

input-declaration   = "input", identifier, ":", value-type ;

parameter-declaration = "param", identifier, ":", parameter-type,
                        [ "=", scalar-expression ] ;

value-type          = "Video" | "Audio" ;

parameter-type      = "Number"
                    | "Integer"
                    | "File"
                    | "Duration"
                    | "TimeRange"
                    | "Keyword", "(", { newline }, identifier,
                      { { newline }, ",", { newline }, identifier },
                      { newline }, ")" ;

All declarations precede the first statement. A statement-end is one or more newlines, the closing brace of the containing block, or end of file.

Statements and invocations

statement           = ( scalar-binding
                      | statement-expression, [ output-binding ] ),
                      statement-end ;

scalar-binding      = identifier, "=", scalar-expression ;

statement-expression = invocation
                     | reference-expression
                     | stack-block ;

invocation          = [ access ], identifier, [ type-argument ],
                      [ arguments ], [ block ] ;

access              = "@owned" | "@visible" ;
type-argument       = "<", value-type, ">" ;

arguments           = "(", { newline },
                      [ argument,
                        { { newline }, ",", { newline }, argument },
                        [ { newline }, "," ],
                        { newline } ],
                      ")" ;

argument            = [ identifier, "=" ], argument-expression ;

argument-expression = invocation
                    | stack-block
                    | scalar-expression ;

block               = "{", { newline }, { statement, { newline } }, "}" ;
stack-block         = [ access ], block ;

reference-expression = "$", identifier ;

output-binding      = "as", output-binding-slot
                    | "as", "(", { newline },
                      output-binding-slot, { newline }, ",", { newline },
                      output-binding-slot,
                      { { newline }, ",", { newline }, output-binding-slot },
                      { newline }, ")" ;

output-binding-slot = identifier ;

An identifier-led argument expression is an invocation when (, <, or { follows it. Program lookup and the classification of graph versus scalar arguments happen after parsing.

The identifier _ is a discard wildcard in an output-binding slot. It occupies one output position but creates no graph name or timeline placement. The output value remains on the stack. Every other identifier names its corresponding output. Parenthesized bindings must contain at least two slots, may contain multiple _ slots, and must contain exactly as many slots as the statement produces outputs.

At statement position, absent and empty arguments are semantically equivalent. After program resolution, an absent block becomes an empty body for a body program. It remains absent for a program that does not accept a caller body. Sugar applies the same rule when it defines a body-capable construct. Consequently, join, join(), join {}, and join() {} are equivalent before normal binding and body-contract validation.

A scalar binding is immutable, has no stack effect, and infers its scalar type from the right-hand expression. Each program body defines one scalar scope. The compiler predeclares bindings in that body for forward references. The bindings inherit visible bindings from enclosing bodies. They do not escape to a parent or sibling body.

Sibling bodies may reuse a binding name. A declaration may not shadow a visible scalar binding. It also may not collide with a program input, parameter, or graph output name. A timeline selector inside a scalar binding resolves only from its explicit root. It may capture a lexical body input. It never borrows a later invocation’s contextual timeline root.

Scalar expressions

scalar-expression  = range-expression ;

range-expression   = sum-expression,
                     [ "..", sum-expression ] ;

sum-expression     = product-expression,
                     { ("+" | "-"), product-expression } ;

product-expression = unary-expression,
                     { ("*" | "/"), unary-expression } ;

unary-expression   = { "+" | "-" }, postfix-expression ;

postfix-expression = primary-expression,
                     { "%" | "ms" | "s" | "f" } ;

primary-expression = number
                   | string
                   | identifier
                   | reference-expression
                   | timeline-selector
                   | "(", scalar-expression, ")" ;

timeline-selector  = "$", identifier,
                     "::", identifier,
                     { "::", identifier } ;

Postfix operators associate from left to right and may repeat. Thus 800%% means (800 / 100) / 100. The grammar deliberately accepts unusual compositions. Checked scalar types determine whether each operation exists.

% requires Number and divides it by 100. ms, s, and f require an expression whose exact result satisfies Integer. They construct Duration. f denotes an exact count on the configured project video frame grid. Number supports +, -, *, and /.

Both Duration unit families support unary signs, addition, and subtraction. Binary Duration arithmetic requires matching families. .. likewise requires two compatible Duration expressions and constructs TimeRange. You cannot mix project-frame endpoints with wall-clock endpoints.

A timeline selector ending in start, middle, or end denotes a coordinate. A selector ending in a placement name denotes that placement’s complete closed-open range. Two coordinates with the same timeline root may also use .. to construct a frame-native TimeRange.

Timeline coordinates with the same root support + and -. Number may scale a coordinate with *. A coordinate supports division by Number. Duration may offset a coordinate with + or -. The compiler checks exact frame alignment and bounds when an expression becomes a TimeRange.

Command-line reference

ClipAsm provides six commands: init, programs, explain, validate, inspect, and render. Run commands from the directory whose relative CLI paths you intend to use. From a source checkout, cargo run -- <COMMAND> is the equivalent development form.

init

clipasm init [PATH]

The exact built-in help is:

$ clipasm init --help
Create a self-contained ClipAsm starter project.

PATH defaults to the current directory and is created when needed. Existing directories are supported only when every starter path is available. Existing files and incompatible directories are never replaced.

Usage: clipasm init [PATH]

Arguments:
  [PATH]
          Directory to initialize. Defaults to the current directory

Options:
  -h, --help
          Print help (see a summary with '-h')

Examples:
  clipasm init hello-video
  clipasm init

Unrelated paths in an existing compatible directory are left alone. Initialization never prompts for permission. It follows ordinary local filesystem directory links. ClipAsm assumes that the caller controls the target tree during initialization. It does not guarantee behavior when another process changes target paths concurrently.

The two forms are:

clipasm init hello-video
clipasm init

The starter tree is exactly:

.gitignore
README.md
clipasm.toml
main.clipasm
assets/
  morning.png
  meadow.png
  evening.png

The installed binary ships this starter tree. The starter program validates to 108 frames and publishes generated/scenic-sequence.mp4. Initialization does not invoke Git, render, or media tools, and it does not contact the network.

For a named path, success is:

$ clipasm init hello-video
Created ClipAsm project at `hello-video`.

Next:
  cd "hello-video"
  clipasm render

Optional source check:
  clipasm validate

When the target is the current directory, ClipAsm omits the cd line. For a path that a portable shell command cannot represent, the output tells you to enter the created directory. You can then run the render command. The source-only validation command remains optional.

The generated files are ordinary, unmanaged project files. ClipAsm does not update, rewrite, or take ownership of them later. Future releases may ship different starter files, but they do not alter existing projects. The development examples in a source checkout are not the installed binary’s starter contract and may differ from it.

programs

clipasm programs [NAME]

With no NAME, this command lists every built-in program in deterministic categories. With NAME, it prints the terminal reference for that exact built-in. The reference includes its call shape, inputs, parameters, defaults, outputs, and binding behavior. It also includes the body contract, example, and full guide URL. An unknown name fails with E_UNKNOWN_BUILTIN_PROGRAM.

programs always describes programs built into the installed ClipAsm binary. It never inspects a project, source file, imported program, media asset, FFmpeg, or FFprobe, and it does not require a repository checkout. See the generated built-in program index for the browsable reference.

explain

clipasm explain <CODE>

explain looks up one built-in ClipAsm diagnostic code, such as E_UNKNOWN_PROGRAM. It prints the title, category, explanation, common causes, recommended actions, and retry guidance. It also prints a link to the relevant reference page. The code identifies the diagnostic class. Its original error message and source location provide the instance-specific context.

This command reads only the diagnostic catalog compiled into the installed binary. It never parses source, discovers a project, opens media, or inspects FFmpeg, FFprobe, or external programs, and it does not require a repository checkout. Unknown codes fail with a dedicated diagnostic and direct readers to the diagnostic index.

For a complete, searchable list of built-in diagnostics, see the diagnostics reference.

Projects and source selection

The validate, inspect, and render commands accept an optional native .clipasm source program:

clipasm <COMMAND> [OPTIONS] [SOURCE]

When you omit SOURCE, ClipAsm searches the current directory and then each parent directory for the nearest clipasm.toml. The manifest is strict:

[project]
entrypoint = "main.clipasm"

[render]
cache = "persistent"
materialization = "all"

project.entrypoint is a forward-slash relative .clipasm path resolved from the manifest directory. Unknown fields, absolute paths, backslashes, drive-style prefixes, and paths containing . or .. are rejected. A discovered manifest symlink must resolve to a regular file. A broken nearer manifest path causes an error. ClipAsm does not continue the search in a parent project. An explicit SOURCE remains a standalone invocation and does not read an ambient project manifest.

Project renders keep persistent state under .clipasm/ at the manifest root, even when the entrypoint is in a nested directory. Explicit standalone sources keep the existing source-adjacent cache location.

render.cache accepts "persistent" or "none" and defaults to "persistent" when [render] is absent. Persistent mode reads verified cache entries and retains newly rendered working artifacts. None mode does not read, create, change, or delete persistent cache entries. It still materializes working artifacts in a private temporary directory, deletes intermediates after their final consumer, and removes the directory when the render ends. Override the project setting for one invocation with clipasm render --cache MODE.

render.materialization is independent of cache retention. It accepts "all" or "fused" and defaults to "all". All mode materializes every reached prepared node, matching the original execution model. Fused mode combines compatible FFmpeg primitives that lead to one materialized endpoint into one filter graph. Stream-disjoint picture and Audio consumers may share a region, but duplicated physical streams remain materialized so fusion does not add duration-scaled buffering. Temporal joins materialize their inputs for the same reason. Cache hits, external programs, operations that require their own FFmpeg input behavior, and branches with different materialized endpoints remain artifact boundaries. Override the project setting for one invocation with clipasm render --materialization MODE.

The selected source file and paths authored inside it resolve according to the source-unit rules in the language reference. Paths supplied through CLI options resolve from the caller’s working directory.

Root bindings

validate, inspect, and render accept repeatable bindings for declarations on the root source program:

OptionMeaning
--video-input NAME=VIDEO_PATHBind one declared root Video input.
--audio-input NAME=AUDIO_PATHBind one declared root Audio input.
--arg NAME=VALUEBind one declared root scalar parameter.

Names must match declarations exactly. Duplicate, unknown, missing, or type-incompatible bindings are errors. Media and File paths supplied through these options resolve from the working directory.

Binding options work the same way for validation, inspection, and rendering:

clipasm validate template.clipasm \
  --video-input source=footage.mp4 \
  --arg range=1s..3s \
  --arg count=2

clipasm render template.clipasm \
  --video-input source=footage.mp4 \
  --arg range=1s..3s \
  --arg count=2 \
  --output final.mp4

CLI paths resolve from the caller’s working directory. Authored paths resolve from the source file that contains them.

validate

clipasm validate [OPTIONS] [SOURCE]

validate parses and checks the complete linked source package, evaluates its stack programs, and infers every domain available from authored data. It does not open media, invoke FFmpeg or FFprobe, or execute external programs.

Use it as the first check while editing:

clipasm validate

Successful output reports the semantic value count. It also reports one of these root result summaries:

Root resultSuccess summary
One Video with an authored domainexact frame count
One Video whose domain depends on mediaduration resolves during preflight
One Audio outputoutput type
Zero or multiple outputsoutput count

inspect

clipasm inspect [OPTIONS] [SOURCE]

inspect performs the same pure compilation work and serializes the compiled semantic program as JSON. By default it writes JSON to standard output.

clipasm inspect

Use -o or --output to write a new file. Create the parent directory first when it does not already exist. The destination must not already exist.

Inspection JSON is a versioned downstream view of compiled semantics. It is not canonical source or an authoring format. Consumers must check format_version. See Machine-readable contracts.

render

clipasm render [OPTIONS] [SOURCE]

render compiles the source, performs preflight, executes the prepared plan, verifies produced artifacts, and publishes an MP4 and sibling versioned manifest. See Machine-readable contracts before consuming that JSON.

clipasm render

The root source may declare config.output. Override it with -o or --output. An override resolves from the caller’s working directory. Rendering requires an output path from one of those sources and exactly one publishable Video output. It may inspect media, invoke FFmpeg and FFprobe, and execute reachable external programs as trusted native code.

Use --cache persistent or --cache none to override cache retention for one render. This option also applies to explicit standalone sources. In none mode, the reused-artifact count is zero. Use --materialization all or --materialization fused independently to select intermediate execution. The render report and manifest record reused artifacts separately from rendered jobs, so one fused region counts as one rendered job.

Help and version

Use -h or --help with the root command or a subcommand, and -V or --version on the root command:

clipasm render --help
clipasm --version

Built-in programs

Find the callable programs that create or transform Video and Audio values.

ClipAsm registers these programs and uses normal call syntax for them. Imported source programs use the same call model. Separate documentation describes language forms such as clip and stack blocks. These forms do not appear in clipasm programs.

The type shapes below are lookup notation, not ClipAsm declaration syntax. See statements and calls for authored syntax.

Program catalog

Sources

ProgramSummaryType shapeProperties
imageCreate a Video from an image file.() -> Video
videoLoad a Video from a video file.() -> Video
audioLoad standalone Audio from an audio file.() -> Audio

Timeline

ProgramSummaryType shapeProperties
concatConcatenate one or more homogeneous timelines.(T...) -> Tgeneric
repeatRepeat a Video or Audio timeline.(T) -> Tgeneric
trimKeep a selected range of a Video or Audio timeline.(T) -> Tgeneric
dropRemove one Video or Audio value from the stack.(T) -> nonegeneric

Audio

ProgramSummaryType shapeProperties
extract_audioExtract the meaningful Audio from a Video.(Video) -> Audio
set_audioReplace a Video’s Audio with standalone Audio.(Video, Audio) -> Video

Effects

ProgramSummaryType shapeProperties
zoom_inApply a linear zoom-in effect to a Video.(Video) -> Video

Transitions

ProgramSummaryType shapeProperties
flash_cutJoin two Videos with a brief white-flash transition.(Video, Video) -> Video
crossfadeOverlap two Videos or Audio values with a crossfade transition.(T, T) -> Tgeneric

Body programs

ProgramSummaryType shapeProperties
joinTransform and concatenate two Video or Audio timelines in a body.(T, T) -> Tgeneric, accepts a body
duringReplace a selected timeline range with the result of a body.(T) -> Tgeneric, accepts a body

image

Create a Video from an image file.

Call shape

image(path: File, duration?: Duration, fit?: Keyword(cover | contain | stretch)) -> Video

This is call-shape notation for lookup, not ClipAsm declaration syntax.

Graph inputs

This program does not take a Video or Audio input from the stack.

Parameters and defaults

NameTypeRequirementDefault or omission behavior
pathFilerequired
durationDurationoptionaluses a requested Video extent from the surrounding body. Without one, the call reports a missing image duration
fitKeyword(cover | contain | stretch)optionalcover

Result and stack behavior

Outputs: Video.

Default stack access is owned. See stack binding for ownership and visibility rules.

Timeline and markers

The program creates a new timeline when it returns Video or Audio.

Example

clipasm 1

config {
    video {
        fps = 30
    }
}

image("assets/title.png", 2s, contain)

Expected validation result: Video with exactly 60 project frames at the example’s explicit fps = 30.

The reference checks parse and compile this exact example. Compilation does not inspect the named media files.

Behavior

  • ClipAsm fits the image to the project Video dimensions.
  • Untagged opaque RGB stills use the sRGB convention, and fitting interpolation runs in display-linear light.
  • The cover mode fills the frame and crops overflow. The contain mode adds padding. The stretch mode can distort the image.
  • A surrounding Video body may supply the requested duration when the author omits duration.

Requirements

  • The resolved duration must contain at least one project frame.

Common diagnostics

These are the diagnostics most specific to this program.

See also

video

Load a Video from a video file.

Call shape

video(path: File, fit?: Keyword(cover | contain | stretch)) -> Video

This is call-shape notation for lookup, not ClipAsm declaration syntax.

Graph inputs

This program does not take a Video or Audio input from the stack.

Parameters and defaults

NameTypeRequirementDefault or omission behavior
pathFilerequired
fitKeyword(cover | contain | stretch)optionalcover

Result and stack behavior

Outputs: Video.

Default stack access is owned. See stack binding for ownership and visibility rules.

Timeline and markers

The program creates a new timeline when it returns Video or Audio.

Example

clipasm 1

video("assets/scene.mp4", contain)

Expected validation result: Video with a source-dependent frame domain resolved during preflight.

The reference checks parse and compile this exact example. Compilation does not inspect the named media files.

Behavior

  • Compilation remains media-pure. Preflight probes the source and resolves its exact project-frame domain.
  • Preflight requires complete BT.709 SDR color metadata; it does not guess missing metadata or silently tone-map HDR.
  • Preflight fits the source to the project Video dimensions and preserves its resolved duration.

See also

audio

Load standalone Audio from an audio file.

Call shape

audio(path: File) -> Audio

This is call-shape notation for lookup, not ClipAsm declaration syntax.

Graph inputs

This program does not take a Video or Audio input from the stack.

Parameters and defaults

NameTypeRequirementDefault or omission behavior
pathFilerequired

Result and stack behavior

Outputs: Audio.

Default stack access is owned. See stack binding for ownership and visibility rules.

Timeline and markers

The program creates a new timeline when it returns Video or Audio.

Example

clipasm 1

audio("assets/music.wav")

Expected validation result: Audio.

The reference checks parse and compile this exact example. Compilation does not inspect the named media files.

Behavior

  • Compilation remains media-pure. Preflight probes and normalizes the source to the project Audio domain.

See also

extract_audio

Extract the meaningful Audio from a Video.

Call shape

extract_audio(video: Video) -> Audio

This is call-shape notation for lookup, not ClipAsm declaration syntax.

Graph inputs

NameTypeCardinality
videoVideoexactly one

Parameters and defaults

This program has no scalar parameters.

Result and stack behavior

Outputs: Audio.

Default stack access is owned. See stack binding for ownership and visibility rules.

Timeline and markers

The program creates a new timeline when it returns Video or Audio.

Example

clipasm 1

video("assets/interview.mp4")
extract_audio

Expected validation result: Audio.

The reference checks parse and compile this exact example. Compilation does not inspect the named media files.

Behavior

  • The standalone Audio output covers the complete Video duration on the project sample grid.

Requirements

  • The Video must carry meaningful attached Audio. A silent Video cannot produce Audio content.

See also

set_audio

Replace a Video’s Audio with standalone Audio.

Call shape

set_audio(video: Video, audio: Audio) -> Video

This is call-shape notation for lookup, not ClipAsm declaration syntax.

Graph inputs

NameTypeCardinality
videoVideoexactly one
audioAudioexactly one

Parameters and defaults

This program has no scalar parameters.

Result and stack behavior

Outputs: Video.

Default stack access is owned. See stack binding for ownership and visibility rules.

Timeline and markers

The program keeps the duration and addressable markers from video.

Example

clipasm 1

set_audio(
    video=video("assets/scene.mp4"),
    audio=audio("assets/music.wav"),
)

Expected validation result: Video with a source-dependent frame domain resolved during preflight.

The reference checks parse and compile this exact example. Compilation does not inspect the named media files.

Behavior

  • The output preserves the Video timeline. ClipAsm marks the output as carrying meaningful Audio.
  • The supplied standalone Audio replaces any Audio already attached to the Video.

See also

concat

Concatenate one or more homogeneous timelines.

Call shape

concat<T: Video | Audio>(values: T...) -> T

This is call-shape notation for lookup, not ClipAsm declaration syntax.

Graph inputs

NameTypeCardinality
valuesTone or more (minimum 1)

Parameters and defaults

This program has no scalar parameters.

Result and stack behavior

Outputs: T.

All T inputs and outputs use one homogeneous type: Video or Audio. Use an explicit <Video> or <Audio> argument when the accessible stack makes inference ambiguous.

Default stack access is owned. See stack binding for ownership and visibility rules.

Timeline and markers

The program places the values bound to values one after another in their existing order.

Example

clipasm 1

config {
    video {
        fps = 30
    }
}

image("assets/one.png", 1s)
image("assets/two.png", 1s)
concat

Expected validation result: Video with exactly 60 project frames at the example’s explicit fps = 30.

The reference checks parse and compile this exact example. Compilation does not inspect the named media files.

Behavior

  • Every bound value must use the same inferred Video or Audio type.
  • The program concatenates the bound values in stack order.
  • Use concat<Video> or concat<Audio> when both homogeneous bindings are possible.

See also

repeat

Repeat a Video or Audio timeline.

Call shape

repeat<T: Video | Audio>(value: T, count: Integer) -> T

This is call-shape notation for lookup, not ClipAsm declaration syntax.

Graph inputs

NameTypeCardinality
valueTexactly one

Parameters and defaults

NameTypeRequirementDefault or omission behavior
countIntegerrequired

Result and stack behavior

Outputs: T.

All T inputs and outputs use one homogeneous type: Video or Audio. Use an explicit <Video> or <Audio> argument when the accessible stack makes inference ambiguous.

Default stack access is owned. See stack binding for ownership and visibility rules.

Timeline and markers

repeat(1) keeps value unchanged. Larger counts repeat its media. A marker cannot address an individual repeated occurrence.

Example

clipasm 1

config {
    video {
        fps = 30
    }
}

image("assets/card.png", 1s)
repeat(3)

Expected validation result: Video with exactly 90 project frames at the example’s explicit fps = 30.

The reference checks parse and compile this exact example. Compilation does not inspect the named media files.

Behavior

  • repeat(1) is a true identity and preserves nested timeline placements.
  • Larger counts create a new repeated timeline. Child placements are unavailable until ClipAsm supports occurrence indexing.

Requirements

  • count must be an Integer greater than or equal to one.

Common diagnostics

These are the diagnostics most specific to this program.

See also

trim

Keep a selected range of a Video or Audio timeline.

Call shape

trim<T: Video | Audio>(value: T, range: TimeRange) -> T

This is call-shape notation for lookup, not ClipAsm declaration syntax.

Graph inputs

NameTypeCardinality
valueTexactly one

Parameters and defaults

NameTypeRequirementDefault or omission behavior
rangeTimeRangerequired

Result and stack behavior

Outputs: T.

All T inputs and outputs use one homogeneous type: Video or Audio. Use an explicit <Video> or <Audio> argument when the accessible stack makes inference ambiguous.

Default stack access is owned. See stack binding for ownership and visibility rules.

Timeline and markers

The program keeps the selected range from value. It preserves only markers that are fully inside the range.

Example

clipasm 1

config {
    video {
        fps = 30
    }
}

video("assets/scene.mp4")
trim(1s..3s)

Expected validation result: Video with exactly 60 project frames at the example’s explicit fps = 30.

The reference checks parse and compile this exact example. Compilation does not inspect the named media files.

Behavior

  • ClipAsm accepts absolute ranges and rooted timeline-marker ranges for both Video and Audio.
  • ClipAsm preserves and rebases complete child placements inside the selected range.
  • ClipAsm omits partial or uncertain placements.
  • Media-dependent marker boundaries remain deferred until preflight resolves the source domain.

Requirements

  • The range must be nonempty, native-grid aligned, within the bound timeline, and owned by that timeline.

Common diagnostics

These are the diagnostics most specific to this program.

See also

drop

Remove one Video or Audio value from the stack.

Call shape

drop<T: Video | Audio>(value: T) -> none

This is call-shape notation for lookup, not ClipAsm declaration syntax.

Graph inputs

NameTypeCardinality
valueTexactly one

Parameters and defaults

This program has no scalar parameters.

Result and stack behavior

This program produces no values.

All T inputs and outputs use one homogeneous type: Video or Audio. Use an explicit <Video> or <Audio> argument when the accessible stack makes inference ambiguous.

Default stack access is owned. See stack binding for ownership and visibility rules.

Timeline and markers

The program creates a new timeline when it returns Video or Audio.

Example

clipasm 1

audio("assets/music.wav")
drop

Expected validation result: no output values.

The reference checks parse and compile this exact example. Compilation does not inspect the named media files.

Behavior

  • The program consumes the bound value from the stack and produces no output value.

See also

zoom_in

Apply a linear zoom-in effect to a Video.

Call shape

zoom_in(video: Video, by?: Number) -> Video

This is call-shape notation for lookup, not ClipAsm declaration syntax.

Graph inputs

NameTypeCardinality
videoVideoexactly one

Parameters and defaults

NameTypeRequirementDefault or omission behavior
byNumberoptional8%

Result and stack behavior

Outputs: Video.

Default stack access is owned. See stack binding for ownership and visibility rules.

Timeline and markers

The program keeps the duration and addressable markers from video.

Example

clipasm 1

config {
    video {
        fps = 30
    }
}

image("assets/card.png", 2s)
zoom_in(12%)

Expected validation result: Video with exactly 60 project frames at the example’s explicit fps = 30.

The reference checks parse and compile this exact example. Compilation does not inspect the named media files.

Behavior

  • For a multi-frame Video, scale increases linearly from 100% on the first frame to exactly 100% + by on the last frame.
  • Directly adjacent zoom_in calls are composed into one perspective resampling pass; their per-frame scale curves multiply in authored order.
  • The program preserves the Video timeline and the attached meaningful-Audio state.

Requirements

  • by must be positive.
  • Adjacent zooms must fit the 24 KiB composed-filter limit.

Common diagnostics

These are the diagnostics most specific to this program.

See also

flash_cut

Join two Videos with a brief white-flash transition.

Call shape

flash_cut(before: Video, after: Video, duration?: Duration) -> Video

This is call-shape notation for lookup, not ClipAsm declaration syntax.

Graph inputs

NameTypeCardinality
beforeVideoexactly one
afterVideoexactly one

Parameters and defaults

NameTypeRequirementDefault or omission behavior
durationDurationoptional160ms

Result and stack behavior

Outputs: Video.

Default stack access is owned. See stack binding for ownership and visibility rules.

Timeline and markers

The program places before and after in sequence. It exposes the corresponding transition regions.

Example

clipasm 1

config {
    video {
        fps = 30
    }
}

image("assets/before.png", 2s)
image("assets/after.png", 2s)
flash_cut

Expected validation result: Video with exactly 120 project frames at the example’s explicit fps = 30.

The reference checks parse and compile this exact example. Compilation does not inspect the named media files.

Behavior

  • duration becomes the smallest whole project-frame count that covers the authored duration.
  • The white fade is evaluated in display-linear BT.709 RGB.
  • The output exposes sequential before and after timeline regions.

Requirements

  • duration must cover at least one project frame.

Common diagnostics

These are the diagnostics most specific to this program.

See also

crossfade

Overlap two Videos or Audio values with a crossfade transition.

Call shape

crossfade<T: Video | Audio>(before: T, after: T, duration?: Duration) -> T

This is call-shape notation for lookup, not ClipAsm declaration syntax.

Graph inputs

NameTypeCardinality
beforeTexactly one
afterTexactly one

Parameters and defaults

NameTypeRequirementDefault or omission behavior
durationDurationoptional500ms

Result and stack behavior

Outputs: T.

All T inputs and outputs use one homogeneous type: Video or Audio. Use an explicit <Video> or <Audio> argument when the accessible stack makes inference ambiguous.

Default stack access is owned. See stack binding for ownership and visibility rules.

Timeline and markers

The program overlaps the end of before with the start of after. It exposes before, overlap, and after regions.

Example

clipasm 1

config {
    video {
        fps = 30
    }
}

image("assets/before.png", 2s)
image("assets/after.png", 2s)
crossfade

Expected validation result: Video with exactly 105 project frames at the example’s explicit fps = 30.

The reference checks parse and compile this exact example. Compilation does not inspect the named media files.

Behavior

  • For Video, duration becomes the smallest whole project-frame count that covers the authored duration; for Audio, it becomes the smallest whole project-sample count.
  • Video pictures blend in display-linear BT.709 RGB, while standalone and attached Audio use equal-power fade curves.
  • The output exposes before, overlap, and after timeline regions.

Requirements

  • before and after must have the same Video or Audio type.
  • duration must cover at least one native frame or sample and cannot exceed either input.

Common diagnostics

These are the diagnostics most specific to this program.

See also

join

Transform and concatenate two Video or Audio timelines in a body.

Call shape

join<T: Video | Audio>(before: T, after: T) { ... } -> T

This is call-shape notation for lookup, not ClipAsm declaration syntax.

Graph inputs

NameTypeCardinality
beforeTexactly one
afterTexactly one

Parameters and defaults

This program has no scalar parameters.

Result and stack behavior

Outputs: T.

All T inputs and outputs use one homogeneous type: Video or Audio. Use an explicit <Video> or <Audio> argument when the accessible stack makes inference ambiguous.

Default stack access is visible. See stack binding for ownership and visibility rules.

Body

The body begins with:

  • T from the complete before input
  • T from the complete after input

The body must leave at least 1 homogeneous T value.

Timeline and markers

The program starts the body with before and after. It joins the matching values left by the body.

Example

clipasm 1

config {
    video {
        fps = 30
    }
}

image("assets/before.png", 1s)
image("assets/after.png", 1s)
join {
    zoom_in(4%)
}

Expected validation result: Video with exactly 60 project frames at the example’s explicit fps = 30.

The reference checks parse and compile this exact example. Compilation does not inspect the named media files.

Behavior

  • The body starts with before followed by after.
  • The body exposes the inputs as the lexical $before and $after references.
  • ClipAsm concatenates each homogeneous value from the body into one output timeline.
  • Named values created by the body remain addressable as placements in the result.

Requirements

  • The body must leave at least one value of the selected homogeneous Video or Audio type.

Common diagnostics

These are the diagnostics most specific to this program.

See also

during

Replace a selected timeline range with the result of a body.

Call shape

during<T: Video | Audio>(timeline: T, range: TimeRange) { ... } -> T

This is call-shape notation for lookup, not ClipAsm declaration syntax.

Graph inputs

NameTypeCardinality
timelineTexactly one

Parameters and defaults

NameTypeRequirementDefault or omission behavior
rangeTimeRangerequired

Result and stack behavior

Outputs: T.

All T inputs and outputs use one homogeneous type: Video or Audio. Use an explicit <Video> or <Audio> argument when the accessible stack makes inference ambiguous.

Default stack access is visible. See stack binding for ownership and visibility rules.

Body

The body begins with:

  • T from the range selected from timeline by range

The body must leave exactly T.

Timeline and markers

The program replaces the selected range in timeline and shifts later markers. It names the inserted result replacement.

Example

clipasm 1

config {
    video {
        fps = 30
    }
}

image("assets/card.png", 3s)
during(1s..2s) {
    zoom_in(4%)
}

Expected validation result: Video with exactly 90 project frames at the example’s explicit fps = 30.

The reference checks parse and compile this exact example. Compilation does not inspect the named media files.

Behavior

  • The body starts with the selected range.
  • The body exposes the complete bound input as the lexical $timeline reference.
  • The body must return exactly one matching value. ClipAsm inserts that value into the original timeline.
  • ClipAsm preserves or shifts placements before and after the range.
  • ClipAsm omits intersecting or uncertain placements. The replacement name identifies the inserted body.
  • A Video selection supplies its requested extent when the author omits the image call’s duration.

Requirements

  • The range must be native-grid aligned, within the bound timeline, and owned by that timeline.
  • Use during<Video> or during<Audio> when a mixed stack makes the generic type ambiguous.

Common diagnostics

These are the diagnostics most specific to this program.

See also

Machine-readable contracts

ClipAsm emits several JSON documents. It supports only three as external contracts. Always read the version field before decoding a document.

Supported integrations

DocumentCurrent versionProduced or consumed byIntended use
Compiled inspection JSONformat_version: 24clipasm inspectsource-analysis and diagnostic tooling
Render manifestformat_version: 4successful native renderautomation and render provenance
External-program requestprotocol_version: 3trusted external executableimplementing an external program

A versioned JSON document is not an authoring format. ClipAsm does not accept these documents as source input.

Compiled inspection JSON

clipasm inspect SOURCE and the Rust CompiledProgram::compiled_json method produce the same media-independent document. It includes project settings, compiled operations, known domains, ordered outputs, names, source origins, and the compiled structure hash.

Source origins are inspection metadata, not semantic identity. In particular, moving or reformatting an external File parameter does not change the structure hash. This requires an unchanged authored path and unchanged resolved call.

Path-bearing inspection fields are JSON strings and therefore require valid Unicode. Pure compilation and semantic identity can still represent native non-Unicode paths. Only a compiled inspection JSON request for such a program fails.

A consumer must support the exact format_version. A new version may add, remove, rename, or reinterpret fields.

Render manifest

A successful native render writes <output>.manifest.json beside the MP4. It records:

  • Manifest and engine versions.
  • The compiled semantic hash.
  • Project Video and Audio settings.
  • The published Video pixel and color encoding.
  • The result fingerprint and exact Video domain.
  • Whether the Video carries meaningful Audio.
  • FFmpeg and FFprobe version summaries.
  • The cache mode and number of verified working artifacts reused. Cache-none renders report zero reused artifacts.
  • The execution materialization mode (all or fused) and number of rendered jobs.

It deliberately excludes local source paths, executable recipes, and cache locations.

External-program request

A reachable external implementation receives one JSON object on standard input. Protocol version 3 contains:

  • Named prepared inputs with artifact paths, types, exact domains, and audio state.
  • Resolved Integer, Keyword, and File parameters.
  • An output object containing the path the process must create, the complete working Video encoding, and the signed-16-bit working Audio encoding.
  • Project Video and Audio settings.
  • Resolved FFmpeg and FFprobe executable paths.

The process does not return JSON. It creates the requested file and exits with status zero. ClipAsm then probes and verifies dimensions, duration, audio, pixel format, bit depth, primaries, transfer, matrix, and range against the request. An implementation must reject protocol versions it does not support.

Paths refer to native host paths, but JSON strings carry them. Every path in an external-program request must therefore be valid Unicode. Native ClipAsm operations do not share this JSON limitation. The executable runs with the user’s permissions. This protocol is not a sandbox.

Internal formats

Prepared inspection JSON (format_version: 16) and the Browser render plan (version: 3, recipe_contract: 10) are internal to matching ClipAsm components. They may help when you debug ClipAsm. They are not persistence or interchange contracts.

Cache entry metadata is a Private implementation detail. Do not read, edit, copy, or construct cache sidecars as an integration mechanism.

Consumer rules

For supported documents:

  1. Read the version field first.
  2. Accept only versions your software explicitly supports.
  3. Ignore unknown fields only when doing so is safe for that decoder.
  4. Review and test every version change.
  5. Never use JSON object ordering as identity.