Introduction
Framely is a programmatic video creation framework built on React. It allows you to create videos using familiar React patterns — components, hooks, and props.
Why Framely?
- Code-First: Define videos as code, not timelines
- React-Based: Use components you already know
- Deterministic: Every render produces identical output
- Type-Safe: Full TypeScript support
How It Works
In Framely, a video is a React component that receives the current frame number. You use this frame number to animate your content.
jsx
function MyVideo() {
const frame = useCurrentFrame();
const opacity = frame / 30; // Fade in over 1 second at 30fps
return (
<div style={{ opacity }}>
Hello, World!
</div>
);
}The renderer captures each frame as an image and stitches them together into a video using FFmpeg.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Studio UI (React + Vite) │
│ │
│ ┌─────────┐ ┌──────────────────────┐ ┌──────────────┐ │
│ │ Comps │ │ Player viewport │ │ Props editor │ │
│ │ sidebar │ │ (live preview) │ │ (edit props) │ │
│ │ │ ├──────────────────────┤ │ │ │
│ │ │ │ Timeline │ │ │ │
│ │ │ │ (sequences, seek) │ │ │ │
│ └─────────┘ └──────────────────────┘ └──────────────┘ │
│ │
│ [Render] → POST /api/render → CLI render pipeline │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ CLI (@framely/cli) │
│ │
│ Playwright (headless Chromium) │
│ │ screenshots each frame as JPEG │
│ ▼ │
│ FFmpeg (JPEG → H.264/H.265/VP9/ProRes/GIF) │
│ │ │
│ ▼ │
│ output.mp4 │
└─────────────────────────────────────────────────────────────┘How Rendering Works
- The Vite dev server serves React compositions
- The CLI opens headless Chromium (Playwright) and navigates to
?renderMode=true - In render mode, the app shows the bare composition at native resolution
- The CLI calls
window.__setFrame(n)for each frame and waits for React to re-render - It takes a JPEG screenshot of the composition container
- Screenshots are piped directly to FFmpeg's stdin (no temp files)
- FFmpeg encodes the stream to the selected codec