Marketing Studio Agent
Skill by ara.so — Marketing Skills collection.
Marketing Studio is an agent-driven video rendering engine for Claude Code. Run /marketing from your product repo to generate a complete launch asset suite: 3D logo reveal, screen-captured product demo, hero launch video with voiceover/music, per-platform social clips, and OG assets. All rendering happens in a central engine repo using Remotion, Blender, Playwright, and ElevenLabs.
Installation
# Clone the engine repo
git clone https://github.com/ucsandman/marketing-studio.git
cd marketing-studio
# Install dependencies
cd studio && npm install && cd ..
# Configure (optional: Blender path, ElevenLabs API key)
cp .env.example .env
# Install skills into Claude Code
node scripts/install-skills.mjs
# Verify toolchain
python launch.py --check
The install script copies skills to ~/.claude/skills and links them to your cloned engine path.
Core Workflow
The /marketing Command
From your product repo:
claude
> /marketing
This runs the full pipeline:
- Brand onboarding – Derives brand tokens from your design system (or prompts for them)
- Logo reveal – 3D Blender animation composited in Remotion
- Product demo – Playwright captures your running app with camera zooms
- Launch video – 30-90s hero video combining demo, logo, copy
- Audio track – ElevenLabs voiceover and music scored to video
- Social clips – Platform-specific cuts (X, LinkedIn, TikTok)
- OG assets – Static image, animated loop, README GIF
Individual Skills
Run any asset type standalone:
> /logo-reveal
> /product-demo
> /launch-video
> /audio-track
> /social-clip
> /og-assets
Brand Configuration
Brands are defined in brands/<id>.json:
{
"id": "myproduct",
"name": "MyProduct",
"tagline": "Ship faster, break less",
"colors": {
"primary": "#3b82f6",
"secondary": "#8b5cf6",
"accent": "#f59e0b",
"background": "#000000",
"foreground": "#ffffff",
"muted": "#6b7280",
"success": "#10b981",
"warning": "#f59e0b",
"error": "#ef4444",
"text": "#f9fafb",
"textMuted": "#9ca3af",
"border": "#374151",
"surface": "#111827"
},
"fonts": {
"heading": "Inter",
"body": "Inter",
"mono": "JetBrains Mono"
},
"voice": {
"tone": "confident, technical, no fluff",
"avoid": ["AI slop", "game-changer", "revolutionary"]
}
}
Register the brand in studio/src/lib/brand.ts:
import { z } from 'zod';
export const BrandSchema = z.object({
id: z.string(),
name: z.string(),
tagline: z.string(),
colors: z.object({
primary: z.string(),
secondary: z.string(),
accent: z.string(),
// ... all 13 colors
}),
fonts: z.object({
heading: z.string(),
body: z.string(),
mono: z.string(),
}),
voice: z.object({
tone: z.string(),
avoid: z.array(z.string()),
}),
});
export type Brand = z.infer<typeof BrandSchema>;
export function getBrand(brandId: string): Brand {
const brandData = require(`../../brands/${brandId}.json`);
return BrandSchema.parse(brandData);
}
Add a logo mark component in studio/src/brands/<id>Mark.tsx:
import React from 'react';
export const MyProductMark: React.FC<{
size?: number;
color?: string;
}> = ({ size = 100, color = '#3b82f6' }) => {
return (
<svg width={size} height={size} viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill={color} />
</svg>
);
};
Manual Rendering
Remotion Commands
Render any composition directly:
cd studio
# Logo reveal
npx remotion render LogoReveal ../out/myproduct/logo.mp4 \
--props='{"brandId":"myproduct","cta":"Watch the launch"}'
# Product demo
npx remotion render ProductDemo ../out/myproduct/demo.mp4 \
--props='{"brandId":"myproduct","captureVideo":"../assets/captures/demo.mp4"}'
# Launch video
npx remotion render LaunchVideo ../out/myproduct/launch.mp4 \
--props='{"brandId":"myproduct","demoVideo":"../out/myproduct/demo.mp4","script":["Hook","Problem","Solution"]}'
# Social clip
npx remotion render SocialClip ../out/myproduct/social-x.mp4 \
--props='{"brandId":"myproduct","platform":"x","feature":"Real-time sync"}'
# Animated OG
npx remotion render AnimatedOG ../out/myproduct/og.mp4 \
--props='{"brandId":"myproduct"}'
Feeder Scripts
Capture Product Demo
cd feeders/capture
node capture.mjs \
--url http://localhost:3000 \
--output ../../assets/captures/demo.mp4 \
--brand myproduct \
--script zoom-dashboard,click-feature,pan-results
Render Blender Logo
cd feeders/blender
python render_logo.py \
--brand myproduct \
--output ../../assets/blender/logo-frames/ \
--style draw-on
Generate Audio
cd feeders/audio
node generate.mjs \
--script "Ship your product in days, not months" \
--voice-id "ELEVENLABS_VOICE_ID" \
--output ../../assets/audio/voiceover.mp3
node generate.mjs \
--type music \
--mood energetic \
--duration 60 \
--output ../../assets/audio/music.mp3
Set ELEVENLABS_API_KEY in .env.
Props Builders
Generate render props via builder scripts:
# Build launch video props
node scripts/build-launch-props.mjs \
--brand myproduct \
--demo-video ./assets/captures/demo.mp4 \
--script "Hook line" "Problem statement" "Solution demo" \
--output ./props/launch-myproduct.json
# Build social clip props
node scripts/build-social-props.mjs \
--brand myproduct \
--platform x \
--feature "Real-time collaboration" \
--output ./props/social-x-myproduct.json
Props files are JSON:
{
"brandId": "myproduct",
"demoVideo": "./assets/captures/demo.mp4",
"logoRevealVideo": "./out/myproduct/logo.mp4",
"script": [
{"type": "hook", "text": "Ship in days, not months", "duration": 3},
{"type": "problem", "text": "Marketing assets take weeks", "duration": 4},
{"type": "solution", "text": "One command, full suite", "duration": 5}
],
"voiceover": "./assets/audio/voiceover.mp3",
"music": "./assets/audio/music.mp3"
}
Environment Variables
# .env
BLENDER_PATH=/Applications/Blender.app/Contents/MacOS/Blender
ELEVENLABS_API_KEY=your_api_key_here
COMFYUI_URL=http://localhost:8188
All are optional; feeders degrade gracefully without them.
Verification & Testing
# Full toolchain health check
python launch.py --check
# Render frame 0 of every composition (smoke test)
node scripts/smoke.mjs
# Brand schema validation
cd studio && npm test
# Launch Remotion Studio for interactive preview
python launch.py
Key Patterns
Resume on Failure
The pipeline writes a run manifest to .marketing-run.json:
{
"brand": "myproduct",
"timestamp": "2026-07-10T12:00:00Z",
"completed": ["logo-reveal", "product-demo"],
"current": "launch-video",
"props": {
"logoRevealVideo": "./out/myproduct/logo.mp4",
"demoVideo": "./out/myproduct/demo.mp4"
}
}
Re-running /marketing resumes from the last incomplete step.
Camera Zoom Math
Product demo captures use measured easing for professional zooms:
// studio/src/lib/camera.ts
export function calculateZoom(
frame: number,
duration: number,
startScale: number = 1,
endScale: number = 1.2
): number {
const progress = frame / duration;
const eased = 1 - Math.pow(1 - progress, 3); // cubic ease-out
return startScale + (endScale - startScale) * eased;
}
Seamless Loop Rules
Animated OG loops must start and end on identical frames:
// studio/src/compositions/AnimatedOG.tsx
export const AnimatedOG: React.FC<{brandId: string}> = ({brandId}) => {
const frame = useCurrentFrame();
const {durationInFrames} = useVideoConfig();
const brand = getBrand(brandId);
// Normalize to [0, 1) for seamless loop
const progress = (frame % durationInFrames) / durationInFrames;
const rotation = progress * 360;
return (
<AbsoluteFill style={{backgroundColor: brand.colors.background}}>
<div style={{transform: `rotate(${rotation}deg)`}}>
{/* logo mark */}
</div>
</AbsoluteFill>
);
};
Platform-Specific Social Clips
Aspect ratios and safe zones per platform:
// studio/src/compositions/SocialClip.tsx
const PLATFORM_SPECS = {
x: { width: 1280, height: 720, safeZone: 0.1 },
linkedin: { width: 1280, height: 720, safeZone: 0.1 },
tiktok: { width: 1080, height: 1920, safeZone: 0.15 },
instagram: { width: 1080, height: 1920, safeZone: 0.12 },
};
export const SocialClip: React.FC<{
brandId: string;
platform: keyof typeof PLATFORM_SPECS;
feature: string;
}> = ({brandId, platform, feature}) => {
const spec = PLATFORM_SPECS[platform];
const safeWidth = spec.width * (1 - 2 * spec.safeZone);
return (
<AbsoluteFill style={{width: spec.width, height: spec.height}}>
{/* content */}
</AbsoluteFill>
);
};
Troubleshooting
Blender Not Found
Error: Blender executable not found at /Applications/Blender.app
Set BLENDER_PATH in .env or skip 3D logo reveals (pipeline falls back to 2D).
ElevenLabs Rate Limit
Error: 429 Too Many Requests
Audio generation is optional. Pipeline renders video without voiceover if API fails.
Playwright Capture Timeout
Error: Page did not load within 30s
Ensure your dev server is running on the correct port. Capture script waits for networkidle state.
Remotion Memory Error
Error: JavaScript heap out of memory
Set Node memory limit:
export NODE_OPTIONS="--max-old-space-size=8192"
npx remotion render ...
Brand Schema Validation Failed
Error: Invalid brand JSON
Run cd studio && npm test to see schema violations. All 13 colors, 3 fonts, and voice rules are required.
File Structure
marketing-studio/
├── brands/ # per-product brand tokens (JSON)
├── studio/ # Remotion project (all compositions)
│ ├── src/
│ │ ├── compositions/
│ │ │ ├── LogoReveal.tsx
│ │ │ ├── ProductDemo.tsx
│ │ │ ├── LaunchVideo.tsx
│ │ │ ├── SocialClip.tsx
│ │ │ └── AnimatedOG.tsx
│ │ ├── brands/ # logo mark components
│ │ └── lib/
│ │ ├── brand.ts
│ │ └── camera.ts
├── feeders/
│ ├── blender/ # headless Blender scenes
│ ├── capture/ # Playwright recorder
│ ├── audio/ # ElevenLabs client
│ └── comfy/ # ComfyUI client (optional)
├── skills/ # Claude Code skills
│ ├── marketing.md
│ ├── logo-reveal.md
│ ├── product-demo.md
│ └── ...
├── scripts/
│ ├── install-skills.mjs
│ ├── build-launch-props.mjs
│ ├── build-social-props.mjs
│ └── smoke.mjs
├── props/ # generated render props (JSON)
├── assets/ # captures, audio, blender frames
├── out/ # final rendered videos
└── examples/ # real output from shipped products
Best Practices
- Run smoke test after brand changes:
node scripts/smoke.mjscatches schema errors before full renders - Use props builders: Never hand-edit props JSON; use
scripts/build-*-props.mjs - Film demo last in UI work: Run
/polishand/frontend-verifybefore/product-demo - Score audio after picture lock: Don't generate voiceover until launch video script is final
- Test locally before
/marketing: Run individual skills (/logo-reveal,/product-demo) to debug brand tokens - Keep run manifest: Don't delete
.marketing-run.jsonduring active runs (enables resume)
Related Skills
/polish– Final UI quality pass before demo filming/frontend-verify– Headless route verification (console errors, failed requests)/de-vibe– Remove AI-generated fingerprints before shipping/launch– Generate announcement drafts per channel
All supporting skills install with scripts/install-skills.mjs.