diff --git a/remotion/README.md b/remotion/README.md index 857004e..e8acfcd 100644 --- a/remotion/README.md +++ b/remotion/README.md @@ -131,8 +131,19 @@ python3 werkzeuge/posen.py # Posen und C ``` Mit `pip install "rembg[gpu]"` nutzt `rembg` eine Nvidia-Grafikkarte automatisch (auf dem VPS gibt es keine; dort ca. 3 s pro Bild). -Orte neu vorberechnen (nach Änderungen in `orte.tsx`): +Orte neu vorberechnen (nach Änderungen in `orte.tsx` bzw. am Nest in `natur.tsx`): `npx remotion still src/index.ts F2Ort-garten public/orte/garten.webp --image-format=webp` +Bilder mit durchsichtigem Hintergrund (Zimmerwand, Tisch, Nest) als PNG +rendern (`--image-format=png`): Remotions WebP-Ausgabe speichert keine +Transparenz. + +### Was das Rendern bremst +Gemessen im Entwurf (halbe Auflösung, 4 Kerne auf dem VPS): die meisten +Szenen schaffen 3,5–5,5 Bilder/s. Teuer sind viele einzeln gezeichnete +Elemente (z. B. über 70 Blätter im Nest) und CSS-Filter oder Mischmodi über +das ganze Bild. Darum: Fertiges wie das Nest als Bild vorberechnen +(`F2Nest-…`), Filter nur auf die Figuren legen (`filter`-Prop von `Figur`/ +`ClipFigur`/`Nest`), keine `mixBlendMode`-Ebenen über das ganze Bild. ## Bekannte Grenzen - Die Figuren haben nur die Pose ihres Bildes. Neue Posen brauchen ein neues diff --git a/remotion/public/orte/nest_hinten.png b/remotion/public/orte/nest_hinten.png new file mode 100644 index 0000000..1acc5db Binary files /dev/null and b/remotion/public/orte/nest_hinten.png differ diff --git a/remotion/public/orte/nest_vorne.png b/remotion/public/orte/nest_vorne.png new file mode 100644 index 0000000..40d6ba1 Binary files /dev/null and b/remotion/public/orte/nest_vorne.png differ diff --git a/remotion/src/Root.tsx b/remotion/src/Root.tsx index 7e7f29f..3823f3b 100644 --- a/remotion/src/Root.tsx +++ b/remotion/src/Root.tsx @@ -4,7 +4,8 @@ import {HintergrundAbend, PapierStruktur, VordergrundAbend} from './szenen/Hinte import {LaterneTest} from './szenen/LaterneTest'; import {VerformTest} from './szenen/VerformTest'; import {FolgenKompositionen} from './baukasten/folge'; -import {ORTE, OrtStill} from './baukasten/orte'; +import {NEST_BILD} from './baukasten/natur'; +import {NestStill, ORTE, OrtStill} from './baukasten/orte'; import {F02} from './folge02/Folge02'; const BILD = {fps: 30, width: 1920, height: 1080} as const; @@ -17,6 +18,9 @@ export const Root: React.FC = () => ( + {(['hinten', 'vorne'] as const).map((seite) => ( + + ))} {Object.keys(ORTE).map((name) => ( ))} diff --git a/remotion/src/baukasten/figur.tsx b/remotion/src/baukasten/figur.tsx index d900f6e..a5818f8 100644 --- a/remotion/src/baukasten/figur.tsx +++ b/remotion/src/baukasten/figur.tsx @@ -33,12 +33,13 @@ type Lage = { stauchY?: number; deckkraft?: number; schatten?: boolean; + filter?: string; // CSS-Filter nur auf die Figur (z. B. Nacht), billiger als über das ganze Bild children: React.ReactNode; figur: FigurName; }; // Gemeinsamer Rahmen: Leinwand der Figur so legen, dass der Fußpunkt auf (x, y) liegt. -const Rahmen: React.FC = ({x, y, hoehe, spiegeln, drehung = 0, stauchX = 1, stauchY = 1, deckkraft = 1, schatten = true, figur, children}) => { +const Rahmen: React.FC = ({x, y, hoehe, spiegeln, drehung = 0, stauchX = 1, stauchY = 1, deckkraft = 1, schatten = true, filter, figur, children}) => { const f = FIGUREN[figur]; const s = hoehe / f.refHoehe; return ( @@ -57,7 +58,7 @@ const Rahmen: React.FC = ({x, y, hoehe, spiegeln, drehung = 0, stauchX = 1 /> )}
-
+
{children}
diff --git a/remotion/src/baukasten/natur.tsx b/remotion/src/baukasten/natur.tsx index 3015992..464f055 100644 --- a/remotion/src/baukasten/natur.tsx +++ b/remotion/src/baukasten/natur.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {random, useCurrentFrame} from 'remotion'; +import {Img, random, staticFile, useCurrentFrame} from 'remotion'; import {SCHRIFT} from './untertitel'; // Kleine, per Code gezeichnete Dinge: Blätter, Vögel, Schnee, Nest, Zzz, Funkeln. @@ -168,17 +168,36 @@ const nestBlaetter = (seite: 'hinten' | 'vorne') => }); const NEST = {hinten: nestBlaetter('hinten'), vorne: nestBlaetter('vorne')}; -export const Nest: React.FC<{x: number; y: number; voll?: number; seite: 'hinten' | 'vorne'; s?: number}> = ({x, y, voll = 1, seite, s = 1}) => ( -
+// Das fertige Nest (voll = 1) kommt als vorberechnetes Bild (public/orte/nest_*.png, +// Komposition F2Nest-…); nur das wachsende Nest wird Blatt für Blatt gezeichnet. +export const NEST_BILD = {b: 440, h: 260, x: 220, y: 180}; // Bildgröße und Lage des Ankerpunkts + +export const NestBlaetter: React.FC<{voll?: number; seite: 'hinten' | 'vorne'}> = ({voll = 1, seite}) => ( + <> {seite === 'hinten' && (
)} {NEST[seite].map((b, i) => b.reihe < voll ? : null, )} -
+ ); +export const Nest: React.FC<{x: number; y: number; voll?: number; seite: 'hinten' | 'vorne'; s?: number; filter?: string}> = ({x, y, voll = 1, seite, s = 1, filter}) => + voll >= 1 ? ( + + ) : ( +
+ {seite === 'hinten' && ( +
+ )} + +
+ ); + // Zzz, die aufsteigen und verblassen. export const Zzz: React.FC<{x: number; y: number; start: number; ende?: number; s?: number}> = ({x, y, start, ende = 1e9, s = 1}) => { const frame = useCurrentFrame(); diff --git a/remotion/src/baukasten/orte.tsx b/remotion/src/baukasten/orte.tsx index 8d361ec..a2eb851 100644 --- a/remotion/src/baukasten/orte.tsx +++ b/remotion/src/baukasten/orte.tsx @@ -1,6 +1,7 @@ import React from 'react'; import {AbsoluteFill, Img, random, staticFile} from 'remotion'; import './untertitel'; // lädt die Schrift für den Kalender +import {NEST_BILD, NestBlaetter} from './natur'; // Orte der Folge 2 in Aquarell-Optik. Werden einmal als PNG vorberechnet // (Kompositionen „F2Ort…“, siehe README) und in den Szenen als Bild benutzt. @@ -385,7 +386,7 @@ export const KalenderMalen: React.FC<{p: number; wackeln: number}> = ({p, wackel let spitze: {x: number; y: number}; let farbe = '#c8642c'; if (p < titelAnteil) { - spitze = {x: 560 + tp * 800, y: 175 + Math.sin(tp * 60) * 14}; + spitze = {x: 440 + tp * 1060, y: 175 + Math.sin(tp * 60) * 14}; } else { const t = kTuer(aktiv); farbe = t.farbe; @@ -400,7 +401,7 @@ export const KalenderMalen: React.FC<{p: number; wackeln: number}> = ({p, wackel - + Pinos Winterfest-Kalender @@ -431,11 +432,15 @@ export const KalenderMalen: React.FC<{p: number; wackeln: number}> = ({p, wackel - - - - - + {/* Ärmel */} + + + {/* Handrücken und Finger, die den Stift halten */} + + + + + ); @@ -455,6 +460,15 @@ export const ORTE: Record = { kalender: () => , kalender_leer: () => , }; +// Fertiges Nest zum Vorberechnen (440 × 260, Anker bei NEST_BILD.x/y) +export const NestStill: React.FC<{seite: 'hinten' | 'vorne'}> = ({seite}) => ( + +
+ +
+
+); + export const OrtStill: React.FC<{name: string}> = ({name}) => { const K = ORTE[name]; return ( diff --git a/remotion/src/baukasten/posen.ts b/remotion/src/baukasten/posen.ts index 83e3d3d..139d01c 100644 --- a/remotion/src/baukasten/posen.ts +++ b/remotion/src/baukasten/posen.ts @@ -894,6 +894,15 @@ export const POSEN: Record = { }; export const CLIPS: Record = { + "lulu_C1_gehen-schleife": { + "figur": "lulu", + "datei": "bibliothek/clips/lulu_C1_gehen-schleife.webm", + "x": 488.67289719626166, + "y": 31.077881619937692, + "b": 422.2305295950155, + "h": 705.1464174454828, + "frames": 121 + }, "lulu_C9_hals-runter": { "figur": "lulu", "datei": "bibliothek/clips/lulu_C9_hals-runter.webm", @@ -903,6 +912,15 @@ export const CLIPS: Record = { "h": 715.8629283489096, "frames": 121 }, + "noa_C1_gehen-schleife": { + "figur": "noa", + "datei": "bibliothek/clips/noa_C1_gehen-schleife.webm", + "x": 537.9688473520249, + "y": 147.8878504672897, + "b": 312.9221183800623, + "h": 555.1152647975077, + "frames": 121 + }, "noa_C4_winken": { "figur": "noa", "datei": "bibliothek/clips/noa_C4_winken.webm", @@ -912,6 +930,24 @@ export const CLIPS: Record = { "h": 546.5420560747663, "frames": 121 }, + "noa_C5_hinsetzen": { + "figur": "noa", + "datei": "bibliothek/clips/noa_C5_hinsetzen.webm", + "x": 531.5389408099688, + "y": 159.67601246105917, + "b": 430.803738317757, + "h": 542.2554517133956, + "frames": 121 + }, + "pino_C1_gehen-schleife": { + "figur": "pino", + "datei": "bibliothek/clips/pino_C1_gehen-schleife.webm", + "x": 475.8130841121495, + "y": 65.37071651090342, + "b": 469.38317757009344, + "h": 623.7009345794391, + "frames": 121 + }, "pino_C3_freudensprung": { "figur": "pino", "datei": "bibliothek/clips/pino_C3_freudensprung.webm", diff --git a/remotion/src/folge02/szene7.tsx b/remotion/src/folge02/szene7.tsx index 3081ab9..7ffc3d1 100644 --- a/remotion/src/folge02/szene7.tsx +++ b/remotion/src/folge02/szene7.tsx @@ -12,6 +12,7 @@ import {plane} from './plan'; const NOA_X7 = 1140; const LULU_X7 = 1800; const LATERNE = {x: 1290, y: BODEN + 10}; +const NACHT = 'brightness(0.62) saturate(0.8) hue-rotate(-8deg)'; // nur auf Figuren und Nest export const S7 = plane('s7', [ @@ -68,7 +69,6 @@ export const Szene7: React.FC = () => { <> -
{f < winken ? ( { x={NOA_X7} y={BODEN} stauchY={atmen(f, 2)} + filter={NACHT} /> ) : ( - + )} - +
- +
- + { ]} x={LULU_X7} y={BODEN - 10} + filter={NACHT} stauchY={atmen(f, 3, 0.006)} drehung={f > z.t1.start && f < z.t1.ende ? Math.sin((f - z.t1.start) * 0.25) * 2 : 0} /> -
{/* warmes Licht der Laterne */}
{ top: LATERNE.y - 560, width: 1040, height: 900, - background: `radial-gradient(closest-side, rgba(255, 200, 110, ${0.42 * flackern}), rgba(255, 180, 90, 0.12) 55%, rgba(255, 180, 90, 0) 100%)`, - mixBlendMode: 'screen', + background: `radial-gradient(closest-side, rgba(255, 200, 110, ${0.3 * flackern}), rgba(255, 180, 90, 0.08) 55%, rgba(255, 180, 90, 0) 100%)`, }} />