Skip to content

Render your first reel

By the end of this you'll have a real 1080×1920 H.264 mp4 on disk, built by Keryx from a storyboard you wrote. About fifteen minutes.

There's no narration and no music in this one, and no AI generation of any kind. That means no API keys and nothing to pay for — which makes it the fastest way to see whether Keryx is doing what you expect before you point money at it. Adding voice, music and generated card art comes later, and the how-to guides cover each.

There's also no video software to install. Keryx renders with FFmpeg compiled to WebAssembly, fetched on first use and cached, so the whole reel is assembled by the keryx binary itself.

Before you start

You'll need:

  • Go 1.26 or newer, if you're installing from source.
  • A network connection for the first render. Keryx fetches its render engine (about 6 MB) the first time it needs it, checks its signature, and caches it. Later renders don't re-download it.
  • A directory to work in. Keryx keeps no state of its own; everything lives in the project directory you run it from, so a scratch folder is fine for now.

You do not need ffmpeg installed. If you already have one you'd rather use, the last section shows how to switch to it.

Install Keryx

go install gitlab.com/phpboyscout/keryx/cmd/keryx@latest

That puts a keryx binary in $(go env GOPATH)/bin. Check it:

$ keryx version

Pre-built archives are attached to each release on GitLab if you'd rather not build from source.

Write the configuration file

Keryx refuses to run without a configuration file. Every command fails the same way until one exists:

ERRO failed to load configuration: no config file found hints="Run 'keryx init' to create a configuration."

So do that:

$ keryx init
INFO Initialising configuration
INFO configuration initialised path=/home/you/.keryx/config.yaml

That writes ~/.keryx/config.yaml with sensible defaults — every posting platform disabled, alerting off, no themes. On a terminal it also offers to walk you through credential set-up; you don't need any of that today, and keryx init --skip-ai --skip-login --skip-key skips the prompts entirely.

Have a look at what it wrote. It ships with an example avatar and voice registered under the name matt, which are the author's own — harmless, but not yours. Delete the avatars: and voices: blocks if you find them confusing.

Add a theme, because there isn't one

Keryx has no built-in themes at all. Palette, fonts, card treatment, music tone and voice settings are configuration, never code — so nothing is seeded and the first generation you attempt will say so:

no theme configured — add one with `keryx theme add <keyword>` (the first theme added becomes the default)

Add a minimal theme with two palette roles and a card treatment:

keryx theme add starter \
  --set 'palette.charcoal=#1A1D21' \
  --set 'palette.cream=#F4F1EA' \
  --set 'card.mode=block'
added theme "starter"
set as the default theme (it is the only one)

The first theme added becomes the default, so you won't have to name it again. card.mode=block means each card is one line of text over a solid palette colour — the treatment that needs no illustration, which is exactly what you want with no image provider configured.

Check it landed:

$ keryx theme list
themes (default: starter)
* starter                  user

It went into ~/.keryx/themes.yaml, so it's available to every project on this machine. A project can carry its own themes in a .keryx.yaml at its root instead.

Create a reel workspace

Move into your working directory and create one:

$ keryx reel new hello-keryx
created reels/hello-keryx

That's a directory holding everything for one reel:

$ ls -A reels/hello-keryx
.cache  cards  .gitignore  music  storyboard.json  vo  workspace.yaml

workspace.yaml is what makes the directory a workspace — it records the reel's settings, and the commands below find their work through it. storyboard.json starts empty, vo/, cards/ and music/ hold the generated media, and .cache/ is disposable and kept out of git by the .gitignore. The full list of what goes where is in the workspace layout reference.

The slug has to be lowercase kebab-case — hello-keryx is fine, Hello Keryx is not.

Write a three-card storyboard

The storyboard is the creative seed: an ordered JSON array, one object per card. Open reels/hello-keryx/storyboard.jsonreel new left it as an empty array — and replace it with this:

[
  { "text": "Keryx turns a post", "bg": "charcoal", "fg": "cream", "dur": 2.0 },
  { "text": "into a short reel.", "bg": "charcoal", "fg": "cream", "dur": 2.0 },
  { "text": "keryx.phpboyscout.uk", "bg": "cream", "fg": "charcoal", "mono": true, "dur": 2.5 }
]

Four things worth knowing about those fields:

  • bg and fg are palette role names, not colours. They have to match roles you defined on the theme — charcoal and cream above. A role the theme doesn't define is a validation error, which is the point: change the theme and every card follows.
  • dur is seconds on screen, and it's only used when there's no narration. Once a card has a voice-over clip, the clip's length drives the timing and dur is ignored.
  • mono switches that card to the theme's monospace font — the convention for a closing URL card.
  • There's no title or duration field. The full set is in the storyboard reference.

Render it

$ keryx reel build --workspace hello-keryx --silent
rendered 3 cards (5.7s) -> reels/hello-keryx/reel-hello-keryx.mp4
assets: 0 selected, 0 locked

--silent says "video only, no narration and no music", and it's what makes each card fall back to its dur. Without it, Keryx expects promoted voice-over clips and will name any card that has neither a clip nor a dur.

On an interactive terminal you'll see a rendering… NN% line while it works. It's written to stderr, so piping the output somewhere leaves you with just the result.

The reel is 5.7 seconds rather than the 6.5 you might expect from adding up the dur values. Cards crossfade into each other by 0.4 s, and two transitions cost 0.8 s.

Play it. Three cards, a crossfade between each, no sound.

If you happen to have FFmpeg installed anyway, you can confirm the shape from the command line — this is the one step in the tutorial that needs it, and skipping it costs you nothing:

$ ffprobe -v error -show_entries stream=width,height,codec_name \
    -of default=nw=1 reels/hello-keryx/reel-hello-keryx.mp4
codec_name=h264
width=1080
height=1920

Change something and watch it change

The build is cheap and repeatable, so this is the loop you'll spend most of your time in. Swap the palette roles on the last card:

{ "text": "keryx.phpboyscout.uk", "bg": "charcoal", "fg": "cream", "mono": true, "dur": 2.5 }

Run the same build again. It overwrites the mp4 in place — nothing accumulates, and nothing else in the workspace is touched.

Get a palette role wrong and the build stops before rendering anything:

$ keryx reel build --workspace hello-keryx --silent
ERRO invalid storyboard:
  - card 1: bg references undefined palette role "nosuchrole"

You can also see what a build would do without doing it:

$ keryx reel plan --workspace hello-keryx
reel plan: hello-keryx  [silent fallback (storyboard dur — no promoted VO)]

card  dur    start  vo_delay  source
1     2.000  0.000  0.500     storyboard
2     2.000  1.600  2.100     storyboard
3     2.500  3.200  3.700     storyboard

total: 5.700s   music bed: 35.000s (35000ms)
assets: 0 selected, 0 locked

5 pre-build gap(s):
  - [missing_vo] card 1: no VO — silent fallback
  - [missing_vo] card 2: no VO — silent fallback
  - [missing_vo] card 3: no VO — silent fallback
  - [missing_cover] no cover.png
  - [no_music] no music bed

The start column shows the crossfade at work — card 2 starts at 1.6 s, not 2.0 s. The gaps are expected here; every one of them is something you haven't generated yet. The 35 s music bed is the fallback length used when no narration exists to size a bed against.

Rendering with your own ffmpeg instead

The render you just did used the WebAssembly engine, which is why you didn't install anything. If you already have an FFmpeg build you'd rather use — a tuned one, or one with codecs the published engine doesn't carry — switch to it:

# ~/.keryx/config.yaml
providers:
  render: ffmpeg

That shells out to ffmpeg on your PATH, so ffmpeg -version needs to print something before it'll work.

One capability only the system backend has. VO take screening (voice takes --screen, voice pick --best) uses FFmpeg's silencedetect to find the take with the fewest internal pauses. The published WebAssembly module is built lean and doesn't include that filter, so on the default engine screening ranks takes on length alone and tells you it's doing so:

INFO silence screening unavailable on the afmpeg render backend; ranking takes on duration alone

That doesn't matter for this tutorial — there's no narration in it. It starts to matter when you're picking between five reads of the same line.

How fast the default engine is depends on your machine. Where a native driver is published for your platform, Keryx fetches that and renders in seconds. Where one isn't, it falls back to running the engine in WebAssembly, which is dramatically slower — minutes rather than seconds for a short reel, on a typical laptop.

The gap is not fixed. Building a reel is encoding work, which is the hardest thing for the WebAssembly engine — it runs on one core and without the CPU's video instructions. The native engine uses every core you have, so fewer cores narrow the gap and more cores widen it. Keryx tells you which engine it used, so a long render is never a mystery.

Native drivers exist for linux/amd64 today, with more planned.

Where to go next