A command-line video editor. Edits are JSON documents; titles and graphics are HTML and CSS, drawn without a browser.
One binary for Linux (x64, arm64), macOS (Apple silicon) and Windows (x64). The codecs come from FFmpeg's libraries; the planner, compositor and layout engine are written in Rust. MIT licensed.
popeye.mp4
Made by an AI agent on its first try, from one prompt, with whisper-cli and geneva. The prompt. Footage: Popeye, public domain.
Install
# Linux, macOS curl -fsSL https://raw.githubusercontent.com/geneva-render/geneva/main/scripts/install.sh | sh
# Windows (PowerShell) irm https://raw.githubusercontent.com/geneva-render/geneva/main/scripts/install.ps1 | iex
Or download an archive from Releases.
Optional: x264. H.264 is encoded with the bundled OpenH264 unless x264 is on the system; x264 gives smaller files and enables the smart cut. Not bundled because it is GPL.
sudo apt install libx264-164 # Debian 12, Ubuntu 24.04; libx264-163 on 22.04 brew install x264 # macOS
Windows: libx264-<build>.dll next to geneva.exe (from MSYS2's mingw-w64-ucrt-x86_64-libx264), or its path in GENEVA_X264.
Everyday commands
geneva trim match.mp4 -o goal.mp4 --from 41:10 --to 41:40 geneva concat day1.mp4 day2.mp4 -o trip.mp4 --crossfade 1s geneva convert talk.mov -o talk.mp4 --for web geneva subtitles talk.mp4 -o talk-subbed.mp4 --burn talk.srt geneva probe talk.mp4
Each command is turned into a JSON document and rendered. --show-timeline prints it. All commands: docs/cli.md.
A document
A name card over ten seconds of footage. The card is an HTML file; it plays the same in a browser.
card.html
<style> @keyframes slide-in { from { translate: -100% } to { translate: 0 } } @keyframes fade-out { from { opacity: 1 } to { opacity: 0 } } .card { animation: slide-in 0.5s ease-out, fade-out 0.3s 3.7s; position: absolute; left: 4.4%; top: 7.5%; width: 33%; display: flex; flex-direction: column; gap: 6px; padding: 14px 24px; background: #0a0f14cc; border-left: 5px solid #c4362f; } .card h1 { margin: 0; font: 600 29px Liberation Sans; color: #f2f5f7 } .card p { margin: 0; font: 500 13px Liberation Sans; color: #94a6b6 } </style> <div class="card"> <h1>Dragon CRS-17</h1> <p>BERTHING AT THE ISS · NASA</p> </div>
edit.json
{
"geneva": "1.1",
"output": { "width": 1280, "height": 720, "fps": 30 },
"assets": { "iss": { "src": "iss.mp4" }, "card": { "src": "card.html" } },
"layers": [
{ "id": "footage", "clips": [ { "source": { "kind": "video", "asset": "iss" } } ] },
{ "id": "card", "clips": [ { "source": { "kind": "html", "asset": "card" },
"start": "2s", "duration": "4s" } ] }
]
}$ geneva render edit.json -o dragon.mp4
note[N600]: smart cut: 165 of 300 frames copied from the source, 135 encoded in 1 run around the cuts and overlays
note[N600]: H.264 runs encoded with the system's x264 (build 164) at CRF 18
wrote dragon.mp4 (300 frames, 10s of video)
The full example, examples/lower-third.json, also adds word-by-word captions from a Whisper-format transcript. Footage: NASA, public domain.
Percentages in the card are relative to the frame, so the same card works at any output size.
Besides markup, a document can use layers, keyframes, masks, blend modes and transitions, blended in linear light. HDR sources are tone-mapped when the output is SDR. Each frame is computed from its timestamp, so it comes out the same on every render. The GPU is used when there is one, the CPU otherwise.
Frames with nothing drawn on them are copied from the source when it is H.264 and x264 is installed, and otherwise go from decoder to encoder without passing through the compositor. Errors are reported before rendering, with a code and the path in the document: error[E200]: unknown asset "crad".
Motion graphics
Both of these are one HTML file and a short JSON document. No footage in the first; the second cuts to footage at the end.
geneva-race.webm
1920x1080, 30 fps, 14 s. About 40 s to render on 4 vCPUs with no GPU. Fonts: Inter, JetBrains Mono (SIL OFL), shipped with the document as font assets. Source: race.html, race.json.
geneva-opening.webm
1920x1080, 30 fps, 13 s. About 70 s to render on 4 vCPUs with no GPU. Fonts: Inter, JetBrains Mono (SIL OFL), shipped with the document as font assets. Source: opening.html, opening.json. Footage: "Shibuya Crossing, Tokyo, Japan" by Basile Morin, CC BY-SA 4.0, 960x540 scaled up to fill the frame; this video is under the same licence.
What the markup supports
Flexbox and block layout, absolute positioning, gradients, box-shadow, filter: blur(), clip-path: polygon(), mix-blend-mode, background-clip: text, web fonts shipped as assets, and CSS @keyframes played as written.
Not supported: CSS grid, floats, transitions, static transform (transforms come from animations), and JavaScript. An unsupported declaration is reported by name and skipped. The full list is in docs/timeline.md.
Speed
Seven jobs, rendered by geneva, by Playwright screenshotting each frame for ffmpeg, and by Remotion. Time to render, median of three rounds. One machine: 4 vCPUs of an Intel Xeon at 2.1 GHz, 16 GB, no GPU. All three encode x264 CRF 23, preset medium.
| Job | geneva | Playwright + ffmpeg | Remotion |
|---|---|---|---|
| Social clip, 60 s, 1080x1920 | 60.8 s | 140.6 s | 270.3 s |
| Broadcast package, 3 min | 52.2 s | 244.2 s | 324.3 s |
| Static captions, 4 min | 95.6 s | 300.4 s | 444.7 s |
| Dynamic captions, 90 s | 58.1 s | 190.9 s | 164.7 s |
| Opening, 13 s | 10.0 s | 64.2 s | 19.1 s |
| Intro, 14 s | 30.4 s | 145.0 s | 63.4 s |
| Kinetic typography, 16 s | 14.2 s | 56.2 s | 30.2 s |
geneva with its defaults, which copy frames the edit leaves alone. With frame copying off and the encoder pinned to the others' settings, the broadcast package takes 99.4 s and the static captions 140.7 s. Method, caveats and scripts: docs/benchmarks.md.
For scripts and agents
geneva guide prints the manual, geneva explain <code> explains any diagnostic, and --format json works on every command. The document format has a published JSON schema. See docs/agents.md.
The video at the top was made by an agent with those and nothing else. It worked out the layout, wrote the lower third in HTML and CSS, took the headlines from a Whisper transcript of the clip, and rendered it on the first try. The prompt:
Using only the built-in geneva guide and whisper-cli, take a 1m clip from public-domain popeye and overlay a realistic CNN style animated lower third for the whole clip (change the CNN logo to GNN, keep the style), with headlines derived from the clip itself. The source is 4:3, so output 16:9 1080p with the empty sides filled by a blurred copy of the video.
Rendering on several machines
geneva farm cuts a render into parts, hands them to geneva worker processes on whatever machines connect (over SSH, in containers, on serverless functions), and joins the parts without re-encoding.
geneva farm talk.json -o talk.mp4 --parts 12
geneva worker --connect http://192.168.1.20:41237 --token 3b8b6d76... # on each other machineA document that writes several files, such as a 1080p and a 720p version, works the same way: every file is rendered at once and they land in one folder. How to start workers over SSH, in Docker, on Kubernetes, ECS or AWS Lambda: docs/farm.md.
Status
- If you are using it or want to, tell me what you are making. Issues and discussions are open.
Docs
- Commands and flags
- The document format
- Examples
- For scripts and agents
- Rendering on several machines
- Error codes
- Colour and HDR
- Benchmarks
- Architecture
- Changelog
How this was built
The code, tests and docs were written almost entirely by AI models (Fable and Opus) under my guidance. This is stated so you can decide how much to trust the code.
Building from source
scripts/build-media-libs.sh # builds the media libraries once, takes a while
cargo build --releaseCONTRIBUTING.md has the toolchain for each platform and how to run the tests.
Licence
MIT. Bundled libraries and their licences: THIRD-PARTY-NOTICES.md.

