One-key formula: AI images, TTS narration, auto music, subtitles, and self-review pipeline

Prove that adding one API key (OPENAI_API_KEY) to the zero-key foundation
produces dramatically better videos for ~$0.69 each. Two compositions built:
The Abyss (deep ocean visual essay) and VOID (neural interface product ad).

New tools:
- audio_probe: ffprobe wrapper with probe_duration() helper
- composition_validator: pre-render checks (asset existence, audio-video sync)
- pixabay_music: royalty-free music scraper (no API key needed)
- freesound_music: Freesound API search + download

Remotion upgrades:
- BackgroundImageLayer: AI images behind data scenes with ken-burns + dark overlay
- Gradient support: all 9 components changed from backgroundColor to background CSS
- CaptionOverlay: word spacing fix, WhisperX word-level subtitles
- HeroTitle: reduced overlay opacity so background images show through

Process codified in agent skills:
- compose-director: audio acquisition flow (present user with voice/music/subtitle
  options), mandatory pre-render validation, post-render self-review (extract
  frames + transcribe + inspect + present findings to user)
- scene-director: narration duration budgeting (word budget from video duration)
- remotion skill: pre-render validation section
- TTS tool: now returns audio_duration_seconds in result

README updated with VOID product ad video embed.
This commit is contained in:
calesthio
2026-03-30 16:54:13 -07:00
parent 4ef335ac0b
commit 8cac647193
21 changed files with 1329 additions and 112 deletions
+104 -94
View File
@@ -88,6 +88,8 @@ interface Cut {
heroSubtitle?: string;
// Styling overrides
backgroundColor?: string;
backgroundImage?: string; // AI-generated or stock image rendered behind the component
backgroundOverlay?: number; // Opacity of dark overlay on backgroundImage (0-1, default 0.55)
color?: string;
accentColor?: string;
fontSize?: number;
@@ -219,7 +221,7 @@ const ImageScene: React.FC<{ src: string; animation?: string }> = ({
// "static" or "none" → just display
return (
<AbsoluteFill style={{ overflow: "hidden", backgroundColor: "#0F172A" }}>
<AbsoluteFill style={{ overflow: "hidden", background: "#0F172A" }}>
<Img
src={resolveAsset(src)}
style={{
@@ -255,7 +257,7 @@ const VideoScene: React.FC<{ src: string; startFrom?: number }> = ({
});
return (
<AbsoluteFill style={{ backgroundColor: "#0F172A" }}>
<AbsoluteFill style={{ background: "#0F172A" }}>
<OffthreadVideo
src={resolveAsset(src)}
startFrom={Math.round(startFrom * fps)}
@@ -276,154 +278,162 @@ const VideoScene: React.FC<{ src: string; startFrom?: number }> = ({
// Scene renderer — maps cut type / source to the right component
// ---------------------------------------------------------------------------
// Background image layer — renders an AI-generated/stock image behind data components
const BackgroundImageLayer: React.FC<{
src: string;
overlayOpacity?: number;
children: React.ReactNode;
}> = ({ src, overlayOpacity = 0.55, children }) => {
const frame = useCurrentFrame();
const { fps, durationInFrames } = useVideoConfig();
// Subtle ken-burns on the background
const progress = interpolate(frame, [0, durationInFrames], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const bgScale = 1 + progress * 0.08;
return (
<AbsoluteFill style={{ overflow: "hidden" }}>
{/* Background image with subtle zoom */}
<Img
src={resolveAsset(src)}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
transform: `scale(${bgScale})`,
willChange: "transform",
}}
/>
{/* Dark overlay for readability */}
<AbsoluteFill
style={{
background: `rgba(15, 23, 42, ${overlayOpacity})`,
}}
/>
{/* Component content on top */}
{children}
</AbsoluteFill>
);
};
const SceneRenderer: React.FC<{ cut: Cut }> = ({ cut }) => {
// Wrap component with background image if specified
const maybeWrapWithBgImage = (element: React.ReactElement) => {
if (cut.backgroundImage) {
return (
<BackgroundImageLayer
src={cut.backgroundImage}
overlayOpacity={cut.backgroundOverlay ?? 0.55}
>
{element}
</BackgroundImageLayer>
);
}
return element;
};
// Resolve the scene element based on cut type, then wrap with backgroundImage if set
const bgColor = cut.backgroundImage ? "transparent" : cut.backgroundColor;
// Explicit component types
if (cut.type === "text_card" && cut.text) {
return (
<TextCard
text={cut.text}
fontSize={cut.fontSize}
color={cut.color}
backgroundColor={cut.backgroundColor}
/>
return maybeWrapWithBgImage(
<TextCard text={cut.text} fontSize={cut.fontSize} color={cut.color} backgroundColor={bgColor} />
);
}
if (cut.type === "stat_card" && cut.stat) {
return (
<StatCard
stat={cut.stat}
subtitle={cut.subtitle}
accentColor={cut.accentColor}
backgroundColor={cut.backgroundColor}
/>
return maybeWrapWithBgImage(
<StatCard stat={cut.stat} subtitle={cut.subtitle} accentColor={cut.accentColor} backgroundColor={bgColor} />
);
}
if (cut.type === "callout" && cut.text) {
return (
return maybeWrapWithBgImage(
<CalloutBox
text={cut.text}
type={cut.callout_type}
title={cut.title}
borderColor={cut.accentColor}
backgroundColor={cut.backgroundColor}
textColor={cut.color}
containerBackgroundColor={cut.backgroundColor}
text={cut.text} type={cut.callout_type} title={cut.title}
borderColor={cut.accentColor} backgroundColor={cut.backgroundColor}
textColor={cut.color} containerBackgroundColor={bgColor}
/>
);
}
if (
cut.type === "comparison" &&
cut.leftLabel &&
cut.rightLabel &&
cut.leftValue &&
cut.rightValue
) {
return (
if (cut.type === "comparison" && cut.leftLabel && cut.rightLabel && cut.leftValue && cut.rightValue) {
return maybeWrapWithBgImage(
<ComparisonCard
leftLabel={cut.leftLabel}
rightLabel={cut.rightLabel}
leftValue={cut.leftValue}
rightValue={cut.rightValue}
title={cut.title}
backgroundColor={cut.backgroundColor}
textColor={cut.color}
leftLabel={cut.leftLabel} rightLabel={cut.rightLabel}
leftValue={cut.leftValue} rightValue={cut.rightValue}
title={cut.title} backgroundColor={bgColor} textColor={cut.color}
/>
);
}
if (cut.type === "hero_title" && cut.text) {
return <HeroTitle title={cut.text} subtitle={cut.heroSubtitle || cut.subtitle} />;
return maybeWrapWithBgImage(
<HeroTitle title={cut.text} subtitle={cut.heroSubtitle || cut.subtitle} />
);
}
// --- Chart types ---
if (cut.type === "bar_chart" && cut.chartData) {
return (
return maybeWrapWithBgImage(
<BarChart
data={cut.chartData}
title={cut.title}
colors={cut.chartColors}
data={cut.chartData} title={cut.title} colors={cut.chartColors}
animationStyle={(cut.chartAnimation as any) || "grow-up"}
showGrid={cut.showGrid}
showValues={cut.showValues}
backgroundColor={cut.backgroundColor}
showGrid={cut.showGrid} showValues={cut.showValues} backgroundColor={bgColor}
/>
);
}
if (cut.type === "line_chart" && cut.chartSeries) {
return (
return maybeWrapWithBgImage(
<LineChart
series={cut.chartSeries}
title={cut.title}
colors={cut.chartColors}
series={cut.chartSeries} title={cut.title} colors={cut.chartColors}
animationStyle={(cut.chartAnimation as any) || "draw"}
showGrid={cut.showGrid}
showMarkers={cut.showMarkers}
showLegend={cut.showLegend}
xLabel={cut.xLabel}
yLabel={cut.yLabel}
backgroundColor={cut.backgroundColor}
showGrid={cut.showGrid} showMarkers={cut.showMarkers} showLegend={cut.showLegend}
xLabel={cut.xLabel} yLabel={cut.yLabel} backgroundColor={bgColor}
/>
);
}
if (cut.type === "pie_chart" && cut.chartData) {
return (
return maybeWrapWithBgImage(
<PieChart
data={cut.chartData}
title={cut.title}
colors={cut.chartColors}
data={cut.chartData} title={cut.title} colors={cut.chartColors}
animationStyle={(cut.chartAnimation as any) || "expand"}
donut={cut.donut}
centerLabel={cut.centerLabel}
centerValue={cut.centerValue}
showLegend={cut.showLegend}
backgroundColor={cut.backgroundColor}
donut={cut.donut} centerLabel={cut.centerLabel} centerValue={cut.centerValue}
showLegend={cut.showLegend} backgroundColor={bgColor}
/>
);
}
if (cut.type === "kpi_grid" && cut.chartData) {
return (
return maybeWrapWithBgImage(
<KPIGrid
metrics={cut.chartData}
title={cut.title}
columns={cut.columns}
colors={cut.chartColors}
animationStyle={(cut.chartAnimation as any) || "count-up"}
backgroundColor={cut.backgroundColor}
metrics={cut.chartData} title={cut.title} columns={cut.columns}
colors={cut.chartColors} animationStyle={(cut.chartAnimation as any) || "count-up"}
backgroundColor={bgColor}
/>
);
}
if (cut.type === "progress_bar" && cut.progress !== undefined) {
return (
return maybeWrapWithBgImage(
<AbsoluteFill
style={{
backgroundColor: cut.backgroundColor || "#FFFFFF",
display: "flex",
alignItems: "center",
justifyContent: "center",
background: bgColor || "#FFFFFF",
display: "flex", alignItems: "center", justifyContent: "center",
padding: "80px 120px",
}}
>
{cut.title && (
<div
style={{
position: "absolute",
top: 120,
fontSize: 48,
fontWeight: 700,
color: "#1F2937",
textAlign: "center",
width: "100%",
}}
>
<div style={{
position: "absolute", top: 120, fontSize: 48, fontWeight: 700,
color: "#1F2937", textAlign: "center", width: "100%",
}}>
{cut.title}
</div>
)}
<ProgressBar
progress={cut.progress}
label={cut.progressLabel}
progress={cut.progress} label={cut.progressLabel}
color={cut.progressColor || cut.accentColor}
animationStyle={(cut.progressAnimation as any) || "fill"}
segments={cut.progressSegments}
backgroundColor={cut.backgroundColor}
segments={cut.progressSegments} backgroundColor={cut.backgroundColor}
/>
</AbsoluteFill>
);
@@ -493,7 +503,7 @@ export const Explainer: React.FC<ExplainerProps> = ({
const { fps, durationInFrames } = useVideoConfig();
return (
<AbsoluteFill style={{ backgroundColor: "#0F172A", fontFamily }}>
<AbsoluteFill style={{ background: "#0F172A", fontFamily }}>
{/* Layer 1: Visual scenes */}
{cuts.map((cut) => {
const from = Math.round(cut.in_seconds * fps);
@@ -111,7 +111,7 @@ export const CalloutBox: React.FC<CalloutBoxProps> = ({
return (
<AbsoluteFill
style={{
backgroundColor: containerBackgroundColor,
background: containerBackgroundColor,
justifyContent: "center",
alignItems: "center",
}}
@@ -107,7 +107,7 @@ const PageRenderer: React.FC<{
: "0 2px 4px rgba(0,0,0,0.5)",
}}
>
{w.word}
{w.word}{i < page.words.length - 1 ? " " : ""}
</span>
);
})}
@@ -134,7 +134,7 @@ export const ComparisonCard: React.FC<ComparisonCardProps> = ({
return (
<AbsoluteFill
style={{
backgroundColor,
background: backgroundColor,
justifyContent: "center",
alignItems: "center",
}}
@@ -24,7 +24,7 @@ export const HeroTitle: React.FC<HeroTitleProps> = ({ title, subtitle }) => {
justifyContent: "center",
alignItems: "center",
background:
"radial-gradient(ellipse at center, rgba(15,23,42,0.85) 0%, rgba(15,23,42,0.95) 100%)",
"radial-gradient(ellipse at center, rgba(15,23,42,0.35) 0%, rgba(15,23,42,0.55) 100%)",
}}
>
<div style={{ textAlign: "center", maxWidth: "85%" }}>
@@ -129,7 +129,7 @@ export const ProgressBar: React.FC<ProgressBarProps> = ({
return (
<AbsoluteFill
style={{
backgroundColor,
background: backgroundColor,
justifyContent: "center",
alignItems: "center",
}}
@@ -41,7 +41,7 @@ export const StatCard: React.FC<StatCardProps> = ({
style={{
justifyContent: "center",
alignItems: "center",
backgroundColor,
background: backgroundColor,
}}
>
<div style={{ textAlign: "center" }}>
@@ -30,7 +30,7 @@ export const TextCard: React.FC<TextCardProps> = ({
style={{
justifyContent: "center",
alignItems: "center",
backgroundColor,
background: backgroundColor,
}}
>
<div
@@ -73,7 +73,7 @@ export const BarChart: React.FC<BarChartProps> = ({
return (
<AbsoluteFill
style={{
backgroundColor,
background: backgroundColor,
justifyContent: "flex-start",
alignItems: "center",
padding: 40,
@@ -78,7 +78,7 @@ export const KPIGrid: React.FC<KPIGridProps> = ({
return (
<AbsoluteFill
style={{
backgroundColor,
background: backgroundColor,
justifyContent: "flex-start",
alignItems: "center",
fontFamily,
@@ -101,7 +101,7 @@ export const LineChart: React.FC<LineChartProps> = ({
return (
<AbsoluteFill
style={{
backgroundColor,
background: backgroundColor,
justifyContent: "flex-start",
alignItems: "center",
padding: 40,
@@ -91,7 +91,7 @@ export const PieChart: React.FC<PieChartProps> = ({
return (
<AbsoluteFill
style={{
backgroundColor,
background: backgroundColor,
justifyContent: "flex-start",
alignItems: "center",
padding: 40,