Fix gap #22 and add CinematicRenderer captions + music support

- AGENT_GUIDE.md: add "never read source code" rule — skills are the
  interface, not .py files
- animation.yaml: add Layer 2 skill-first guardrail in assets stage
- video-reference-analyst.md: Step 4b now mandates Layer 2 before Layer 3,
  explicitly forbids reading implementation code
- CinematicRenderer: add TikTok-style CaptionOverlay and separate music
  track support (narration + music as independent audio layers)
- cinematic/types.ts: add CinematicCaptionConfig and music prop types
This commit is contained in:
calesthio
2026-04-04 14:23:46 -07:00
parent 370f2f11b7
commit 7b40edb82c
5 changed files with 60 additions and 8 deletions
+30 -3
View File
@@ -24,6 +24,7 @@ function resolveAsset(src: string): string {
return staticFile(clean);
}
import { CinematicRendererProps, CinematicTone, CinematicVideoScene } from "./cinematic/types";
import { CaptionOverlay } from "./components/CaptionOverlay";
const FPS = 30;
@@ -339,19 +340,34 @@ export const CinematicRenderer: React.FC<CinematicRendererProps> = ({
titleWidth = 1320,
signalLineCount = 18,
soundtrack,
music,
captions,
}) => {
return (
<AbsoluteFill style={{ backgroundColor: "#000000" }}>
{/* Layer 1: Narration audio */}
{soundtrack ? (
<Soundtrack
src={soundtrack.src}
volume={soundtrack.volume ?? 0.45}
volume={soundtrack.volume ?? 1}
trimBeforeSeconds={soundtrack.trimBeforeSeconds}
trimAfterSeconds={soundtrack.trimAfterSeconds}
fadeInSeconds={soundtrack.fadeInSeconds ?? 1.5}
fadeOutSeconds={soundtrack.fadeOutSeconds ?? 2}
fadeInSeconds={soundtrack.fadeInSeconds ?? 0.3}
fadeOutSeconds={soundtrack.fadeOutSeconds ?? 0.5}
/>
) : null}
{/* Layer 2: Music bed (separate track, ducked) */}
{music ? (
<Soundtrack
src={music.src}
volume={music.volume ?? 0.15}
trimBeforeSeconds={music.trimBeforeSeconds}
trimAfterSeconds={music.trimAfterSeconds}
fadeInSeconds={music.fadeInSeconds ?? 2}
fadeOutSeconds={music.fadeOutSeconds ?? 3}
/>
) : null}
{/* Layer 3: Video scenes */}
{scenes.map((scene) => (
<Sequence
key={scene.id}
@@ -372,6 +388,17 @@ export const CinematicRenderer: React.FC<CinematicRendererProps> = ({
)}
</Sequence>
))}
{/* Layer 4: TikTok-style captions */}
{captions?.words ? (
<CaptionOverlay
words={captions.words}
wordsPerPage={captions.wordsPerPage ?? 5}
fontSize={captions.fontSize ?? 48}
color={captions.color ?? "#F8FAFC"}
highlightColor={captions.highlightColor ?? "#FBBF24"}
backgroundColor={captions.backgroundColor ?? "rgba(0, 0, 0, 0.6)"}
/>
) : null}
</AbsoluteFill>
);
};
+17
View File
@@ -35,6 +35,21 @@ export interface CinematicSoundtrack {
fadeOutSeconds?: number;
}
export interface CinematicWordCaption {
word: string;
startMs: number;
endMs: number;
}
export interface CinematicCaptionConfig {
words: CinematicWordCaption[];
wordsPerPage?: number;
fontSize?: number;
color?: string;
highlightColor?: string;
backgroundColor?: string;
}
export interface CinematicRendererProps {
[key: string]: unknown;
scenes: CinematicScene[];
@@ -42,4 +57,6 @@ export interface CinematicRendererProps {
titleWidth?: number;
signalLineCount?: number;
soundtrack?: CinematicSoundtrack;
music?: CinematicSoundtrack;
captions?: CinematicCaptionConfig;
}