Programmatic video generation for personalized outreach using Remotion.
- Node.js 18+
- npm or yarn
cd onhyper-videos
npm installStart the Remotion Studio to preview compositions:
npm run devThis opens a browser interface where you can:
- Preview all video compositions
- Scrub through frames
- Export individual frames
- Adjust props in real-time
Render with default props:
npx remotion render PersonalOutreach out/video.mp4Pass custom props via CLI:
npx remotion render PersonalOutreach out/custom-video.mp4 \
--props '{"prospectName":"John","companyName":"TechCorp","featureHighlight":"anthropic"}'Or create a props file:
# Create props.json
echo '{
"prospectName": "John",
"companyName": "TechCorp",
"featureHighlight": "anthropic"
}' > props.json
# Render with props file
npx remotion render PersonalOutreach out/custom-video.mp4 --props-file props.json| Flag | Description |
|---|---|
--codec |
Video codec (h264, h265, vp8, vp9) - default: h264 |
--fps |
Frames per second - default: 30 |
--frames |
Frame range (e.g., 0-89 for first 3 seconds) |
--height |
Video height - default: 720 |
--width |
Video width - default: 1280 |
--quality |
JPEG quality (1-100) |
The PersonalOutreach composition is structured as follows:
| Scene | Duration | Content |
|---|---|---|
| Title | 0:00-0:03 | Animated "Hi {prospectName}" intro |
| Company | 0:03-0:08 | Company name + context hook |
| Feature | 0:08-0:20 | Code demo for selected LLM provider |
| CTA | 0:20-0:25 | "Try OnHyper.io free" call to action |
| Outro | 0:25-0:30 | Brand logo + tagline |
openai- OpenAI Integration (green theme)anthropic- Anthropic Claude (beige theme)openrouter- OpenRouter (purple theme)general- Multi-LLM Platform (blue theme)
onhyper-videos/
├── src/
│ ├── Composition.tsx # Main video composition
│ ├── Root.tsx # Remotion root (registers compositions)
│ ├── types.ts # TypeScript interfaces and constants
│ ├── index.css # Global styles
│ └── components/
│ ├── TitleScene.tsx # Animated intro scene
│ ├── CompanyScene.tsx # Company name scene
│ ├── FeatureShowcase.tsx # Code demo scene
│ ├── CTAScene.tsx # Call to action scene
│ └── OutroScene.tsx # Brand outro scene
├── out/ # Rendered videos (gitignored)
├── remotion.config.ts # Remotion configuration
└── package.json
Create a new file in src/components/:
// src/components/NewScene.tsx
import React from 'react';
import {
AbsoluteFill,
useCurrentFrame,
interpolate,
spring,
useVideoConfig,
} from 'remotion';
interface NewSceneProps {
customText: string;
startFrame: number;
durationInFrames: number;
}
export const NewScene: React.FC<NewSceneProps> = ({
customText,
startFrame,
durationInFrames,
}) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const localFrame = frame - startFrame;
// Animation logic here
const opacity = interpolate(localFrame, [0, 15], [0, 1], {
extrapolateRight: 'clamp',
});
return (
<AbsoluteFill
style={{
justifyContent: 'center',
alignItems: 'center',
opacity,
}}
>
<h1>{customText}</h1>
</AbsoluteFill>
);
};Edit src/Composition.tsx to include the new scene:
import { NewScene } from './components/NewScene';
// Add inside the AbsoluteFill
<Sequence from={START_FRAME} durationInFrames={DURATION}>
<NewScene
customText={props.customText}
startFrame={START_FRAME}
durationInFrames={DURATION}
/>
</Sequence>Edit src/types.ts:
export interface VideoProps {
prospectName: string;
companyName: string;
featureHighlight: 'openai' | 'anthropic' | 'openrouter' | 'general';
customText?: string; // New optional prop
}Spring animations (smooth entrance):
const entrance = spring({
frame: localFrame,
fps,
config: {
damping: 100,
stiffness: 150,
mass: 0.7,
},
});Linear interpolation (fade/slide):
const opacity = interpolate(localFrame, [0, 20], [0, 1], {
extrapolateRight: 'clamp',
});Cyclical animation (pulse/breathe):
const pulse = Math.sin(frame * 0.15) * 0.3 + 0.7;For serverless rendering at scale, deploy to AWS Lambda using Remotion Lambda.
npm install @remotion/lambdaSet up AWS credentials with these permissions:
- S3 access for video storage
- Lambda execution role
- CloudWatch Logs
export AWS_ACCESS_KEY_ID=your-key
export AWS_SECRET_ACCESS_KEY=your-secret
export AWS_REGION=us-east-1// scripts/deploy-lambda.ts
import { deployFunction } from '@remotion/lambda';
const { functionName } = await deployFunction({
region: 'us-east-1',
timeoutInSeconds: 300,
memorySizeInMb: 2048,
createCloudWatchLogGroup: true,
});
console.log('Deployed function:', functionName);# Bundle the project
npx remotion bundle
# Deploy to S3 (creates a serve URL)
npx remotion lambda sites create --site-name onhyper-videosimport { renderMediaOnLambda } from '@remotion/lambda';
const result = await renderMediaOnLambda({
region: 'us-east-1',
functionName: 'your-function-name',
serveUrl: 'your-s3-serve-url',
composition: 'PersonalOutreach',
inputProps: {
prospectName: 'John',
companyName: 'TechCorp',
featureHighlight: 'openai',
},
codec: 'h264',
});
console.log('Video URL:', result.url);| Resolution | Duration | Est. Cost (Lambda + S3) |
|---|---|---|
| 720p | 30s | ~$0.01 |
| 1080p | 30s | ~$0.02 |
| 4K | 30s | ~$0.05 |
Edit src/types.ts to modify feature highlight colors:
export const FEATURE_CONFIGS = {
openai: {
color: '#10a37f', // OpenAI green
// ...
},
// Add new providers
custom: {
title: 'Custom Integration',
code: '// Your code here',
color: '#ff6b6b', // Custom color
logo: 'C',
},
};Edit src/Root.tsx:
<Composition
id="PersonalOutreach"
component={VideoComposition}
width={1920} // HD width
height={1080} // HD height
fps={30}
durationInFrames={TOTAL_DURATION}
/>If Chrome download fails:
remotion browser download- Use
--concurrencyflag:npx remotion render ... --concurrency 8 - Lower resolution for previews
- Disable WebGL if not needed:
Config.setChromiumOptions({ disableWebSecurity: true })
Run linting:
npm run lintBuilt with Remotion for OnHyper personalized video outreach.