HyperFrames-Gradle : Transforming AsciiDoc into MP4 Video
Published on 31 May 2026
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:
-
Revision capsule (capsule-gradle) — a reveal.js deck captured in WebM with
TTS narration. Perfect for reviewing a module.
-
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
-
AsciidoctorJ parses the source AsciiDoc 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 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:
-
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.
-
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.
-
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. |
Conclusion: The Missing Link
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
-
Article 0124 — The Independent Plugin + Consumer Root Architecture
-
foundry/public/hyperframes-gradle/hyperframes-plugin/doc/HYPERFRAMES_ARCHITECTURE.adoc— Detailed Architecture