Lied 4: Noa tippt mit den Higgsfield-Posen auf die Körperteile statt Leuchtkreisen; Karte zeigt die Pose klein
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1a6e76e346
commit
a705b32b1e
2 changed files with 36 additions and 37 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {AbsoluteFill, Audio, Sequence, interpolate, staticFile, useCurrentFrame} from 'remotion';
|
import {AbsoluteFill, Audio, Sequence, interpolate, staticFile, useCurrentFrame} from 'remotion';
|
||||||
import {INTRO_OPTIONEN, Intro, OUTRO, Outro} from '../../baukasten/buch';
|
import {INTRO_OPTIONEN, Intro, OUTRO, Outro} from '../../baukasten/buch';
|
||||||
import {Figur, Kamera} from '../../baukasten/figur';
|
import {Figur, Kamera, type Posenfolge} from '../../baukasten/figur';
|
||||||
import {Blende, Vignette, kamFahrt} from '../../baukasten/helfer';
|
import {Blende, Vignette, kamFahrt} from '../../baukasten/helfer';
|
||||||
import {BODEN, Ort} from '../../baukasten/orte';
|
import {BODEN, Ort} from '../../baukasten/orte';
|
||||||
import {SCHRIFT} from '../../baukasten/untertitel';
|
import {SCHRIFT} from '../../baukasten/untertitel';
|
||||||
|
|
@ -21,7 +21,8 @@ export type Bild = {
|
||||||
kamera?: {x: number; y: number; z: number};
|
kamera?: {x: number; y: number; z: number};
|
||||||
leuchten?: (Punkt & {farbe: string; r?: number; ab?: number})[];
|
leuchten?: (Punkt & {farbe: string; r?: number; ab?: number})[];
|
||||||
karte?: React.ReactNode;
|
karte?: React.ReactNode;
|
||||||
noa?: string;
|
noa?: string | Posenfolge; // Posenfolge mit Frames ab Liedbeginn (z. B. Pose je Wort)
|
||||||
|
tipps?: number[]; // Sekunden, an denen Noa kurz im Takt nachfedert (Tippen)
|
||||||
pino?: string;
|
pino?: string;
|
||||||
lulu?: string;
|
lulu?: string;
|
||||||
luluStreckt?: boolean;
|
luluStreckt?: boolean;
|
||||||
|
|
@ -152,7 +153,7 @@ const Garten: React.FC<{daten: LiedDaten; szene: (i: number, z: Zeile) => Bild}>
|
||||||
<Kamera {...kam}>
|
<Kamera {...kam}>
|
||||||
<Ort name="garten" />
|
<Ort name="garten" />
|
||||||
<Figur pose={b.lulu ?? 'lulu_A3_lachen'} x={PLATZ.lulu} y={BODEN} drehung={schwank(0.5, 2)} stauchY={strecken} blende={8} />
|
<Figur pose={b.lulu ?? 'lulu_A3_lachen'} x={PLATZ.lulu} y={BODEN} drehung={schwank(0.5, 2)} stauchY={strecken} blende={8} />
|
||||||
<Figur pose={b.noa ?? 'noa_A3_lachen'} x={PLATZ.noa} y={BODEN + wipp(0.3, 10)} drehung={schwank(0, 2.5)} blende={6} />
|
<Figur pose={b.noa ?? 'noa_A3_lachen'} x={PLATZ.noa} y={BODEN + wipp(0.3, 10)} drehung={schwank(0, 2.5)} stauchY={1 - (b.tipps ?? []).reduce((m, tp) => m + 0.03 * Math.max(0, 1 - Math.abs(f - s(tp)) / 4), 0)} blende={4} />
|
||||||
<Figur pose={b.pino ?? 'pino_A3_lachen'} x={PLATZ.pino} y={BODEN + wipp(0, 14)} drehung={schwank(1, 3)} blende={8} />
|
<Figur pose={b.pino ?? 'pino_A3_lachen'} x={PLATZ.pino} y={BODEN + wipp(0, 14)} drehung={schwank(1, 3)} blende={8} />
|
||||||
{bilder.map((bb, k) =>
|
{bilder.map((bb, k) =>
|
||||||
(bb.leuchten ?? []).map((l, j) => <Leuchtkreis key={`${k}-${j}`} {...l} ab={s(l.ab ?? zeilen[k].start - 0.1)} bis={zeilenEnde(k)} />),
|
(bb.leuchten ?? []).map((l, j) => <Leuchtkreis key={`${k}-${j}`} {...l} ab={s(l.ab ?? zeilen[k].start - 0.1)} bis={zeilenEnde(k)} />),
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,14 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Figur} from '../../baukasten/figur';
|
import {Figur, type Posenfolge} from '../../baukasten/figur';
|
||||||
import {punkt} from '../../baukasten/helfer';
|
|
||||||
import {BODEN} from '../../baukasten/orte';
|
|
||||||
import {SPRACHE} from '../../baukasten/sprache';
|
import {SPRACHE} from '../../baukasten/sprache';
|
||||||
import {type Bild, EinfachesLied, type LiedDaten, PLATZ, type Punkt, type Zeile, wortZeit} from '../einfach/EinfachesLied';
|
import {type Bild, EinfachesLied, type LiedDaten, PLATZ, type Zeile, wortZeit} from '../einfach/EinfachesLied';
|
||||||
import {DE, ES, FR} from '../einfach/daten/lied04';
|
import {DE, ES, FR} from '../einfach/daten/lied04';
|
||||||
|
|
||||||
// Liedvideo „Wer hat den längsten Hals?“ (Pinos Liederbuch, Lied 4: Körperteile) in DE/ES/FR.
|
// Liedvideo „Wer hat den längsten Hals?“ (Pinos Liederbuch, Lied 4: Körperteile) in DE/ES/FR.
|
||||||
// Refrain: Lulu streckt den Hals. Strophen (Ohren, Hände, Beine, Arme): nah bei Noa, die Stellen leuchten,
|
// Refrain: Lulu streckt den Hals, Noa zeigt auf ihn. Strophen (Ohren, Hände, Beine, Arme): nah bei Noa, sie tippt
|
||||||
// die Karte zeigt eine kleine Noa mit der leuchtenden Stelle. Turbo-Teil (3×): jedes genannte Körperteil
|
// auf das Körperteil (Higgsfield-Posen noa_B_tippt-*), die Karte zeigt die Pose klein. Turbo-Teil (3×): zu jedem
|
||||||
// leuchtet zu seinem Wort, im dritten Durchgang langsam. Drehbuch drehbuecher/lieder/lied04-koerperteile/.
|
// gesungenen Körperteil die passende Pose, im dritten Durchgang langsam. Keine Leuchtkreise (Nutzerin 09.11.). Drehbuch drehbuecher/lieder/lied04-koerperteile/.
|
||||||
|
|
||||||
// Körperteile in noa_A3_lachen (Leinwand = Bild + 339/156, gemessen)
|
|
||||||
const TEIL: Record<string, [number, number][]> = {
|
|
||||||
ohren: [[607, 306], [809, 306]],
|
|
||||||
haende: [[629, 576], [809, 551]],
|
|
||||||
beine: [[684, 616], [744, 616]],
|
|
||||||
arme: [[629, 476], [799, 466]],
|
|
||||||
nase: [[734, 296]],
|
|
||||||
bauch: [[709, 486]],
|
|
||||||
hals: [[709, 361]],
|
|
||||||
fuesse: [[684, 691], [754, 691]],
|
|
||||||
zeh: [[759, 721]],
|
|
||||||
};
|
|
||||||
const FARBE = '#d9482b'; // kräftiges Rot-Orange: hebt sich von Jacke und Gesicht ab
|
|
||||||
const WOERTER: [RegExp, string][] = [
|
const WOERTER: [RegExp, string][] = [
|
||||||
[/^(ohren|orejas|oreilles)/i, 'ohren'],
|
[/^(ohren|orejas|oreilles)/i, 'ohren'],
|
||||||
[/^(h[aä]nde|manos|mains)/i, 'haende'],
|
[/^(h[aä]nde|manos|mains)/i, 'haende'],
|
||||||
|
|
@ -36,16 +21,25 @@ const WOERTER: [RegExp, string][] = [
|
||||||
[/^(zeh|deditos|orteils)/i, 'zeh'],
|
[/^(zeh|deditos|orteils)/i, 'zeh'],
|
||||||
];
|
];
|
||||||
const teilVon = (wort: string) => WOERTER.find(([re]) => re.test(wort.replace(/[^\p{L}]/gu, '')))?.[1];
|
const teilVon = (wort: string) => WOERTER.find(([re]) => re.test(wort.replace(/[^\p{L}]/gu, '')))?.[1];
|
||||||
const anNoa = (teil: string): Punkt[] => TEIL[teil].map(([cx, cy]) => punkt('noa', PLATZ.noa, BODEN, cx, cy));
|
|
||||||
|
|
||||||
// Karte: kleine Noa, die genannte Stelle leuchtet
|
// Higgsfield-Posen (09.11.2026): Noa tippt wirklich auf das Körperteil. Hände = klatschen, Hals = sie zeigt
|
||||||
|
// auf Lulus langen Hals, Zeh = wie Füße.
|
||||||
|
const POSE: Record<string, string> = {
|
||||||
|
ohren: 'noa_B_tippt-ohren',
|
||||||
|
nase: 'noa_B_tippt-nase',
|
||||||
|
bauch: 'noa_B_tippt-bauch',
|
||||||
|
beine: 'noa_B_tippt-beine',
|
||||||
|
arme: 'noa_B_tippt-arme',
|
||||||
|
fuesse: 'noa_B_tippt-fuesse',
|
||||||
|
zeh: 'noa_B_tippt-fuesse',
|
||||||
|
haende: 'noa_A_klatschen',
|
||||||
|
hals: 'noa_B_zeigen',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Karte: kleine Noa in derselben Pose (Bild statt Text)
|
||||||
const NoaKarte: React.FC<{teil: string}> = ({teil}) => (
|
const NoaKarte: React.FC<{teil: string}> = ({teil}) => (
|
||||||
<div style={{position: 'relative', width: 204, height: 204}}>
|
<div style={{position: 'relative', width: 204, height: 204}}>
|
||||||
<Figur pose="noa_A3_lachen" x={102} y={198} hoehe={180} schatten={false} />
|
<Figur pose={POSE[teil]} x={102} y={198} hoehe={180} schatten={false} />
|
||||||
{TEIL[teil].map(([cx, cy], k) => {
|
|
||||||
const p = punkt('noa', 102, 198, cx, cy, 180);
|
|
||||||
return <div key={k} style={{position: 'absolute', left: p.x - 16, top: p.y - 16, width: 32, height: 32, borderRadius: '50%', border: `5px solid ${FARBE}`, background: `${FARBE}66`}} />;
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -64,28 +58,32 @@ const lage = (i: number) => {
|
||||||
return i - k;
|
return i - k;
|
||||||
};
|
};
|
||||||
const NOA_NAH = {x: PLATZ.noa, y: 765, z: 1.75}; // ganze Noa über dem Mitlesetext
|
const NOA_NAH = {x: PLATZ.noa, y: 765, z: 1.75}; // ganze Noa über dem Mitlesetext
|
||||||
const LULU_HALS = punkt('lulu', PLATZ.lulu, BODEN, 322 + 320, 23 + 230);
|
|
||||||
|
const istTipp = (w: string) => /^(tipp|toc)/i.test(w.replace(/[^\p{L}]/gu, ''));
|
||||||
|
|
||||||
const szene = (i: number, z: Zeile): Bild => {
|
const szene = (i: number, z: Zeile): Bild => {
|
||||||
const a = z.abschnitt.toLowerCase();
|
const a = z.abschnitt.toLowerCase();
|
||||||
const n = lage(i);
|
const n = lage(i);
|
||||||
const woerter = z.zeile.split(' ').map((w) => w.split('@')[0]);
|
const woerter = z.zeile.split(' ').map((w) => w.split('@')[0]);
|
||||||
|
const tipps = woerter.flatMap((w, k) => (istTipp(w) ? [wortZeit(z, k)] : []));
|
||||||
if (a.startsWith('chorus')) {
|
if (a.startsWith('chorus')) {
|
||||||
if (n === 0) return {kamera: {x: PLATZ.lulu - 60, y: 420, z: 1.5}, luluStreckt: true, lulu: 'lulu_A3_lachen'};
|
if (n === 0) return {kamera: {x: PLATZ.lulu - 60, y: 420, z: 1.5}, luluStreckt: true, lulu: 'lulu_A3_lachen'};
|
||||||
if (n === 1) return {leuchten: [{...LULU_HALS, farbe: FARBE, r: 90}]};
|
if (n === 1) return {noa: 'noa_B_zeigen', luluStreckt: true, tipps};
|
||||||
return {};
|
return {tipps};
|
||||||
}
|
}
|
||||||
if (a.startsWith('verse')) {
|
if (a.startsWith('verse')) {
|
||||||
const teil = woerter.map(teilVon).find(Boolean) ?? 'ohren';
|
const teil = woerter.map(teilVon).find(Boolean) ?? 'ohren';
|
||||||
if (n === 0) return {kamera: NOA_NAH, noa: i % 2 ? 'noa_A5_blick-links' : 'noa_A6_blick-rechts', ruhig: true, karte: <NoaKarte teil={teil} />};
|
if (n === 0) return {kamera: NOA_NAH, noa: i % 2 ? 'noa_A5_blick-links' : 'noa_A6_blick-rechts', ruhig: true, karte: <NoaKarte teil={teil} />};
|
||||||
return {kamera: NOA_NAH, leuchten: anNoa(teil).map((p) => ({...p, farbe: FARBE, r: 46})), karte: <NoaKarte teil={teil} />};
|
return {kamera: NOA_NAH, noa: POSE[teil], tipps, karte: <NoaKarte teil={teil} />};
|
||||||
}
|
}
|
||||||
if (a.startsWith('bridge')) {
|
if (a.startsWith('bridge')) {
|
||||||
const leuchten = woerter.flatMap((w, k) => {
|
// zu jedem gesungenen Körperteil die passende Pose (knapp vor dem Wort, damit sie beim Wort steht)
|
||||||
|
const folge: Posenfolge = [[0, 'noa_A3_lachen']];
|
||||||
|
woerter.forEach((w, k) => {
|
||||||
const teil = teilVon(w);
|
const teil = teilVon(w);
|
||||||
return teil ? anNoa(teil).map((p) => ({...p, farbe: FARBE, r: 40, ab: wortZeit(z, k) - 0.05})) : [];
|
if (teil) folge.push([Math.round(wortZeit(z, k) * 30) - 4, POSE[teil]]);
|
||||||
});
|
});
|
||||||
return {kamera: {x: PLATZ.noa - 40, y: 720, z: 1.5}, leuchten, ruhig: a.includes('slow')};
|
return {kamera: {x: PLATZ.noa - 40, y: 720, z: 1.5}, noa: folge.length > 1 ? folge : 'noa_A3_lachen', luluStreckt: woerter.some((w) => teilVon(w) === 'hals'), ruhig: a.includes('slow')};
|
||||||
}
|
}
|
||||||
return {ruhig: true};
|
return {ruhig: true};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue