From 20ecbf1f98fc6c4085683a5a11d29fd9450778fe Mon Sep 17 00:00:00 2001 From: asepharyana Date: Tue, 22 Sep 2026 22:58:11 +0700 Subject: [PATCH] feat(shorts): 11 animated bg motifs (grid/dots/waves/stars/zigzag/brackets) + per-script seed shuffle --- scripts/render-worker.mjs | 7 +- src/remotion/ShortsVideo.tsx | 24 +++---- src/remotion/index.tsx | 2 +- src/remotion/parts.tsx | 122 +++++++++++++++++++++++++++++------ 4 files changed, 122 insertions(+), 33 deletions(-) diff --git a/scripts/render-worker.mjs b/scripts/render-worker.mjs index 8a4f1aa..1dde9a2 100644 --- a/scripts/render-worker.mjs +++ b/scripts/render-worker.mjs @@ -278,10 +278,15 @@ async function renderScript(script) { // 4. Render silent video via Remotion CLI (props = segments JSON) const silentMp4 = path.join(work, "silent.mp4"); log(`[${id}] Remotion render ${timeline.length} segs → silent.mp4`); + // Deterministic per-script seed → background motifs + scene variety shuffle + // per video (same script re-render keeps same look; different scripts differ). + let seed = 0; + for (const ch of id) seed = (seed * 31 + ch.charCodeAt(0)) >>> 0; + seed %= 110; sh("npx", [ "--yes", "remotion", "render", REMOTION_ENTRY, "Shorts", silentMp4, "--codec=h264", "--crf=20", "--browser-executable=" + CHROME, - "--props=" + JSON.stringify({ segments: timeline }), + "--props=" + JSON.stringify({ segments: timeline, seed }), "--concurrency=6", "--log=info", ], { timeout: 600_000, env: { ...process.env } }); diff --git a/src/remotion/ShortsVideo.tsx b/src/remotion/ShortsVideo.tsx index 1927c87..15d9a9b 100644 --- a/src/remotion/ShortsVideo.tsx +++ b/src/remotion/ShortsVideo.tsx @@ -74,9 +74,10 @@ function extractStat(text: string): { value: string; label: string } | null { } /** Pick a scene for this segment (variety — pattern interrupt every cut). */ -function pickScene(seg: Segment, index: number): Segment["scene"] | "caption" { +function pickScene(seg: Segment, index: number, seed = 0): Segment["scene"] | "caption" { if (seg.scene) return seg.scene; const label = (seg.label || "").toUpperCase(); + const i = (index + seed) % 5; // AI-tips scenes handled by worker override; here we only route the shared // creative variety (bang for twist, bullets for insight) — HOOK stays title. if (label === "THE MISTAKE" || label === "WHY IT FAILS") return "compare"; @@ -89,7 +90,7 @@ function pickScene(seg: Segment, index: number): Segment["scene"] | "caption" { if (seg.stat) return "stat"; const stat = extractStat(seg.text); if (stat) return "stat"; - if (index % 3 === 2) return "title"; + if (i % 3 === 2) return "title"; return "caption"; } @@ -98,9 +99,10 @@ const Scene: React.FC<{ seg: Segment; index: number; from: number; -}> = ({ seg, index, from }) => { + seed?: number; +}> = ({ seg, index, from, seed = 0 }) => { const { duration, label, text } = seg; - const scene = pickScene(seg, index); + const scene = pickScene(seg, index, seed); const stat = seg.stat || (scene === "stat" ? extractStat(text) : null); const sentences = seg.sentences && seg.sentences.length @@ -112,7 +114,7 @@ const Scene: React.FC<{ if (scene === "terminal") { return ( - + {label ? : null} @@ -125,7 +127,7 @@ const Scene: React.FC<{ if (scene === "compare" || scene === "fix") { return ( - + {label ? : null} @@ -138,7 +140,7 @@ const Scene: React.FC<{ if (scene === "bullets") { return ( - + {label ? : null} @@ -151,7 +153,7 @@ const Scene: React.FC<{ if (scene === "bang") { return ( - + {label ? : null} @@ -164,7 +166,7 @@ const Scene: React.FC<{ return ( - + {label ? : null} @@ -243,7 +245,7 @@ function titleFor(seg: Segment): string { } /** Composition: each scene time-gated by Sequence (frame resets per scene). */ -export const ShortsVideo: React.FC<{ segments: Segment[] }> = ({ segments }) => { +export const ShortsVideo: React.FC<{ segments: Segment[]; seed?: number }> = ({ segments, seed = 0 }) => { let acc = 0; return ( <> @@ -252,7 +254,7 @@ export const ShortsVideo: React.FC<{ segments: Segment[] }> = ({ segments }) => acc += s.duration; return ( - + ); })} diff --git a/src/remotion/index.tsx b/src/remotion/index.tsx index db60f75..e3b3a71 100644 --- a/src/remotion/index.tsx +++ b/src/remotion/index.tsx @@ -14,7 +14,7 @@ const Shorts: React.FC = () => { fps={30} width={1080} height={1920} - defaultProps={{ segments: DEFAULT_SEGMENTS }} + defaultProps={{ segments: DEFAULT_SEGMENTS, seed: 0 }} calculateMetadata={({ props }) => { const segs = props.segments?.length ? props.segments : DEFAULT_SEGMENTS; return { durationInFrames: useTotalDuration(segs) }; diff --git a/src/remotion/parts.tsx b/src/remotion/parts.tsx index 74de2bd..2421188 100644 --- a/src/remotion/parts.tsx +++ b/src/remotion/parts.tsx @@ -27,37 +27,47 @@ const SCENES = [ { bg: "#101014", motif: "barcode" }, // vertical bars { bg: "#15151a", motif: "rings" }, // target rings { bg: "#0f0f12", motif: "diag" }, // diagonal ticks + { bg: "#0c0c10", motif: "grid" }, // blueprint grid + { bg: "#141418", motif: "dots" }, // dot matrix + { bg: "#0e0e12", motif: "waves" }, // sine waves + { bg: "#131317", motif: "stars" }, // rotating asterisks + { bg: "#111115", motif: "zigzag" }, // zigzag bands + { bg: "#0b0b0e", motif: "brackets" }, // blueprint corner frame ]; -/** Flat mono background: solid color + faint white SVG motif per scene. */ +/** Flat mono background: solid color + faint white SVG motif per scene. + * Each motif animates with its own cheap (transform/opacity) motion. */ export const SvgBackground: React.FC<{ variant?: number }> = ({ variant = 0 }) => { const { width, height } = useVideoConfig(); const frame = useCurrentFrame(); const t = frame / 60; const s = SCENES[variant % SCENES.length]; - const stroke = "rgba(255,255,255,0.14)"; + const stroke = "rgba(255,255,255,0.15)"; const strokeSoft = "rgba(255,255,255,0.07)"; + const W = width; + const H = height; return ( - {/* faint motion — shape drifts very slowly (transform only, cheap) */} {s.motif === "circles" && ( <> - - - + + + + + )} {s.motif === "plus" && ( <> {[0.12, 0.22, 0.5, 0.72, 0.88].map((x, i) => ( - + @@ -67,30 +77,102 @@ export const SvgBackground: React.FC<{ variant?: number }> = ({ variant = 0 }) = {s.motif === "barcode" && ( <> {[30, 70, 110, 150, 190, 230, 270, 310].map((x, i) => ( - + ))} - {[width - 330, width - 290, width - 250, width - 210, width - 170, width - 130, width - 90].map((x, i) => ( - + {[W - 330, W - 290, W - 250, W - 210, W - 170, W - 130, W - 90].map((x, i) => ( + ))} )} {s.motif === "rings" && ( - <> - - - - + + + + + )} {s.motif === "diag" && ( <> {[0, 1, 2, 3, 4, 5].map((i) => ( - + ))} {[0, 1, 2, 3].map((i) => ( - + ))} )} + {s.motif === "grid" && ( + <> + {[0.15, 0.3, 0.45, 0.6, 0.75, 0.9].map((y, i) => ( + + ))} + {[0.1, 0.25, 0.4, 0.55, 0.7, 0.85].map((x, i) => ( + + ))} + + + )} + {s.motif === "dots" && ( + <> + {[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9].map((x, i) => ( + + {[0.15, 0.3, 0.45, 0.6, 0.75, 0.9].map((y, j) => ( + + ))} + + ))} + + )} + {s.motif === "waves" && ( + <> + + + + )} + {s.motif === "stars" && ( + <> + {[ + { x: 0.16, y: 0.2, s: 30, ph: 0 }, + { x: 0.8, y: 0.28, s: 22, ph: 1.4 }, + { x: 0.3, y: 0.72, s: 26, ph: 2.6 }, + { x: 0.72, y: 0.8, s: 18, ph: 3.8 }, + { x: 0.88, y: 0.62, s: 34, ph: 0.7 }, + ].map((st, i) => ( + + {[0, 1, 2, 3, 4, 5].map((k) => ( + + ))} + + ))} + + )} + {s.motif === "zigzag" && ( + <> + {[0.14, 0.3, 0.46].map((y, i) => ( + + ))} + + )} + {s.motif === "brackets" && ( + <> + + + + + + + + )} {/* slow drift to keep the frame alive */}