diff --git a/.gitignore b/.gitignore index 4daccac..18d666a 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,7 @@ node_modules/ __pycache__/ schnitt/ausgabe/ schnitt/tmp/ + +# Zugangsschlüssel (ElevenLabs u. a.), nie ins Git +.env +.env.local diff --git a/figuren/bibliothek.md b/figuren/bibliothek.md index e7e5c14..1b9644e 100644 --- a/figuren/bibliothek.md +++ b/figuren/bibliothek.md @@ -44,6 +44,7 @@ unter `figuren/bibliothek/`. Die **Clips** (MP4) sind nicht im Git (siehe | noa_A8_rueckansicht | Rückansicht | 1 | 1,5 | | | noa_A_arm-hoch-winken | rechter Arm hoch, winkend | 1 | 1,5 | | | noa_A_arme-hoch | beide Arme hoch | 1 | 1,5 | | +| noa_A_klatschen | klatscht, Fäustlinge vor der Brust (02.11.2026, Nano Banana 2 aus noa_ref_stehend, für das Lied) | 1 | 1,5 | ohne rembg freigestellt | | noa_A_laterne | Laterne in rechter Hand | 2 | 3 | 1. Versuch ohne Laterne; 2. gespiegelt; Laterne ragt seitlich weit heraus | | noa_B1_traurig | traurig | 2 | 1,5 | 1. Versuch abgebrochen, erstattet | | noa_B2_erschrocken | kurz erschrocken | 1 | 1,5 | | diff --git a/figuren/bibliothek/jobs.tsv b/figuren/bibliothek/jobs.tsv index 9ebee54..154d855 100644 --- a/figuren/bibliothek/jobs.tsv +++ b/figuren/bibliothek/jobs.tsv @@ -61,3 +61,4 @@ lulu_B3_schlafen.png 23_e3e6898f-ba75-410d-9488-936dd5af4 lulu_B_kopf-profil.png 23_806c2339-90e8-42af-92b0-36b390e7d noa_B1_traurig.png 030f14b3-a839-44d0-90bf-5a89e1fc92a9 pino_B3_schlafen.png dc751ba2-c05b-493b-95e1-c92668f1c627 +noa_A_klatschen.png cbb2880c-95c7-4276-9cdf-a7eef639c5a7 diff --git a/figuren/bibliothek/noa_A_klatschen.png b/figuren/bibliothek/noa_A_klatschen.png new file mode 100644 index 0000000..2ba1a15 Binary files /dev/null and b/figuren/bibliothek/noa_A_klatschen.png differ diff --git a/remotion/public/bibliothek/figuren/noa_A_klatschen.webp b/remotion/public/bibliothek/figuren/noa_A_klatschen.webp new file mode 100644 index 0000000..d15c60e Binary files /dev/null and b/remotion/public/bibliothek/figuren/noa_A_klatschen.webp differ diff --git a/remotion/src/baukasten/clipkorrektur.ts b/remotion/src/baukasten/clipkorrektur.ts new file mode 100644 index 0000000..0a74753 --- /dev/null +++ b/remotion/src/baukasten/clipkorrektur.ts @@ -0,0 +1,42 @@ +// Korrektur für Clips, in denen die Kamera von Higgsfield leicht zoomt oder schwenkt. +// Gemessen an den Beinen (Mitte und Breite) je Clip-Bild, in Pixeln des Clipbildes. +// Angewendet wird die Rückrechnung: die Beine bleiben am Platz und gleich breit. + +type Messung = [number, number, number]; // [Clip-Bild (24 fps), Beinmitte x, Beinbreite] + +const MESSUNGEN: Record = { + // Lulu senkt den Hals: Beine werden um 20 % breiter und wandern 79 px zur Seite + 'lulu_C9_hals-runter': { + bildBreite: 662, // Breite des Clipbildes in Pixeln + fussY: 659, + werte: [ + [0, 417.5, 211], [16, 417, 210], [20, 417.5, 211], [24, 418.5, 211], [28, 420.5, 213], [32, 423, 214], + [36, 426.5, 215], [40, 430, 216], [44, 434, 218], [48, 438, 218], [52, 442.5, 221], [56, 447, 222], + [60, 452, 224], [64, 457, 226], [68, 461, 228], [72, 466.5, 231], [76, 470.5, 233], [80, 475, 236], + [84, 479.5, 239], [88, 483.5, 241], [92, 487, 244], [96, 490.5, 247], [100, 493.5, 249], [104, 495.5, 251], + [108, 496.5, 253], [120, 496.5, 253], + ], + }, +}; + +export type Korrektur = {k: number; mitte: number; ziel: number; fussY: number; bildBreite: number}; + +// Für Clip-Bild `b`: Maßstab k und Beinmitte (Ist und Soll), alles in Clip-Pixeln. +export const clipKorrektur = (name: string, b: number): Korrektur | null => { + const m = MESSUNGEN[name]; + if (!m) return null; + const w = m.werte; + const bb = Math.min(w[w.length - 1][0], Math.max(0, b)); + let i = 0; + while (i < w.length - 2 && w[i + 1][0] < bb) i++; + const [b0, m0, w0] = w[i]; + const [b1, m1, w1] = w[i + 1]; + const t = b1 === b0 ? 0 : (bb - b0) / (b1 - b0); + const mitte = m0 + (m1 - m0) * t; + const breite = w0 + (w1 - w0) * t; + return {k: w[0][2] / breite, mitte, ziel: w[0][1], fussY: m.fussY, bildBreite: m.bildBreite}; +}; + +// Punkt (in Clip-Pixeln) so verschieben, wie die Korrektur das Bild verschiebt +export const korrigiere = (kor: Korrektur | null, x: number, y: number) => + kor ? {x: kor.ziel + kor.k * (x - kor.mitte), y: kor.fussY + kor.k * (y - kor.fussY)} : {x, y}; diff --git a/remotion/src/baukasten/figur.tsx b/remotion/src/baukasten/figur.tsx index e12eadf..4831abb 100644 --- a/remotion/src/baukasten/figur.tsx +++ b/remotion/src/baukasten/figur.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {Freeze, Img, OffthreadVideo, getStaticFiles, staticFile, useCurrentFrame, useVideoConfig} from 'remotion'; +import {clipKorrektur} from './clipkorrektur'; import {CLIPS, FIGUREN, POSEN} from './posen'; // Figuren aus der Bibliothek (freigestellte Standbilder und Clips). @@ -149,9 +150,15 @@ export const ClipFigur: React.FC = ({name, start, hoehe, tempo = 1, s let t = Math.max(0, (frame - start) * tempo); t = schleife ? t % (laenge - 1) : Math.min(t, laenge - 1); if (rueckwaerts) t = laenge - 1 - t; + // Zoom/Schwenk der Higgsfield-Kamera im Clip zurückrechnen (Beine bleiben am Platz) + const kor = clipKorrektur(name, (t * 24) / fps); + const px = kor ? c.b / kor.bildBreite : 1; + const korStil: React.CSSProperties = kor + ? {transformOrigin: `${kor.mitte * px}px ${kor.fussY * px}px`, transform: `translateX(${(kor.ziel - kor.mitte) * px}px) scale(${kor.k})`} + : {}; return ( -
+
diff --git a/remotion/src/baukasten/helfer.tsx b/remotion/src/baukasten/helfer.tsx index 14efb1f..9a5a543 100644 --- a/remotion/src/baukasten/helfer.tsx +++ b/remotion/src/baukasten/helfer.tsx @@ -1,3 +1,4 @@ +import {clipKorrektur, korrigiere} from './clipkorrektur'; import React from 'react'; import {interpolate, useCurrentFrame} from 'remotion'; import {kurve} from '../bewegung'; @@ -88,6 +89,18 @@ export const luluMaulC9 = (clipBild: number) => { const [b0, x0, y0] = C9_NASE[i]; const [b1, x1, y1] = C9_NASE[i + 1]; const t = b1 === b0 ? 0 : (b - b0) / (b1 - b0); - // Maul knapp über der Unterkante der Nase - return {cx: C9.x + (x0 + (x1 - x0) * t) * C9.s, cy: C9.y + (y0 + (y1 - y0) * t - 6) * C9.s}; + // Maul knapp über der Unterkante der Nase; dieselbe Zoom-Korrektur wie das Clipbild + const p = korrigiere(clipKorrektur('lulu_C9_hals-runter', b), x0 + (x1 - x0) * t, y0 + (y1 - y0) * t - 6); + return {cx: C9.x + p.x * C9.s, cy: C9.y + p.y * C9.s}; +}; + +// Klatschen im Lied: Noa klatscht auf jeden zweiten Schlag (Schlag 1, 3, 5 … ab Liedbeginn). +// Liefert Posenwechsel für die Zeit von `von` bis `bis` (Hände zusammen kurz um den Schlag). +export const klatschFolge = (von: number, bis: number, takt: number, basis = 'noa_A3_lachen'): [number, string][] => { + const folge: [number, string][] = []; + for (let t = takt; t < bis; t += 2 * takt) { + if (t - 3 < von) continue; + folge.push([t - 3, 'noa_A_klatschen'], [t + 4, basis]); + } + return folge; }; diff --git a/remotion/src/baukasten/posen.ts b/remotion/src/baukasten/posen.ts index 139d01c..17ab755 100644 --- a/remotion/src/baukasten/posen.ts +++ b/remotion/src/baukasten/posen.ts @@ -513,6 +513,20 @@ export const POSEN: Record = { 701 ] }, + "noa_A_klatschen": { + "figur": "noa", + "datei": "bibliothek/figuren/noa_A_klatschen.webp", + "x": 339, + "y": 156, + "b": 715, + "h": 551, + "box": [ + 543, + 165, + 807, + 700 + ] + }, "noa_A_arme-hoch": { "figur": "noa", "datei": "bibliothek/figuren/noa_A_arme-hoch.webp", diff --git a/remotion/src/folge01/lied.tsx b/remotion/src/folge01/lied.tsx index 4758b16..5016dad 100644 --- a/remotion/src/folge01/lied.tsx +++ b/remotion/src/folge01/lied.tsx @@ -1,8 +1,8 @@ import React from 'react'; import {random, useCurrentFrame} from 'remotion'; import {Figur, Kamera} from '../baukasten/figur'; -import {Blende, Vignette} from '../baukasten/helfer'; -import {Blume, Klatschen, Laubhaufen} from '../baukasten/natur'; +import {Blende, Vignette, klatschFolge} from '../baukasten/helfer'; +import {Blume, Laubhaufen} from '../baukasten/natur'; import {BODEN, Ort} from '../baukasten/orte'; import {SCHRIFT, Untertitel} from '../baukasten/untertitel'; import {TEXTE, plane} from './plan'; @@ -57,10 +57,13 @@ export const Lied: React.FC = () => { [z.s4.start, 'pino_A_hinterbeine'], [z.w1.start, 'pino_A3_lachen'], ]; + // Noa klatscht im Takt (jeder zweite Schlag), bei „zusammen kriegen wir alles hin“ Arme hoch const noaPose: [number, string][] = [ [0, 'noa_A3_lachen'], + ...klatschFolge(z.r1.start, z.r4.start, TAKT), [z.r4.start, 'noa_A_arme-hoch'], [z.s1.start, 'noa_A3_lachen'], + ...klatschFolge(z.s1.start, z.w4.start, TAKT), [z.w4.start, 'noa_A_arme-hoch'], ]; return ( @@ -69,7 +72,7 @@ export const Lied: React.FC = () => { - + {f >= z.s4.start && f < z.w1.start && } @@ -85,8 +88,6 @@ export const Lied: React.FC = () => {
♫ {TEXTE.lied} ♫
- {/* Klatsch-Hände: auf jeden zweiten Schlag zum Mitklatschen */} - diff --git a/remotion/src/folge02/lied.tsx b/remotion/src/folge02/lied.tsx index ebca6fa..3cbd215 100644 --- a/remotion/src/folge02/lied.tsx +++ b/remotion/src/folge02/lied.tsx @@ -1,8 +1,7 @@ import React from 'react'; import {random, useCurrentFrame} from 'remotion'; import {Figur, Kamera} from '../baukasten/figur'; -import {Blende, Vignette} from '../baukasten/helfer'; -import {Klatschen} from '../baukasten/natur'; +import {Blende, Vignette, klatschFolge} from '../baukasten/helfer'; import {BODEN, Ort} from '../baukasten/orte'; import {SCHRIFT, Untertitel} from '../baukasten/untertitel'; import {TEXTE, plane} from './plan'; @@ -43,11 +42,14 @@ export const Lied: React.FC = () => { @@ -59,8 +61,6 @@ export const Lied: React.FC = () => {
♫ {TEXTE.lied} ♫
- {/* Klatsch-Hände: auf jeden zweiten Schlag zum Mitklatschen */} - diff --git a/remotion/src/folge02/szene4.tsx b/remotion/src/folge02/szene4.tsx index 1396fb1..b0305f7 100644 --- a/remotion/src/folge02/szene4.tsx +++ b/remotion/src/folge02/szene4.tsx @@ -83,8 +83,8 @@ export const Szene4: React.FC = () => { const hockeStauch = hockZeiten.reduce((s, [a, b]) => s * (1 - 0.05 * bump(a)) * (1 + 0.03 * bump(b)), 1); // beim Hinlegen jedes Blatts kurz nach vorn beugen const beugen = zaehlen.reduce((s, c) => s + Math.max(0, Math.sin(Math.min(1, Math.max(0, (f - c + 8) / 16)) * Math.PI)), 0); - // beim Nestbau schieben (vor und zurück) - const schieben = f > zurueck && f < z.bild4.ende ? Math.sin((f - zurueck) * 0.22) : 0; + // beim Nestbau: früher seitliches Hin- und Herrutschen (wirkte unruhig) – jetzt ruhig, nur die Blätter bewegen sich + // Pino const pinoGehtVon = zumStapel + 40; @@ -202,7 +202,7 @@ export const Szene4: React.FC = () => { kam.y += ruck(f, niesen, 9, 22); const lulu = ; - const luluBeimNest = f >= zurueck && ; + const luluBeimNest = f >= zurueck && ; return ( <> @@ -225,10 +225,10 @@ export const Szene4: React.FC = () => { ) : ( )}