VideoGraphics — a Remotion skill for agentic AI video editors. Use when Brad or any user wants to add motion graphics overlays to their videos. Loads the VideoGraphics framework to generate, preview, review, iterate, and render Remotion compositions via a structured conversation. Works with any LLM agent (Hermes, Claude, GPT-Codex, Minimax, Kimi, GLM). Requires Remotion, Node.js, and ffmpeg on the user's machine. NOT for simple video cuts, audio mixing, or color grading.
A Remotion skill for AI agents. The agent reads this skill, then has a structured conversation with the user to generate motion graphics overlays — lower thirds, title cards, spec badges, product reveals — render them as transparent WebMs, and deliver them for compositing in Filmora (or equivalent).
Not a standalone tool. Requires an agent to run it. The agent handles all Remotion code generation and render pipeline management — the user just reviews, requests changes, and approves.
USER BRIEF
↓
ASK FOR PROJECT FOLDER / ASSETS
↓
INFER OR ASK STYLE DIRECTION
↓
GENERATE COMPOSITION CODE
↓
STILL FRAME PREVIEW ← user reviews layout/colors/spacing
↓
USER REQUESTS CHANGES (or confirms)
↓
ITERATE → PREVIEW AGAIN
↓
USER APPROVES
↓
RENDER FINAL (alpha WebM or MP4)
↓
RECORD APPROVAL → UPDATE PREFERENCE PROFILE
~/.hermes/skills/video-graphics/
├── framework/
│ ├── setup.js # Create a new project
│ ├── render.js # Preview + render pipeline
│ ├── preference-engine.js # Learn from approvals
│ ├── style-inferencer.js # Infer style from natural language
│ └── composition-library.js # Built-in composition types
├── projects/ # One folder per project
└── global-assets/ # Reusable elements (subscribe, Q&A, etc.)
Ask the user: "Which project is this for?"
If new project:
node framework/setup.js <project-name> <video-type>
product | related-topic | comparisonIf existing project:
node framework/preference-engine.js context <project-name>Ask the user what assets they have in the project folder:
Tell them: "Drop whatever you have into the project folder — I'll work with what's there."
If the user has a global asset concept (subscribe overlay, Q&A ask), point them to the global-assets folder.
Ask the user: "What style are you going for?"
Accept any natural-language description. Examples:
Run the style inferencer:
node framework/style-inferencer.js expand "<user description>" <video-type>
This gives you structured parameters to pass to the composition generator.
Choose the right composition type from the library:
| Video Type | Common Compositions |
|---|---|
| Product | TitleCard, LowerThird, SpecBadgeRow, CountUp, ProductCarousel, PriceCard |
| Related Topic | TitleCard, LowerThird, GradientOverlay, AccentBar |
| Comparison | ComparisonSplit, SpecBadgeRow, TitleCard, PriceCard |
Generate the Remotion React code. The agent writes the .tsx file in the project's compositions/ folder.
Critical rules for generated code:
Config.setVideoImageFormat("png") in remotion.config.ts before renderingBefore any video render, always generate a still frame:
node framework/render.js preview <CompositionId> --project=<slug> --frame=120
Upload/send the preview PNG to the user. Review checklist:
Ask: "What do you think? Any changes?"
Collect feedback. Common change types:
If changes requested → iterate back to Step 4/5.
When the user says it's good:
# Record the approval (this updates the preference profile)
node framework/preference-engine.js approve <project> <CompositionId> <notes>
Notes can include what was approved — color choices, animation speed, font scale.
Then render the final:
# For Filmora overlay (transparent WebM)
node framework/render.js render <CompositionId> --project=<slug> --format=webm
# For standalone / non-overlay (opaque MP4)
node framework/render.js render <CompositionId> --project=<slug> --format=mp4
Verify alpha:
node framework/render.js verify <output-file.webm>
Output goes to projects/<slug>/output/.
Tell the user how to use the output in Filmora:
Transparent overlay, bottom 36% of frame. Slide-in gradient + staggered content. Exit: content leaves first, gradient follows.
Full-screen title screen. Per-character spring reveal. Transparent or opaque.
Horizontal row of spec badges. Spring stagger entry. Count-up for numeric values.
Number counts from 0 to target. No floating-point jitter (uses interpolate trick).
Horizontal swipe of product images. Spring physics swipe.
Price display with badge, strike-through original, highlight current.
Side-by-side A vs B comparison. Split slide-in, winner badge.
Colored gradient overlay. Fade in → hold → fade out.
Single accent bar with optional glow. Horizontal or vertical.
Fixed subscribe CTA. Lives in global-assets/.
node style-inferencer.js expand "<description>" <video-type> returns:
{
"videoType": "product",
"userRawInput": "poco yellow and black, fast and energetic",
"style": {
"primaryColor": "#000000",
"accentColor": "#FFD500",
"textColor": "#FFFFFF",
"allColors": ["#000000", "#FFD500", "#FFFFFF"],
"animation": { "damping": 12, "stiffness": 350, "mass": 0.8 },
"duration": 8,
"fontScale": 1.75,
"gradientDirection": "to top",
"accentBar": { "position": "left", "width": 4 },
"glow": false
},
"animationGuidelines": {
"damping": 12,
"stiffness": 350,
"mass": 0.8,
"entryStart": 0,
"contentEnter": 20,
"exitStart": 210,
"exitComplete": 240
}
}
For all lower-third / overlay compositions:
ENTRY: gradient enters first (spring, 0 → complete)
content enters after (spring, +20 frames delay)
EXIT: content exits first (spring, -30 frames from end)
gradient exits last (spring, ends at last frame)
Never: gradient and content appearing/leaving simultaneously.
Every approval records:
When starting a new composition for the same project, always call:
node framework/preference-engine.js context <project-name>
And apply those preferences to the new composition.
Reusable elements that live in skills/video-graphics/global-assets/:
| Asset | Use |
|---|---|
| SubscribeAsk | End-card subscribe CTA |
| QandAAsk | Mid/video Q&A prompt overlay |
| TimestampMarker | Chapter / timestamp cards |
| BrandLowerThird | Brand logo lockup lower third |
These are pre-built. The user drops them into their project or references them in Filmora directly.
The user's machine needs:
ffmpeg -version to check)$REMOTION_ROOT env var pointing to your Remotion projectIf any are missing, guide the user to install them before starting.
When mapping natural-language keywords to structured parameters, always use word-boundary regex to avoid false matches:
// ❌ WRONG — matches unintended words
const hasAccentBar = text.includes("bar"); // matches "black", "sub", "mobile"
// ✅ CORRECT — matches only standalone word
const accentBarRegex = /\b(accent|bar|stripe|line|divider)\b/i;
const hasAccentBar = accentBarRegex.test(text);
Keywords that produce only colors are simple arrays. Keywords that carry semantic meaning (brand, energy, animation style) should be extended palette objects:
const COLOR_INFERENCE = {
// Simple array — just colors
premium: ["#1A1A2E", "#16213E", "#0F3460"],
gaming: ["#7B2FFF", "#00E5FF", "#FF3D00", "#000000"],
// Extended palette — colors + animation + accent flags
poco: {
colors: ["#FFD500", "#000000", "#FFFFFF"],
accentBar: true, // always has accent bar
animation: "fast", // pre-set animation profile
},
racing: {
colors: ["#FFD500", "#000000", "#FF3D00"],
accentBar: true,
animation: "fast",
},
};
When processing keywords:
colors, accentBar, animation flagslet keywordAccentBar = false;
let keywordAnimation = null;
for (const [keyword, palette] of Object.entries(COLOR_INFERENCE)) {
if (text.includes(keyword)) {
if (typeof palette === "object" && !Array.isArray(palette)) {
// Extended palette (brand keyword)
colors.push(...palette.colors);
if (palette.accentBar) keywordAccentBar = true;
if (palette.animation) keywordAnimation = palette.animation;
} else {
// Simple palette
colors.push(...palette);
}
}
}
Then later in the function:
const accentBarRegex = /\b(accent|bar|stripe|line|divider)\b/i;
const hasAccentBar = keywordAccentBar || accentBarRegex.test(text);
VP9 alpha files ALWAYS show pix_fmt=yuv420p in ffprobe. This is normal.
Real alpha indicator: TAG:alpha_mode=1
ffprobe -v error -select_streams v:0 \
-show_entries stream=codec_name,pix_fmt:stream_tags=alpha_mode \
-of default=noprint_wrappers=1:nokey=0 out/file.webm
Always verify after render. If standalone player shows black over transparent areas — this is normal. Trust Filmora timeline compositing + alpha_mode=1.
TAG:alpha_mode=1, not pix_fmt--codec=vp9 --pixel-format=yuva420p --image-format=png