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:
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user