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.
- Get ClipAsm running and render the included starter.
- Go from one image to a sequence.
- Name and reference a clip.
- Transform one scene.
- Add a flash between scenes.
- Change a named scene after assembly.
- 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:
- Check a program before rendering
- Inspect compiled JSON
- Supply root inputs and parameters
- Import and call a source program
- Add or replace a soundtrack
- Review and run an external program
- Troubleshoot validation, media, tools, rendering, and cache problems
Understand ClipAsm
These pages explain the underlying model without using the learning project:
- From source to published video
- Color and linear-light processing
- Stack ownership and visibility
- Source programs and imports
- External programs and the trust boundary
Examples and reference
Use the example catalog for small runnable programs. For exact information, use:
- Language reference for
.clipasmsyntax and behavior - Built-in programs for call signatures and constraints
- Command-line reference for every CLI command and option
- Diagnostics for error-code guidance
- Machine-readable contracts for supported JSON integrations
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=PATHfor a declared Video--audio-input NAME=PATHfor a declared Audio--arg NAME=VALUEfor 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 wrapperexamples/programs/brighten/program.clipasm, the external declarationexamples/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
- Run
clipasm validate SOURCEto separate source and binding problems from media, tool, and execution problems. - If validation fails, inspect the first reported source location.
- If the diagnostic has a code, run
clipasm explain <CODE>. - Correct the source or binding problem.
- Run
clipasm validate SOURCEagain. - When validation succeeds, run
render. Rendering repeats source checks and then reports reachable media, tool, external-process, cache, or publication problems. - Use
inspectonly when the compiled graph or JSON integration is itself the question. Rendering does not requireinspect.
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
.clipasmfile resolve from that source file’s directory. - Import paths resolve from the importing source file.
- CLI media and
Filebindings 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:
| Command | Opens media? | Runs tools or external programs? | Main result |
|---|---|---|---|
validate | No | No | source and type check |
inspect | No | No | compiled JSON |
render | Yes, when reachable | Yes, when required | MP4 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
renderperforms - 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_inperspective interpolation;- the white fade in
flash_cut; - Video
crossfadeblending.
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
| Example | Shows | Expected render | Extra requirement | Related page |
|---|---|---|---|---|
examples/scenic-sequence.clipasm | clip, image sources, a name, and a reference | 4.5 seconds | — | Chapter 3: Name and reference a clip |
examples/learning-journey.clipasm | the completed learning path | 4.5 seconds | — | Learn ClipAsm |
examples/crossfade.clipasm | exact crossfade overlap | 3.5 seconds | — | crossfade reference |
examples/gentle-motion-edit.clipasm | during and zoom_in | 2 seconds | — | during reference |
examples/reusable-composition.clipasm | named clips, references, and stack-bound flash_cut | 3 seconds | — | Transition chapter |
examples/imported-program.clipasm | importing a ClipAsm source program | 2 seconds | — | Import how-to |
examples/external-brighten.clipasm | trusted external implementation | 2 seconds | Python 3 and a code review | External-program guide |
examples/root-bindings.clipasm | root Video input and required parameters | 2 seconds | CLI bindings and --output | Root-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:
| Topic | Page |
|---|---|
| file layout, project settings, inputs, parameters, and output | Files and configuration |
| exact numbers, durations, expressions, and scalar aliases | Scalar values and expressions |
| calls, optional syntax, bodies, and generic type selection | Statements and calls |
| implicit inputs, explicit graph arguments, ownership, and visibility | Stack binding |
clip, stack blocks, graph names, and references | Composition forms |
| placement paths, marker ranges, and timeline coordinates | Timeline selectors and ranges |
| source imports and trusted external implementations | Imports and external programs |
| built-in call signatures and examples | Built-in programs |
| CLI source and root bindings | Command-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:
- the TimeRange operator
.. - addition and subtraction
- multiplication and division
- unary
+and- - postfix
%,ms,s, andf - 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)
@ownedor@visiblechooses 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 outputbinds 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.
clipis 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
executableis either a source-relative path or a bare name found through the platform command lookup.argumentsis an ordered list of literal strings andfile("...")values.semantic_versionis a positive author-controlled version for output meaning.preservenames 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:
| Option | Meaning |
|---|---|
--video-input NAME=VIDEO_PATH | Bind one declared root Video input. |
--audio-input NAME=AUDIO_PATH | Bind one declared root Audio input. |
--arg NAME=VALUE | Bind 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 result | Success summary |
|---|---|
| One Video with an authored domain | exact frame count |
| One Video whose domain depends on media | duration resolves during preflight |
| One Audio output | output type |
| Zero or multiple outputs | output 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
Related documentation
- Check a program before rendering
- Supply root inputs and parameters
- Troubleshooting
- Language reference
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
| Program | Summary | Type shape | Properties |
|---|---|---|---|
image | Create a Video from an image file. | () -> Video | — |
video | Load a Video from a video file. | () -> Video | — |
audio | Load standalone Audio from an audio file. | () -> Audio | — |
Timeline
| Program | Summary | Type shape | Properties |
|---|---|---|---|
concat | Concatenate one or more homogeneous timelines. | (T...) -> T | generic |
repeat | Repeat a Video or Audio timeline. | (T) -> T | generic |
trim | Keep a selected range of a Video or Audio timeline. | (T) -> T | generic |
drop | Remove one Video or Audio value from the stack. | (T) -> none | generic |
Audio
| Program | Summary | Type shape | Properties |
|---|---|---|---|
extract_audio | Extract the meaningful Audio from a Video. | (Video) -> Audio | — |
set_audio | Replace a Video’s Audio with standalone Audio. | (Video, Audio) -> Video | — |
Effects
| Program | Summary | Type shape | Properties |
|---|---|---|---|
zoom_in | Apply a linear zoom-in effect to a Video. | (Video) -> Video | — |
Transitions
| Program | Summary | Type shape | Properties |
|---|---|---|---|
flash_cut | Join two Videos with a brief white-flash transition. | (Video, Video) -> Video | — |
crossfade | Overlap two Videos or Audio values with a crossfade transition. | (T, T) -> T | generic |
Body programs
| Program | Summary | Type shape | Properties |
|---|---|---|---|
join | Transform and concatenate two Video or Audio timelines in a body. | (T, T) -> T | generic, accepts a body |
during | Replace a selected timeline range with the result of a body. | (T) -> T | generic, 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
| Name | Type | Requirement | Default or omission behavior |
|---|---|---|---|
path | File | required | — |
duration | Duration | optional | uses a requested Video extent from the surrounding body. Without one, the call reports a missing image duration |
fit | Keyword(cover | contain | stretch) | optional | cover |
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
covermode fills the frame and crops overflow. Thecontainmode adds padding. Thestretchmode 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.
E_MISSING_IMAGE_DURATION— Missing image durationE_INVALID_DURATION— Invalid duration
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
| Name | Type | Requirement | Default or omission behavior |
|---|---|---|---|
path | File | required | — |
fit | Keyword(cover | contain | stretch) | optional | cover |
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
| Name | Type | Requirement | Default or omission behavior |
|---|---|---|---|
path | File | required | — |
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
| Name | Type | Cardinality |
|---|---|---|
video | Video | exactly 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
| Name | Type | Cardinality |
|---|---|---|
video | Video | exactly one |
audio | Audio | exactly 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
| Name | Type | Cardinality |
|---|---|---|
values | T | one 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>orconcat<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
| Name | Type | Cardinality |
|---|---|---|
value | T | exactly one |
Parameters and defaults
| Name | Type | Requirement | Default or omission behavior |
|---|---|---|---|
count | Integer | required | — |
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.
E_INVALID_REPEAT_COUNT— Invalid repeat count
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
| Name | Type | Cardinality |
|---|---|---|
value | T | exactly one |
Parameters and defaults
| Name | Type | Requirement | Default or omission behavior |
|---|---|---|---|
range | TimeRange | required | — |
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.
E_INVALID_TIME_RANGE— Invalid time range
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
| Name | Type | Cardinality |
|---|---|---|
value | T | exactly 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
| Name | Type | Cardinality |
|---|---|---|
video | Video | exactly one |
Parameters and defaults
| Name | Type | Requirement | Default or omission behavior |
|---|---|---|---|
by | Number | optional | 8% |
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.
E_INVALID_ZOOM_AMOUNT— Invalid zoom amountE_GRAPH_TOO_LARGE— Graph size limit exceeded
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
| Name | Type | Cardinality |
|---|---|---|
before | Video | exactly one |
after | Video | exactly one |
Parameters and defaults
| Name | Type | Requirement | Default or omission behavior |
|---|---|---|---|
duration | Duration | optional | 160ms |
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.
E_INVALID_FLASH_CUT_DURATION— Invalid flash-cut duration
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
| Name | Type | Cardinality |
|---|---|---|
before | T | exactly one |
after | T | exactly one |
Parameters and defaults
| Name | Type | Requirement | Default or omission behavior |
|---|---|---|---|
duration | Duration | optional | 500ms |
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.
E_INVALID_CROSSFADE_DURATION— Invalid crossfade durationE_CROSSFADE_AUDIO_DURATION— Invalid crossfade audio duration
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
| Name | Type | Cardinality |
|---|---|---|
before | T | exactly one |
after | T | exactly 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:
Tfrom the completebeforeinputTfrom the completeafterinput
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
beforefollowed byafter. - The body exposes the inputs as the lexical
$beforeand$afterreferences. - 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.
E_EMPTY_JOIN— Empty join result
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
| Name | Type | Cardinality |
|---|---|---|
timeline | T | exactly one |
Parameters and defaults
| Name | Type | Requirement | Default or omission behavior |
|---|---|---|---|
range | TimeRange | required | — |
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:
Tfrom the range selected fromtimelinebyrange
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
$timelinereference. - 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
replacementname 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>orduring<Audio>when a mixed stack makes the generic type ambiguous.
Common diagnostics
These are the diagnostics most specific to this program.
E_BODY_OUTPUT_COUNT— Wrong body output count
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
| Document | Current version | Produced or consumed by | Intended use |
|---|---|---|---|
| Compiled inspection JSON | format_version: 24 | clipasm inspect | source-analysis and diagnostic tooling |
| Render manifest | format_version: 4 | successful native render | automation and render provenance |
| External-program request | protocol_version: 3 | trusted external executable | implementing 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 (
allorfused) 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:
- Read the version field first.
- Accept only versions your software explicitly supports.
- Ignore unknown fields only when doing so is safe for that decoder.
- Review and test every version change.
- Never use JSON object ordering as identity.