reading time : 8 minutes

Two weeks ago, I launched`capsule-gradle`— a plugin that transforms a reveal.js deck into a WebM video capsule with TTS. Today, I am adding a new plugin: hyperframes-gradle. It does something that capsule-gradle does not: transform AsciiDoc directly into MP4 video, without going through reveal.js.

Here is why this plugin exists, how it interfaces with the existing setup, and what the HyperFrames engine by HeyGen brings to the content production pipeline.

The Gap in the Video Pipeline

My documentation pipeline produces two types of videos:

  1. Revision capsule (capsule-gradle) — a reveal.js deck captured in WebM with

TTS narration. Perfect for reviewing a module.

  1. Animation slides (slider-gradle) — an interactive HTML reveal.js deck for

the in-person trainer.

But a third format is missing: the standalone video.

Use case

Current solution

Missing

Trainer slides

slider-gradle → reveal.js

Revision capsule

capsule-gradle → WebM

Teaser video

None

Animated technical demo

None

Doc-to-video

None

The first use case is critical: how to automatically generate a 60-second video presenting a project or a module, with GSAP animations, TTS narration, and background music — all from the same AsciiDoc file as the slides?

Capsule-gradle cannot answer this: it already requires a reveal.js deck. slider-gradle does produce this deck, but it is not designed for video rendering. A direct pipeline was needed: AsciiDoc → video.

HyperFrames: The Engine

I discoveredhttps://github.com/heygen-com/hyperframes[HyperFrames] il y a a few days ago. It is an open-source framework (Apache 2.0) created by HeyGen, the AI video unicorn. 22,700 stars on GitHub. The pitch can be summed up in one sentence:

Write HTML. Render video. Built for agents.

The principle: you write HTML with`data-*`attributes, you reference a GSAP animation (or CSS, Lottie, Three.js…), and the engine renders it all into MP4 via Puppeteer (headless Chrome) + FFmpeg. Deterministic result: same input, same frames, same video.

<div id="stage" data-composition-id="intro" data-start="0" data-width="1920" data-height="1080">
  <video class="clip" data-start="0" data-duration="6" data-track-index="0"
         src="background.mp4" muted playsinline></video>

  <h1 id="title" class="clip" data-start="1" data-duration="4" data-track-index="1">
    Formation Docker & Kubernetes
  </h1>

  <audio data-start="0" data-duration="6" data-track-index="2"
         data-volume="0.3" src="music.wav"></audio>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
  <script>
    const tl = gsap.timeline({ paused: true });
    tl.from("#title", { opacity: 0, y: 40, duration: 0.8 }, 1);
    window.__timelines = window.__timelines || {};
    window.__timelines.intro = tl;
  </script>
</div>

What makes HyperFrames different from Remotion? No React, no bundler, no JSX. Raw HTML that AI agents know how to write natively. It is tailored exactly for my pipeline: planner-gradle with deepseek can generate this HTML from an SPG/SPD plan, and a Gradle plugin can convert it to MP4.

The Plugin Architecture

hyperframes-gradle follows the same pattern as all my plugins: the « Independent Plugin + Consumer Root » pattern.

hyperframes-gradle/
├── settings.gradle.kts          ← racine consommateur (dogfood)
├── build.gradle.kts              ← 3 lignes : apply plugin hyperframes
├── hyperframes-plugin/           ← BUILD INDÉPENDANT
│   ├── gradlew                    ← son propre wrapper
│   ├── build.gradle.kts           ← java-gradle-plugin
│   ├── src/main/kotlin/education/cccp/hyperframes/
│   │   ├── HyperframesPlugin.kt
│   │   ├── GenerateHyperframesHtmlTask.kt
│   │   ├── RenderHyperframesTask.kt
│   │   └── HyperframesExtension.kt
│   ├── .agents/                   ← gouvernance agent
│   └── doc/
│       └── HYPERFRAMES_ARCHITECTURE.adoc
└── video.yml                      ← configuration dogfood

The Four-Step Pipeline

pipeline hyperframes
  1. AsciidoctorJ parses the source AsciiDoc and extracts the custom blocks

[hyperframes-composition], [hyperframes-track], [hyperframes-animation].

  1. GenerateHyperframesHtmlTask generates the HyperFrames HTML with the

`data-*`corresponding attributes.

  1. RenderHyperframesTask calls the HyperFrames CLI via`ProcessBuilder`:

npx hyperframes render --input index.html --output video.mp4.

  1. The MP4 is deployed into`output/`with a`metadata.json`for orchestration.

The AsciiDoc DSL

The true added value of the plugin is the AsciiDoc DSL. The user never writes HTML. They annotate their existing AsciiDoc document:

= Formation Docker & Kubernetes
:hyperframes-width: 1920
:hyperframes-height: 1080
:hyperframes-fps: 30

[hyperframes-composition, id="intro"]
== Introduction

Le titre apparaît avec un fondu GSAP sur fond vidéo.

[hyperframes-track, index=0, start=0, duration=6]
video::assets/background.mp4[muted, playsinline]

[hyperframes-track, index=1, start=1, duration=4]
Formation Docker & Kubernetes

[hyperframes-animation, type=gsap]

const tl = gsap.timeline({ paused: true }); tl.from("#title", { opacity: 0, y: 40, duration: 0.8 }, 1); window.timelines = window.timelines || {}; window.__timelines.intro = tl;

The docinfo attributes (:hyperframes-width:) configure the dimensions. Custom blocks define the compositions and tracks. The `[hyperframes-animation]`block contains the raw GSAP code — which the AI agent can generate from a natural language description.

The Node.js Bridge

The only technical point of friction: HyperFrames is in Node.js/TypeScript, and my plugins are in Kotlin/JVM. The solution is a`ProcessBuilder`:

class RenderHyperframesTask : DefaultTask() {

    @TaskAction
    fun render() {
        val process = ProcessBuilder(
            "npx", "hyperframes", "render",
            "--input", inputHtml.absolutePath,
            "--output", outputMp4.absolutePath
        )
            .inheritIO()
            .start()

        val exitCode = process.waitFor()
        require(exitCode == 0) {
            "HyperFrames render failed with exit code $exitCode"
        }
    }
}

Zero code coupling JVM ↔ Node.js. The contract is via command line and files on disk. This is the same pattern as`plantuml-gradle` (which calls the PlantUML CLI) or`slider-gradle`(which calls AsciidoctorJ). Nothing new under the sun — just applied to a more recent tool.

Two Video Plugins, Two Usages

capsule-gradle

hyperframes-gradle

Source

reveal.js deck (existing HTML)

AsciiDoc → HyperFrames HTML

Use case

Revision capsule

Standalone explanatory video

Nature

Slides → video

Document → video

Rendering

Playwright Java → WebM

HyperFrames CLI → MP4

Animation

Native reveal.js transitions

GSAP/CSS data-attributes

Stack

100% JVM (Kotlin)

JVM + external Node.js CLI

They do not replace each other. They complement each other. The complete value chain:

AsciiDoc ──→ slider-gradle ──→ reveal.js deck ──→ capsule-gradle ──→ WebM (revision capsule)

AsciiDoc ──→ hyperframes-gradle ──→ MP4 (standalone video)

The user writes a single AsciiDoc file. slider-gradle produces the slides. capsule-gradle produces the revision capsule. hyperframes-gradle produces the teaser video and technical demos.

The 6 EPICs of the Roadmap

The plugin is structured into 6 EPICs:

EPIC

Description

Priority

HF-0

Bootstrap governance + architecture scoping

✅ COMPLETED

HF-1

Gradle plugin stub + AsciidoctorJ integration

P0

HF-2

HyperFrames CLI Bridge (ProcessBuilder → npx → MP4)

P1

HF-3

Custom AsciiDoc DSL (blocks, docinfo, templates)

P1

HF-4

runner-gradle integration (metadata.json)

P2

HF-5

Ready-to-use templates (title-card, code-diff, captions)

P3

HF-6

CI + Maven Central / Gradle Portal publication

P3

Session 000 (bootstrap) is already completed. Session 001 will tackle HF-1: the Gradle plugin stub and AsciidoctorJ integration.

Why It Works

Three reasons:

  1. HyperFrames is "built for agents". My pipeline relies on AI agents

(planner-gradle + deepseek) that generate content. HyperFrames accepts raw HTML — the format that LLMs master best. No React, no JSX, no learning curve for the agent.

  1. The AsciiDoc DSL is natural. My entire ecosystem speaks AsciiDoc.

slider-gradle, codex-gradle, training-gradle — they all consume`.adoc`. Adding custom blocks`[hyperframes-composition]`is a logical extension, not a break.

  1. The Node.js bridge is a proven pattern. ProcessBuilder outside the JVM,

that is what I already do for PlantUML, Graphviz, Piper. Adding HyperFrames changes nothing in the architecture — it is just one more external tool, driven from Gradle.

The true strength of the pattern is that there are no npm dependencies in the Gradle build. The plugin does not import Node.js code. It executes a shell command. If HyperFrames evolves or breaks, the plugin is not coupled — simply update the command.

With hyperframes-gradle, my video pipeline is complete. I have:

  • The interactive slides (slider-gradle)

  • The revision capsules (capsule-gradle)

  • The standalone videos (hyperframes-gradle)

Three formats. A single source file: AsciiDoc.

The next goal is HF-1: get the plugin to compile and generate a first HyperFrames HTML from an annotated AsciiDoc document. Session 001 is already scoped.

hyperframes-gradle exists. The governance is in place. The backlog is written. The architecture is documented. The plugin is born.

References

Related articles