HyperFrames-Gradle: Transform AsciiDoc into MP4 Video
Published on 31 May 2026
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:
-
Revision capsule (capsule-gradle) — a reveal.js deck captured as WebM with
narration TTS. Perfect for reviewing a module.
-
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
-
AsciidoctorJ parses the AsciiDoc source and extracts the custom blocks
[hyperframes-composition], [hyperframes-track], [hyperframes-animation].
-
GenerateHyperframesHtmlTask generates the HyperFrames HTML with the
`data-*`corresponding attributes.
-
RenderHyperframesTask calls the HyperFrames CLI via`ProcessBuilder`:
npx hyperframes render --input index.html --output video.mp4.
-
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 :
-
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.
-
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
-
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. |
Conclusion: The Missing Link
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
-
Article 0124 — Independent Plugin Architecture + Consumer Root
-
foundry/public/hyperframes-gradle/hyperframes-plugin/doc/HYPERFRAMES_ARCHITECTURE.adoc— Detailed Architecture