feat: add preset template compositions

Root.tsx now includes:
- TemplateVideo: generic template-based composition
- RedditMinecraft: viral reddit story + gameplay preset
- BedtimeStory: cozy children's story preset
- DialogExplainer: character dialog preset

Each composition has sensible defaults demonstrating the style.
Agents can render these directly or pass custom props.
This commit is contained in:
Chora
2026-02-02 20:01:42 -06:00
parent eecb66809e
commit ae83b817d9

View File

@@ -8,8 +8,9 @@ import React from 'react'
import { Composition } from 'remotion'
import { z } from 'zod'
import { CutroomVideo } from './CutroomVideo'
import { TemplateVideo, TemplateVideoProps } from './TemplateVideo'
// Schema for props validation
// Schema for basic props validation
const cutroomVideoSchema = z.object({
title: z.string(),
script: z.object({
@@ -31,6 +32,58 @@ const cutroomVideoSchema = z.object({
})),
})
// Schema for template-based videos
const templateVideoSchema = z.object({
title: z.string(),
script: z.object({
hook: z.string(),
sections: z.array(z.object({
heading: z.string(),
content: z.string(),
duration: z.number(),
speaker: z.string().optional(),
})),
cta: z.string(),
}),
voiceUrl: z.string().optional(),
musicUrl: z.string().optional(),
backgroundUrl: z.string().optional(),
clips: z.array(z.object({
url: z.string(),
startTime: z.number(),
duration: z.number(),
})).optional(),
template: z.object({
visualStyle: z.enum(['broll', 'gameplay', 'gradient', 'static']),
backgroundColor: z.string().optional(),
gradientColors: z.tuple([z.string(), z.string()]).optional(),
backgroundOpacity: z.number().optional(),
captionStyle: z.enum(['bold', 'subtle', 'karaoke', 'speech-bubble', 'none']),
captionPosition: z.enum(['top', 'center', 'bottom']),
captionFont: z.string().optional(),
captionColor: z.string().optional(),
captionStroke: z.string().optional(),
captionAnimation: z.enum(['none', 'word-by-word', 'typewriter', 'fade']),
transition: z.enum(['cut', 'crossfade', 'swipe']),
colorGrade: z.object({
warmth: z.number().optional(),
saturation: z.number().optional(),
vignette: z.number().optional(),
}).optional(),
watermarkUrl: z.string().optional(),
watermarkPosition: z.enum(['top-left', 'top-right', 'bottom-left', 'bottom-right']).optional(),
showEndCard: z.boolean().optional(),
ctaText: z.string().optional(),
isDialog: z.boolean().optional(),
characters: z.array(z.object({
name: z.string(),
spriteUrl: z.string(),
position: z.enum(['left', 'right']),
color: z.string(),
})).optional(),
}),
})
const defaultProps: z.infer<typeof cutroomVideoSchema> = {
title: 'Sample Video',
script: {
@@ -47,9 +100,84 @@ const defaultProps: z.infer<typeof cutroomVideoSchema> = {
clips: [],
}
// Default template props for different styles
const redditMinecraftProps: z.infer<typeof templateVideoSchema> = {
title: 'AITA for telling my roommate to stop...',
script: {
hook: 'Am I the bad guy here?',
sections: [
{ heading: '', content: 'So my roommate has been leaving dishes in the sink for weeks...', duration: 15 },
{ heading: '', content: 'I finally confronted them about it and they called me controlling.', duration: 15 },
{ heading: '', content: 'Now the whole apartment is giving me the cold shoulder.', duration: 10 },
],
cta: 'Follow for Part 2!',
},
template: {
visualStyle: 'gameplay',
captionStyle: 'bold',
captionPosition: 'center',
captionColor: '#ffffff',
captionStroke: '#000000',
captionAnimation: 'word-by-word',
transition: 'cut',
},
}
const bedtimeStoryProps: z.infer<typeof templateVideoSchema> = {
title: 'The Little Star',
script: {
hook: 'Once upon a time...',
sections: [
{ heading: '', content: 'There was a little star who lived in the night sky.', duration: 20 },
{ heading: '', content: 'Every night, the star would shine brightly for all the children below.', duration: 20 },
{ heading: '', content: 'And the children would make wishes upon the little star.', duration: 20 },
],
cta: 'Sweet dreams...',
},
template: {
visualStyle: 'gradient',
gradientColors: ['#1a1a2e', '#16213e'],
captionStyle: 'subtle',
captionPosition: 'center',
captionColor: '#f0e6d3',
captionAnimation: 'fade',
transition: 'crossfade',
colorGrade: { vignette: 0.4 },
showEndCard: false,
},
}
const dialogExplainerProps: z.infer<typeof templateVideoSchema> = {
title: 'Why is the sky blue?',
script: {
hook: 'Hey, I have a question...',
sections: [
{ heading: '', content: 'Hey, why is the sky blue?', duration: 5, speaker: 'Character A' },
{ heading: '', content: 'Well, light from the sun scatters in the atmosphere!', duration: 8, speaker: 'Character B' },
{ heading: '', content: 'Blue light scatters more because it travels in shorter waves.', duration: 8, speaker: 'Character B' },
{ heading: '', content: 'Ohhh that makes sense!', duration: 4, speaker: 'Character A' },
],
cta: 'Follow for more!',
},
template: {
visualStyle: 'static',
backgroundColor: '#87ceeb',
captionStyle: 'speech-bubble',
captionPosition: 'top',
captionAnimation: 'none',
transition: 'cut',
isDialog: true,
characters: [
{ name: 'Character A', spriteUrl: '/characters/curious.png', position: 'left', color: '#ffd700' },
{ name: 'Character B', spriteUrl: '/characters/smart.png', position: 'right', color: '#90ee90' },
],
},
}
export const RemotionRoot: React.FC = () => {
return (
<>
{/* Basic Compositions */}
<Composition
id="CutroomVideo"
component={CutroomVideo}
@@ -70,6 +198,52 @@ export const RemotionRoot: React.FC = () => {
schema={cutroomVideoSchema}
defaultProps={defaultProps}
/>
{/* Template-Based Compositions */}
<Composition
id="TemplateVideo"
component={TemplateVideo}
durationInFrames={1800}
fps={30}
width={1080}
height={1920}
schema={templateVideoSchema}
defaultProps={redditMinecraftProps}
/>
{/* Preset Template Compositions */}
<Composition
id="RedditMinecraft"
component={TemplateVideo}
durationInFrames={1200}
fps={30}
width={1080}
height={1920}
schema={templateVideoSchema}
defaultProps={redditMinecraftProps}
/>
<Composition
id="BedtimeStory"
component={TemplateVideo}
durationInFrames={2700}
fps={30}
width={1080}
height={1920}
schema={templateVideoSchema}
defaultProps={bedtimeStoryProps}
/>
<Composition
id="DialogExplainer"
component={TemplateVideo}
durationInFrames={900}
fps={30}
width={1080}
height={1920}
schema={templateVideoSchema}
defaultProps={dialogExplainerProps}
/>
</>
)
}