Headless session → MP4 screencast. Replays a persisted session's transcript
through the REAL TUI render pipeline (screen/render-frame! painting into a
Lanterna VirtualTerminal — no TTY needed), and encodes the captured frames
to .mp4:
pure-JVM H.264 via jcodec's AWTSequenceEncoder (each frame rendered to a
BufferedImage with Java2D). Pixel-exact because WE draw the glyphs — no
external player font drift. No native deps, GraalVM-friendly.
The replay is HUMANIZED rather than a flat scroll-through: every disclosure
COLLAPSED, the session is re-enacted turn by turn as if it were happening
live — a user turn is TYPED IN character by character; before each answer a
WORK beat holds the live Vis is calling the provider… spinner in the
assistant's place; then the finished answer JUMPS IN and SLOWLY scrolls into
view. See session->frames.
The heavy TUI stack (screen, Lanterna) is reached via requiring-resolve
so merely loading this namespace stays cheap.
Headless session → MP4 screencast. Replays a persisted session's transcript through the REAL TUI render pipeline (`screen/render-frame!` painting into a Lanterna `VirtualTerminal` — no TTY needed), and encodes the captured frames to `.mp4`: pure-JVM H.264 via jcodec's `AWTSequenceEncoder` (each frame rendered to a `BufferedImage` with Java2D). Pixel-exact because WE draw the glyphs — no external player font drift. No native deps, GraalVM-friendly. The replay is HUMANIZED rather than a flat scroll-through: every disclosure COLLAPSED, the session is re-enacted turn by turn as if it were happening live — a user turn is TYPED IN character by character; before each answer a WORK beat holds the live `Vis is calling the provider…` spinner in the assistant's place; then the finished answer JUMPS IN and SLOWLY scrolls into view. See `session->frames`. The heavy TUI stack (`screen`, Lanterna) is reached via `requiring-resolve` so merely loading this namespace stays cheap.
(export! session-id opts)Render session-id to an MP4 screencast file.
opts:
:out output path (default: <session-id>.mp4 in the cwd)
:cols :rows :fps :theme :font-size
:type-cps :work-ms :jump-hold-ms :scroll-rows-per-frame :post-user-ms :post-assist-ms — see defaults
Returns {:path :format :frames :video-ms :cols :rows}.
Render `session-id` to an MP4 screencast file.
opts:
:out output path (default: <session-id>.mp4 in the cwd)
:cols :rows :fps :theme :font-size
:type-cps :work-ms :jump-hold-ms :scroll-rows-per-frame :post-user-ms :post-assist-ms — see `defaults`
Returns `{:path :format :frames :video-ms :cols :rows}`.(frames->mp4! {:keys [cols rows frames]}
out
{:keys [font-size fps] :or {font-size 18 fps 8}})Encode a session->frames result to out (an MP4 File/path) via jcodec's
pure-Java AWTSequenceEncoder. Returns the output File.
Encode a `session->frames` result to `out` (an MP4 File/path) via jcodec's pure-Java `AWTSequenceEncoder`. Returns the output File.
(session->frames session-id opts)Replay session-id headless and return
{:cols :rows :frames [{:grid :delay-ms} …] :video-ms}.
opts overrides defaults. The replay is HUMANIZED: rather than dumping the
whole transcript and scrolling through it, it RE-ENACTS the session as if it
were happening live, one message at a time, with every disclosure COLLAPSED
(the natural resting view a live session shows, not an expanded dump):
• a USER turn is TYPED IN character by character — the bubble grows as the
text lands, like a human at the keyboard — then a short beat;
• before each ASSISTANT answer there is a WORK beat: the LIVE progress
bubble holds in the assistant's place (⠿ Vis is calling the provider… 1.2s / Esc to cancel, spinner animating, elapsed clock ticking) so it
reads as real work being done;
• the finished answer then JUMPS IN whole (a bubble popping in near the
bottom of the viewport), holds a beat, then SLOWLY scrolls into view;
• a beat to read it, and on to the next turn.
Pacing is by FRAME COUNT at a constant fps (the encoder ignores per-frame
delays): every 'beat' is that many repeated frames, and the whole clip
length scales with the session's content. scroll/follow carries no :pos,
so each captured frame SNAPS to its target — deterministic, no stray ease.
Replay `session-id` headless and return
`{:cols :rows :frames [{:grid :delay-ms} …] :video-ms}`.
`opts` overrides `defaults`. The replay is HUMANIZED: rather than dumping the
whole transcript and scrolling through it, it RE-ENACTS the session as if it
were happening live, one message at a time, with every disclosure COLLAPSED
(the natural resting view a live session shows, not an expanded dump):
• a USER turn is TYPED IN character by character — the bubble grows as the
text lands, like a human at the keyboard — then a short beat;
• before each ASSISTANT answer there is a WORK beat: the LIVE progress
bubble holds in the assistant's place (`⠿ Vis is calling the provider…
1.2s / Esc to cancel`, spinner animating, elapsed clock ticking) so it
reads as real work being done;
• the finished answer then JUMPS IN whole (a bubble popping in near the
bottom of the viewport), holds a beat, then SLOWLY scrolls into view;
• a beat to read it, and on to the next turn.
Pacing is by FRAME COUNT at a constant `fps` (the encoder ignores per-frame
delays): every 'beat' is that many repeated frames, and the whole clip
length scales with the session's content. `scroll/follow` carries no `:pos`,
so each captured frame SNAPS to its target — deterministic, no stray ease.cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |