A tiny declarative FFmpeg timeline builder in Odin.
The model is deliberately simple:
Odin timeline data
↓
filter_complex.txt
↓
real ffmpeg process
↓
output.mp4
It does not bind libav* and it does not try to wrap every FFmpeg filter. It builds the parts that are annoying to author by hand and leaves an escape hatch with timeline.raw(...).
Put your audio at song.m4a, then:
odin run .or:
just runThe example recreates the temporal-incident style:
tl.text_file(&timeline, "texts/tuesday.txt", 52, 74,
tl.Text_Style{
x = "(w-text_w)/2",
y = "185",
size = 42,
color = "white",
alpha = 1,
line_spacing = 16,
})
tl.countdown(&timeline, 136, 142, []string{"10", "5", "3", "2", "1"})init/destroyaudio_reactive_archive— starts the spectrum + waveform visualizer graphtext_file— timed text from a file; preferred for arbitrary prosetext_literal— timed inline text for simple safe stringscountdown— evenly splits a time range across valuesraw— append any raw FFmpeg filterfinish— finalize to[vout]write_filter— write the generated filtergraphrender— invoke FFmpeg throughcore:os.process_exec
FFmpeg drawtext escaping can become unpleasant quickly. textfile= makes the timeline readable and keeps punctuation-heavy copy out of the filtergraph.
The code targets the current Odin core:os.process_exec and core:strings.Builder APIs documented for dev-2026-07.
