Animation pipeline: AnimeScene engine, Ghibli-style compositions, audio energy tool, and README showcase

Add anime_scene rendering engine (AnimeScene + ParticleOverlay components) with multi-image
crossfade, 9 camera motion types, 5 particle systems, and cinematic lighting overlays.
Fix critical Remotion durationInFrames footgun by passing sceneDurationSeconds from parent.
Add audio offset/loop support in Explainer for skipping quiet music intros.

New tools: audio_energy.py analyzes per-second loudness via ebur128 to find optimal music
offset and detect when looping is needed.

Update all 6 animation pipeline skills (proposal, scene, asset, compose, executive-producer,
remotion.md) with battle-tested image_animation workflow including tool availability scan,
FLUX multi-image generation, composition JSON format, pre-render validation, and post-render
self-review.

Add 3 demo compositions (Candyland, Mori no Seishin, Deep Ocean) and anime-ghibli style
playbook. Update README with 3 anime video showcases and animation prompts. Add Animation
Pipeline section to PROMPT_GALLERY.md.
This commit is contained in:
calesthio
2026-03-31 17:40:50 -07:00
parent 249a6cb9dd
commit 237af7fb5c
18 changed files with 2061 additions and 56 deletions
+40 -1
View File
@@ -34,6 +34,9 @@ import { CaptionOverlay, WordCaption } from "./components/CaptionOverlay";
import { SectionTitle } from "./components/SectionTitle";
import { StatReveal } from "./components/StatReveal";
import { HeroTitle } from "./components/HeroTitle";
import { AnimeScene } from "./components/AnimeScene";
import type { CameraMotion } from "./components/AnimeScene";
import type { ParticleType } from "./components/ParticleOverlay";
// Load Space Grotesk font for cinematic typography
const { fontFamily } = loadFont("normal", {
@@ -102,6 +105,15 @@ interface Cut {
scale?: number;
position?: string | { x: number; y: number };
};
// Anime scene props (type: "anime_scene")
images?: string[];
particles?: ParticleType;
particleColor?: string;
particleCount?: number;
particleIntensity?: number;
vignette?: boolean;
lightingFrom?: string;
lightingTo?: string;
}
interface Overlay {
@@ -124,6 +136,11 @@ interface AudioConfig {
music?: AudioLayer & {
fadeInSeconds?: number;
fadeOutSeconds?: number;
/** Start playback from this offset in seconds (skip quiet intros).
* Use the audio_energy tool to find the optimal offset. */
offsetSeconds?: number;
/** Loop the music if it's shorter than the video duration. */
loop?: boolean;
};
}
@@ -439,6 +456,25 @@ const SceneRenderer: React.FC<{ cut: Cut }> = ({ cut }) => {
);
}
// --- Anime scene (multi-image crossfade + particles) ---
if (cut.type === "anime_scene" && cut.images && cut.images.length > 0) {
return (
<AnimeScene
images={cut.images}
animation={(cut.animation as CameraMotion) || "ken-burns"}
particles={cut.particles}
particleColor={cut.particleColor}
particleCount={cut.particleCount}
particleIntensity={cut.particleIntensity}
backgroundColor={cut.backgroundColor}
vignette={cut.vignette ?? true}
lightingFrom={cut.lightingFrom}
lightingTo={cut.lightingTo}
sceneDurationSeconds={cut.out_seconds - cut.in_seconds}
/>
);
}
// --- Media types (image / video fallback) ---
const animation = cut.animation || cut.transform?.animation;
@@ -546,10 +582,13 @@ export const Explainer: React.FC<ExplainerProps> = ({
<Audio src={resolveAsset(audio.narration.src)} volume={audio.narration.volume ?? 1} />
)}
{/* Layer 4: Audio — music with fade in/out */}
{/* Layer 4: Audio — music with offset, fade in/out, and optional loop */}
{audio?.music?.src && (
<Audio
src={resolveAsset(audio.music.src)}
startFrom={Math.round((audio.music.offsetSeconds ?? 0) * fps)}
loop={audio.music.loop ?? false}
loopVolumeCurveBehavior="repeat"
volume={(f) => {
const baseVol = audio.music!.volume ?? 0.1;
const fadeInDur = (audio.music!.fadeInSeconds ?? 2) * fps;