PTENES
MODULE 2.5

✅ Validate & render

The final pipeline step: lint with no errors, inspect with no overflow, render a draft to check frame by frame, validate the narration, render high quality, and generate both formats—all in the right order.

6
Topics
~35
Minutes
Practical
Level
Delivery
Type
Validation & Render Pipeline LINT → INSPECT → DRAFT → HIGH → MP4 × 2 lint 0 errors overlapping inspect 0 overflow --samples 16 draft 1 frame/scene ffmpeg -nostdin 👁 high --fps 30 ~3–4 min 16:9 MP4 1920 × 1080 YouTube 9:16 MP4 1080×1920 Shorts ① LINT ② INSPECT ③ DRAFT ④ HIGH
1

🧹 npx hyperframes lint

The first quality gate: the linter analyzes your index.html generated and reports structural issues before you spend time rendering. Goal: 0 errors.

Main Concept

The linter reads the index.html generated (not the build-index.mjs) and validates the composition structure. Errors stop the render—warnings are informational.

Always run after node build-index.mjs e before for any render. Lint is fast (<2 s) and doesn't launch Chrome.

Command
# generate index.html before running lint
node build-index.mjs
npx hyperframes lint

# expected output (0 errors)
✔ 0 errors, 0 warnings
The 3 most common errors
✗
overlapping_clips

Two scene clips have overlapping time intervals. Fix this by adjusting the array AUDIO[] no build-index.mjs — the sum of the durations must be strictly sequential.

✗
multiple_root_compositions

More than one element with data-composition at the document root. HyperFrames accepts only one composition per file index.html.

✗
google_fonts_import

The lint detects a @import url('fonts.googleapis.com/...') in the CSS. External fonts are prohibited because headless Chrome has no internet access during rendering. Use node fetch-fonts.mjs to download the .woff2 and serve locally via assets/fonts/fonts.css.

💡
Lint in the development loop

During composition, run node build-index.mjs && npx hyperframes lint with each relevant modification to the build-index.mjs. Cost: less than 2 seconds. Prevents surprises at render time.

Key concepts
🧹
0 errors
Required goal
⏱️
<2 s
No Chrome
🔤
Local fonts
fetch-fonts.mjs
📋
AUDIO[]
Sequential
2

🔍 npx hyperframes inspect --samples 16

The inspector launches headless Chrome, captures 16 frames distributed throughout the video, and audits each one: layout, text overflow, elements outside the canvas. Goal: 0 issues.

Command
# 16 samples cover videos up to ~110s well
npx hyperframes inspect --samples 16

# expected output
✔ Inspected 16 frames — 0 layout issues found
✓ Best practices for 0 issues
  • ✓ Add data-layout-ignore in off-canvas decorative elements (giant background words, glows, .bg-layer)
  • ✓ Prefer left/right instead of width for highlight markers
  • ✓ Test long code in overflow-x: auto with an explicitly sized container
  • ✓ Use z-index:-1 in the glows so they don't overflow the bounding box
✗ Common causes of overflow
  • ✗ Code-line text overflowing the container — shorten it or break it into lines
  • ✗ SVG without viewBox not defined — the auditor can't calculate bounds
  • ✗ position: absolute without overflow: hidden in the parent
  • ✗ Decorative elements without data-layout-ignore — marked as false-positive overflow
⚠️
Inspect does not replace visual review

The inspector detects bounding-box overflow, not readability or contrast issues. After 0 problemas in inspect, you still need to review frames from the draft render visually — especially in scenes with text over a gradient.

Key concepts
🔍
16 samples
Good coverage
🏷️
data-layout-ignore
Decorative elements
📐
Bounding box
Actual overflow
🧪
Headless Chrome
Actual render
3

🎬 Draft render — iterate quickly

The mode --quality draft generates a low-resolution MP4 in seconds. Extract one frame per scene with ffmpeg -nostdin and check it visually before spending time on the final render.

Frame-by-frame review workflow
1
Generate the MP4 draft
node build-index.mjs # generates index.html
npx hyperframes render --quality draft --output renders/draft.mp4
2
Extract 1 frame per scene with ffmpeg

Use the scene start time as <t>. For a scene that starts at 12.5 s, use -ss 12.5. The flag -nostdin is required on Windows/git-bash to prevent ffmpeg from consuming stdin and exiting without generating the file.

# frame from the scene that starts at t=12.5s
ffmpeg -nostdin -y -ss 12.5 -i renders/draft.mp4 \
-vframes 1 -update 1 frame-cena3.png
3
Open the PNG with the Read tool

Claude can view PNG images directly. Extract one frame per scene and check text alignment, overflow, animations in the right position, and readability against the background. Repeat for all scenes.

4
Fix it and try again

Edit the build-index.mjs, run node build-index.mjs && npx hyperframes render --quality draft again. The draft loop is cheap—iterate freely before the final render.

📊 Draft vs High — when to use each
Aspect Draft High
Objective Visual review Final delivery
Speed Fast (<30 s) 3–4 min / 110s of video
Quality Reduced Maximum (30 fps)
Usage Iteration loop Once, at the end
💡
Recommended minimum coverage

1 frame per scene works well. For an 8-scene video, that’s 8 ffmpeg calls. Focus on transitions and titles—they’re the parts most likely to overflow.

Key concepts
🎬
--quality draft
Fast iteration
📸
-vframes 1
Single frame
🛡️
-nostdin
Win/git-bash
🔁
-update 1
Overwrites PNG
4

👂 Validate the narration with the user

Claude can't hear audio. The user is responsible for validating the narration — and should do so before from the high render to avoid wasting 3–4 minutes of processing.

⚠️
Claude can't listen to WAV files

The image analysis tool (Read) works for PNGs and frames, but WAV audio isn’t supported. All narration validation must be done by the user listening to the files assets/audio/sN.wav directly.

What to validate in the voice-over

Before rendering at high quality, confirm with the user: correct pronunciation of technical terms, speed (pf_dora --speed 0.98 is the default), natural pauses between scenes, and a duration that fits each scene’s visuals.

✓ Audio validation checklist
  • ✓ Listen to each assets/audio/sN.wav before the final render
  • ✓ Confirm that acronyms were expanded correctly (e.g., "GSAP" → "jee-sap" sounds natural?)
  • ✓ Make sure the WAV duration is consistent with the visual scene
  • ✓ Check via ffprobe whether the measured duration matches the AUDIO[]
✗ Don't skip audio validation
  • ✗ Don't run a high-quality render without listening to the WAVs—costly rework
  • ✗ Don't rely only on the measured duration—the speech may be cut off
  • ✗ Don't use a speed above 1.05—the voice sounds metallic and artificial
  • ✗ Don't ignore incorrect pronunciations of English terms in the PT-BR text
Confirm duration with ffprobe
# measure the exact duration of each WAV
ffprobe -v error -show_entries format=duration \
-of default=noprint_wrappers=1:nokey=1 \
assets/audio/s1.wav

# result: e.g. 14.2327
14.2327

# use this value in AUDIO[0] in build-index.mjs
const AUDIO = [14.23, /* s2 */ 11.80, ...]
💡
PT-BR voices available in Kokoro

Beyond pf_dora (female, recommended default with --speed 0.98), there are pm_alex e pm_santa for male voice variations. If a term is pronounced incorrectly, rewrite it phonetically in the assets/txt/sN.txt and regenerate the WAV.

Key concepts
👂
Human review
Claude doesn't listen
🎙️
pf_dora
speed 0.98
⏱️
ffprobe
Measures duration
📝
AUDIO[]
Syncs the scene
5

🚀 Render high — delivery quality

After clean lint, inspect with no overflow, a reviewed draft, and approved narration, it’s time for the final render: --quality high --fps 30. An ~110s video is about ~3,500 frames and takes 3–4 minutes on a typical machine with 22 cores.

What happens in the high render

HyperFrames launches headless Chrome at full resolution (1920×1080 or 1080×1920), captures each frame as a PNG, passes them all to FFmpeg, and encodes H.264 with the WAV audio embedded. At 30 fps, 110 seconds = 3,300 frames.

Render time varies with the number of available colors. With 22 colors, expect ~3–4 minutes for a 110s video.

Command (16:9 high-quality render)
# final 16:9 render (1920×1080)
node build-index.mjs &&
npx hyperframes render \
--quality high \
--fps 30 \
--output renders/meu-video-16x9.mp4

# output during rendering
⠸ Rendering frame 1547/3300 (46.9%)...
✔ Render complete → renders/meu-video-16x9.mp4
📊 Estimated render time (22 colors)
Short video (~60s)
~1,800 frames
≈ 1.5–2 min rendering
Standard video (~110s)
~3,300 frames
≈ 3–4 min rendering
Long video (~180s)
~5,400 frames
≈ 5–7 min rendering
✓ Before starting the high render
  • ✓ npx hyperframes lint — 0 confirmed errors
  • ✓ npx hyperframes inspect --samples 16 — 0 layout issues
  • ✓ Draft visually checked (1 frame/scene)
  • ✓ Voice-over approved by the user
✗ Don't use high-quality rendering if...
  • ✗ Lint still reports errors — it will fail midway through rendering
  • ✗ Didn't see any frames from the draft—risk of rework
  • ✗ The user hasn't listened to the WAVs — they may need to render again
  • ✗ O index.html was not regenerated after the last edit
Key concepts
🚀
--quality high
Full resolution
🎞️
30 fps
Default delivery
⏳
~3,500 frames
110s video
🏁
H.264 MP4
Ready to upload
6

📐 Generate both formats — 16:9 and 9:16

From the same project, generate the 16:9 (YouTube) and 9:16 (Shorts/Reels) versions in sequence. The critical rule: render immediately after generating each format — never leave two index.html simultaneous at the root.

Single index.html rule

O build-index.mjs always overwrites the same one index.html. If you run both modes before rendering, the second overwrites the first and you lose the composition. The correct sequence is: generate → render → generate another version → render.

Complete sequence (correct order)
# ── STEP 1: 16:9 format ──────────────────────────
node build-index.mjs # no flag → 1920×1080
npx hyperframes render \
--quality high --fps 30 \
--output renders/meu-video-16x9.mp4

# ── STEP 2: 9:16 format ──────────────────────────
node build-index.mjs --vertical # → 1080×1920
npx hyperframes render \
--quality high --fps 30 \
--output renders/meu-video-9x16.mp4

# result: 2 files in renders/
renders/meu-video-16x9.mp4 # YouTube
renders/meu-video-9x16.mp4 # Shorts / Reels
📊 Formats and destinations
16:9 — Horizontal
Resolution: 1920 × 1080 px
Destination: YouTube, Vimeo, LinkedIn
Flag: node build-index.mjs (without a flag)
Output: renders/nome-16x9.mp4
9:16 — Vertical
Resolution: 1080 × 1920 px
Destination: YouTube Shorts, Instagram Reels, TikTok
Flag: node build-index.mjs --vertical
Output: renders/nome-9x16.mp4
💡
Complete pipeline in one script

To automate both formats at once, chain with &&:

node build-index.mjs && npx hyperframes render --quality high --output renders/v-16x9.mp4 &&
node build-index.mjs --vertical && npx hyperframes render --quality high --output renders/v-9x16.mp4

O && ensures the 9:16 render starts only after the 16:9 render finishes successfully.

✓ Correct sequence
  • ✓ node build-index.mjs → render 16:9 → node build-index.mjs --vertical → render 9:16
  • ✓ Always confirm which one index.html is active before rendering
  • ✓ Name the outputs with the suffix -16x9 e -9x16 from the start
✗ Errors that cost render time
  • ✗ Run build-index.mjs e build-index.mjs --vertical without rendering between the two
  • ✗ Render without --output explicit — may overwrite a previous render
  • ✗ Use the same output name for both formats
Key concepts
📺
1920×1080
YouTube
📱
1080×1920
Shorts/Reels
🚩
--vertical
Single flag
⚡
Generate → render
Required order

📋 Module 2.5 Summary

What you learned
  • ✓ npx hyperframes lint: the 3 fatal errors and how to fix each one
  • ✓ npx hyperframes inspect --samples 16: overflow, data-layout-ignore and false positives
  • ✓ Draft render + frame extraction with ffmpeg -nostdin -y -ss <t> -vframes 1 -update 1
  • ✓ Claude can't hear audio — the user is responsible for validating the narration
  • ✓ High-quality render: --quality high --fps 30 · ~110s ≈ 3,500 frames ≈ 3–4 min
  • ✓ 16:9 → 9:16 sequence: generate and render each format before generating the next
Next track
T3
🔧 Inside HyperFrames
You’ve finished Track 2 — Pipeline. The next track dives into the framework’s internals: how headless Chrome controls time, how FFmpeg encodes, and how GSAP integrates with frame-by-frame rendering.
Start Track 3: Under the hood →