reading time : 8 minutes

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

Here’s why this plugin exists, how it fits with the existing, and what HeyGen’s HyperFrames engine brings to the content production pipeline.

The Hole in the Video Pipeline

My documentary pipeline produces two types of videos:

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

narration TTS. Perfect for reviewing a module.

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

the in-person trainer

But it lacks a third format: the standalone video.

use case

Current solution

Lack

Training slides

slider-gradle → reveal.js

</think> (output nothing)

Revision capsule

capsule-gradle → WebM

✅

Teaser video

None

�❌

Animated technical demo

None

�❌

Doc-to-video

None

❌

The first use case is critical: how to generate automatically a 60-second video that presents a project or a module, with GSAP animations narration TTS, and musical background — everything from the same AsciiDoc file what about the slides?

Capsule-gradle cannot respond: it already needs a reveal.js deck. slider-gradle produces this deck well, but it is not designed for video rendering. We needed a direct pipeline: AsciiDoc → video.

HyperFrames : The Engine

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

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 everything in 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 can write natively. It’s 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 Architecture of the Plugin

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

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

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultFontSize 11

title hyperframes-gradle pipeline
rectangle "AsciiDoc\nsource" as adoc #LightYellow
rectangle "AsciidoctorJ" as asciidoctorj #LightBlue
rectangle "HTML HyperFrames\n(data-* attrs)" as html #LightGreen
rectangle "HyperFrames CLI\n(Node.js 22+)" as cli #Lavender
rectangle "video.mp4" as mp4 #Gold

adoc -right-> asciidoctorj : parse blocs customs
asciidoctorj -right-> html : génère HTML
html -right-> cli : ProcessBuilder\nnpx hyperframes render
cli -right-> mp4 : Puppeteer + FFmpeg
@enduml
  1. AsciidoctorJ parses the AsciiDoc source 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 in`output/`with a`metadata.json`for orchestration.

The AsciiDoc DSL

The real added value of the plugin is the AsciiDoc DSL. The user He never writes HTML. He annotates his 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:`) set the dimensions. Custom blocks define the compositions and the tracks. The block `[hyperframes-animation]`contains the raw GSAP code — that the AI agent can generate from a natural language description.

The Node.js Bridge

The only technical friction point: 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 coupling between JVM code and Node.js. The contract is via the command line. and files on disk. It’s the same pattern as`plantuml-gradle` (which calls the PlantUML CLI) or`slider-gradle`(who calls AsciidoctorJ). Nothing new under the sun — just applied to a newer tool.

Two Video Plugins, Two Uses

capsule-gradle

hyperframes-gradle

Source

Deck reveal.js (existing HTML)

AsciiDoc → HTML HyperFrames

Use case

Revision capsule

Standalone explanatory video

Nature

Slides → video

Document → video

Rendering

Playwright Java → WebM

HyperFrames CLI → MP4

Animation

Transitions reveal.js natives

GSAP/CSS data-attributes

Stack

100% JVM (Kotlin)

JVM + external Node.js CLI

They don’t replace each other. They complement each other. The complete value chain:

AsciiDoc ──→ slider-gradle ──→ deck reveal.js ──→ 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 review capsule. hyperframes-gradle produces The teaser video and technical demos.

The 6 EPICs of the Roadmap

The plugin is structured in 6 EPICs:

EPIC

Description

Priority

HF-0

Bootstrap governance + architecture framing

�✅ FINISHED

HF-1

Gradle stub plugin + AsciidoctorJ integration

P0

HF-2

Bridge CLI HyperFrames (ProcessBuilder → npx → MP4)

P1

HF-3

DSL AsciiDoc customs (blocks, docinfo, templates)

P1

HF-4

Integration runner-gradle (metadata.json)

P2

HF-5

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

P3

HF-6

CI + publishing Maven Central / Gradle Portal

P3

Session 000 (bootstrap) is already finished. Session 001 will target HF-1: the stub of the Gradle plugin and the 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 of JSX, no learning curve for the agent.

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

slider-gradle, codex-gradle, training-gradle — all consume`.adoc`. Add custom blocks`[hyperframes-composition]`is an extension logical, not a rupture

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

This is what I already do for PlantUML, Graphviz, Piper. Add HyperFrames It changes nothing about the architecture — it’s just another external tool, driven by Gradle.

The real strength of the pattern is that there is no npm dependency in the Gradle build. The plugin does not import any Node.js code. It executes a shell command. If HyperFrames evolves or breaks, the plugin is not coupled — it’s enough to update the command.

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

  • The interactive slides (slider-gradle)

  • The review capsules (capsule-gradle)

  • standalone videos (hyperframes-gradle)

Three formats. One single source file: AsciiDoc.

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

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

References

Related articles