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.

Before you start

You'll need:

  • Go 1.26 or newer, if you're installing from source.
  • ffmpeg on your PATH. Keryx shells out to it by default. ffmpeg -version should print something. (There's a second renderer that needs no system ffmpeg at all — see the last section.)
  • 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.

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 for this type — add one with `keryx theme add <keyword> --type <type>`

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

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

The first theme of a type becomes that type's 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

reel (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: an empty storyboard, the vo/ and cards/ directories, the disposable takes/ and .cache/ directories, and a .gitignore so the disposable parts stay out of git. 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.

Check the file is what it claims to be:

$ 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

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

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 without a system ffmpeg

If ffmpeg isn't installed and you'd rather not install it, Keryx ships a second renderer that runs FFmpeg compiled to WebAssembly, entirely in memory:

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

The first render downloads a pinned ffmpeg-wasi module — a few tens of megabytes — verifies its SHA-256 and caches it, so only the first run is slow. You don't need to fetch anything by hand.

One limit worth knowing before you set it: because providers.render is a plain value and providers.render.module is a nested one, you cannot select afmpeg in config and also override its module in config. If you need a locally-built or mirrored module, point at it with the KERYX_FFMPEG_WASI environment variable instead.

Where to go next