Liedvideo Lied 2 „Rot wie Noas Stiefel“ (DE/ES/FR) mit neuer Vorlage für einfache Liedvideos

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lara 2026-11-08 18:54:47 +00:00
parent 827ea74767
commit a4809d4089
4 changed files with 386 additions and 0 deletions

View file

@ -22,6 +22,7 @@ import {SHORTS_F08} from './shorts/folge08';
import {SZENEN as SZENEN_F01} from './folge01/Folge01'; import {SZENEN as SZENEN_F01} from './folge01/Folge01';
import {SZENEN as SZENEN_F08} from './folge08/Folge08'; import {SZENEN as SZENEN_F08} from './folge08/Folge08';
import {LIED01_DAUER, Lied01} from './lieder/lied01/Lied01'; import {LIED01_DAUER, Lied01} from './lieder/lied01/Lied01';
import {LIED02_DAUER, Lied02} from './lieder/lied02/Lied02';
import {LIED03_DAUER, Lied03} from './lieder/lied03/Lied03'; import {LIED03_DAUER, Lied03} from './lieder/lied03/Lied03';
const BILD = {fps: 30, width: 1920, height: 1080} as const; const BILD = {fps: 30, width: 1920, height: 1080} as const;
@ -29,6 +30,7 @@ const BILD = {fps: 30, width: 1920, height: 1080} as const;
export const Root: React.FC = () => ( export const Root: React.FC = () => (
<> <>
<Composition id="Lied01" component={Lied01} durationInFrames={LIED01_DAUER} {...BILD} /> <Composition id="Lied01" component={Lied01} durationInFrames={LIED01_DAUER} {...BILD} />
<Composition id="Lied02" component={Lied02} durationInFrames={LIED02_DAUER} {...BILD} />
<Composition id="Lied03" component={Lied03} durationInFrames={LIED03_DAUER} {...BILD} /> <Composition id="Lied03" component={Lied03} durationInFrames={LIED03_DAUER} {...BILD} />
<Composition id="GangTest" component={GangTest} durationInFrames={GANGTEST_DAUER} {...BILD} /> <Composition id="GangTest" component={GangTest} durationInFrames={GANGTEST_DAUER} {...BILD} />
<Composition id="LaterneTest" component={LaterneTest} durationInFrames={420} {...BILD} /> <Composition id="LaterneTest" component={LaterneTest} durationInFrames={420} {...BILD} />

View file

@ -0,0 +1,190 @@
import React from 'react';
import {AbsoluteFill, Audio, Sequence, interpolate, staticFile, useCurrentFrame} from 'remotion';
import {INTRO_OPTIONEN, Intro, OUTRO, Outro} from '../../baukasten/buch';
import {Figur, Kamera} from '../../baukasten/figur';
import {Blende, Vignette, kamFahrt} from '../../baukasten/helfer';
import {BODEN, Ort} from '../../baukasten/orte';
import {SCHRIFT} from '../../baukasten/untertitel';
import {plane} from '../../baukasten/zeit';
import {Karaoke, type LiedZeile, zerlege} from '../karaoke';
import {Noten} from '../lied03/Lied03';
// Vorlage für einfache Liedvideos (Pinos Liederbuch, Lied 2 und 4; Team-Check 08.11.2026):
// die drei stehen nebeneinander im Garten, wippen nur im Refrain, in den Strophen ruhig. Je Liedzeile
// höchstens ein Bild (Kamera-Ziel, weicher Leuchtkreis, Mitmach-Karte mit Bild), kein Blinken, am Ende
// das Bilderbuch und kein Hinweis auf das nächste Video. Was in welcher Zeile passiert, bestimmt das Lied
// über `szene(i, zeile)`; die Zeiten kommen aus der Suno-Aufnahme (daten/*.ts).
export type Zeile = {abschnitt: string; zeile: LiedZeile; start: number};
export type Punkt = {x: number; y: number};
export type Bild = {
kamera?: {x: number; y: number; z: number};
leuchten?: (Punkt & {farbe: string; r?: number; ab?: number})[];
karte?: React.ReactNode;
noa?: string;
pino?: string;
lulu?: string;
luluStreckt?: boolean;
ruhig?: boolean; // keine Bewegung (Mitmach-Takt)
reihe?: {farbe: string; ab: number}[]; // Farbkleckse, die oben nacheinander erscheinen (bleiben bis Ende der nächsten Zeile)
};
export type LiedDaten = {datei: string; dauer: number; zeilen: Zeile[]; titel: string; zeileTitel: string};
const FPS = 30;
const s = (t: number) => Math.round(t * FPS);
export const PLATZ = {pino: 760, noa: 980, lulu: 1300};
const WEIT = {x: 1030, y: 640, z: 1.2};
export const wortZeit = (z: Zeile, wort: number) => zerlege(z.zeile)[wort]?.t ?? z.start;
// Weicher Leuchtkreis: blendet ein, atmet ganz langsam (kein Blinken)
const Leuchtkreis: React.FC<Punkt & {farbe: string; r?: number; ab: number; bis: number}> = ({x, y, farbe, r = 70, ab, bis}) => {
const f = useCurrentFrame();
const an = interpolate(f, [ab, ab + 10, bis - 8, bis], [0, 1, 1, 0], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
if (an <= 0) return null;
const atem = 0.85 + 0.15 * Math.sin(f * 0.06);
return (
<div
style={{
position: 'absolute',
left: x - r,
top: y - r,
width: 2 * r,
height: 2 * r,
borderRadius: '50%',
background: `radial-gradient(closest-side, ${farbe}88, ${farbe}44 55%, ${farbe}00)`,
border: `5px solid ${farbe}`,
opacity: an * atem,
transform: `scale(${0.9 + 0.1 * an})`,
}}
/>
);
};
// Mitmach-Karte oben links (Bild statt Text: die Kinder lesen noch nicht)
const Karte: React.FC<{ab: number; bis: number; children: React.ReactNode}> = ({ab, bis, children}) => {
const f = useCurrentFrame();
const an = interpolate(f, [ab, ab + 10, bis - 8, bis], [0, 1, 1, 0], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
if (an <= 0) return null;
return (
<div style={{position: 'absolute', top: 44, left: 56, width: 220, height: 220, borderRadius: 40, background: 'rgba(251,244,228,0.95)', border: '8px solid #3f5a8a', boxShadow: '0 8px 26px rgba(60,40,20,0.22)', overflow: 'hidden', opacity: an, transform: `scale(${0.85 + 0.15 * an})`, transformOrigin: '0 0'}}>
{children}
</div>
);
};
export const Farbklecks: React.FC<{farbe: string}> = ({farbe}) => (
<svg viewBox="-110 -110 220 220" width={204} height={204}>
<path d="M -60 -52 C -20 -86, 46 -80, 66 -40 C 88 0, 70 52, 24 66 C -20 80, -74 60, -78 14 C -82 -18, -76 -40, -60 -52 Z" fill={farbe} stroke="rgba(60,40,20,0.25)" strokeWidth={4} />
</svg>
);
export const EinfachesLied: React.FC<{daten: LiedDaten; szene: (i: number, z: Zeile) => Bild}> = ({daten, szene}) => {
const {zeilen, dauer, datei} = daten;
const intro = s(zeilen[0].start - 0.3);
const introPlan = plane([{id: 'buch', dauer: 2.2}, {id: 't1', dauer: 3}, {id: 't2', dauer: Math.max(0.5, zeilen[0].start - 0.3 - 2.2 - 3 - INTRO_OPTIONEN.nachlauf)}], INTRO_OPTIONEN);
const outroStart = s(dauer) - OUTRO.dauer;
return (
<AbsoluteFill style={{background: '#f4ead8'}}>
<Sequence durationInFrames={outroStart + 12}>
<Garten daten={daten} szene={szene} />
</Sequence>
<Sequence from={intro} durationInFrames={outroStart - intro + 12}>
<Blende dauer={outroStart - intro + 12} rein={10} raus={12} />
</Sequence>
<Sequence durationInFrames={intro}>
<AbsoluteFill style={{background: '#f4ead8'}}>
<Intro plan={introPlan} titel={daten.titel} posen={['pino_A3_lachen', 'noa_A3_lachen', 'lulu_A3_lachen']} />
</AbsoluteFill>
</Sequence>
<Sequence from={outroStart}>
<Outro posen={['pino_A3_lachen', 'noa_A_arm-hoch-winken', 'lulu_A3_lachen']} />
</Sequence>
<Karaoke zeilen={zeilen.map((z) => z.zeile)} />
<Audio src={staticFile(datei)} />
</AbsoluteFill>
);
};
const Garten: React.FC<{daten: LiedDaten; szene: (i: number, z: Zeile) => Bild}> = ({daten, szene}) => {
const f = useCurrentFrame();
const {zeilen} = daten;
const t = f / FPS;
// aktuelle Zeile: die letzte, die schon begonnen hat (bis zur nächsten)
let i = 0;
zeilen.forEach((z, k) => {
if (z.start - 0.25 <= t) i = k;
});
const bilder = zeilen.map((z, k) => szene(k, z));
const b = bilder[i];
const imRefrain = zeilen[i].abschnitt.toLowerCase().startsWith('chorus') && !b.ruhig;
// Kamera: jedes Zeilen-Bild gilt ab Zeilenbeginn, 0,7 s Fahrt dorthin
const punkte: [number, number, number, number][] = [[0, WEIT.x, WEIT.y, WEIT.z]];
zeilen.forEach((z, k) => {
const ziel = bilder[k].kamera ?? WEIT;
const fr = s(z.start - 0.25);
const vorher = punkte[punkte.length - 1];
punkte.push([Math.max(vorher[0] + 1, fr - 21), vorher[1], vorher[2], vorher[3]], [Math.max(vorher[0] + 2, fr), ziel.x, ziel.y, ziel.z]);
});
const kam = kamFahrt(f, punkte);
const takt = (f / FPS) * (96 / 60) * Math.PI;
const wipp = (phase: number, h: number) => (imRefrain ? -Math.abs(Math.sin(takt + phase)) * h : 0);
const schwank = (phase: number, g: number) => (imRefrain ? Math.sin(takt / 2 + phase) * g : 0);
const zeilenEnde = (k: number) => s(zeilen[k + 1]?.start ?? zeilen[k].start + 4) - 6;
const strecken = b.luluStreckt ? interpolate(f, [s(zeilen[i].start - 0.2), s(zeilen[i].start + 0.4)], [1, 1.08], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'}) : 1;
return (
<>
<Kamera {...kam}>
<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.noa ?? 'noa_A3_lachen'} x={PLATZ.noa} y={BODEN + wipp(0.3, 10)} drehung={schwank(0, 2.5)} blende={6} />
<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) =>
(bb.leuchten ?? []).map((l, j) => <Leuchtkreis key={`${k}-${j}`} {...l} ab={s(l.ab ?? zeilen[k].start - 0.1)} bis={zeilenEnde(k)} />),
)}
</Kamera>
{imRefrain && <Noten />}
{bilder.map((bb, k) =>
bb.karte ? (
<Karte key={k} ab={s(zeilen[k].start - 0.1)} bis={zeilenEnde(k)}>
{bb.karte}
</Karte>
) : null,
)}
{bilder.map((bb, k) =>
bb.reihe ? (
<div key={`r${k}`} style={{position: 'absolute', top: 120, left: 0, right: 0, display: 'flex', justifyContent: 'center', gap: 18}}>
{bb.reihe.map((r, j) => {
const an = interpolate(f, [s(r.ab), s(r.ab) + 8, zeilenEnde(k + 1) - 8, zeilenEnde(k + 1)], [0, 1, 1, 0], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'});
return (
<div key={j} style={{width: 120, height: 120, opacity: an, transform: `scale(${0.6 + 0.4 * an})`}}>
<div style={{transform: 'scale(0.58)', transformOrigin: '0 0'}}>
<Farbklecks farbe={r.farbe} />
</div>
</div>
);
})}
</div>
) : null,
)}
{/* Liedtitel im ersten Refrain */}
<div
style={{
position: 'absolute',
top: 46,
left: 0,
right: 0,
textAlign: 'center',
fontFamily: SCHRIFT,
fontWeight: 600,
fontSize: 44,
color: '#8a6a4a',
opacity: interpolate(f, [s(zeilen[0].start), s(zeilen[0].start + 0.6), s(zeilen[3].start), s(zeilen[3].start + 0.6)], [0, 1, 1, 0], {extrapolateLeft: 'clamp', extrapolateRight: 'clamp'}),
}}
>
♫ {daten.zeileTitel} ♫
</div>
<Vignette />
</>
);
};

View file

@ -0,0 +1,115 @@
import type {Zeile} from '../EinfachesLied';
// Zeiten der Suno-Aufnahmen (lied02-<sprache>.mp3), am 08.11.2026 mit faster-whisper (small) gemessen, +0,43 s,
// Text aus drehbuecher/lieder/lied02-farben/<sprache>.md (Suno-Text). Erzeugt, nicht von Hand ändern.
// de: 143 von 145 Wörtern direkt erkannt, Rest geschätzt
export const DE: {dauer: number; zeilen: Zeile[]} = {
dauer: 125.99,
zeilen: [
{abschnitt: "Chorus", zeile: "Zeig,@10.27 zeig,@11.03 zeig@11.59 mal@11.87 hin!@12.11", start: 10.27},
{abschnitt: "Chorus", zeile: "Such@12.87 die@13.03 Farben,@13.33 schau@14.11 gut@14.31 hin!@14.61", start: 12.87},
{abschnitt: "Chorus", zeile: "Rot@15.23 und@15.53 Gelb@15.81 und@16.25 Blau@16.41 und@16.85 Grün,@17.03", start: 15.23},
{abschnitt: "Chorus", zeile: "alle@17.77 Farben_\u0001@18.05 das@19.01 ist@19.17 schön!@19.41", start: 17.77},
{abschnitt: "Verse 1", zeile: "Wer@20.91 trägt@21.63 Rot?@21.95 Wer@22.61 trägt@22.81 Rot?@23.17", start: 20.91},
{abschnitt: "Verse 1", zeile: "Rot@23.85 wie@24.71 Noas@24.97 Stiefel!@25.45", start: 23.85},
{abschnitt: "Verse 1", zeile: "Rot@26.59 wie@27.27 Noas@27.93 Stiefel!@29.01", start: 26.59},
{abschnitt: "Verse 2", zeile: "Wer@30.67 trägt@31.41 Gelb?@31.77 Wer@32.47 trägt@32.61 Gelb?@32.97", start: 30.67},
{abschnitt: "Verse 2", zeile: "Gelb@34.03 wie@34.59 Noas@34.77 Jacke!@35.35", start: 34.03},
{abschnitt: "Verse 2", zeile: "Gelb@36.41 wie@37.25 Noas@37.81 Jacke!@38.85", start: 36.41},
{abschnitt: "Chorus", zeile: "Zeig,@41.49 zeig,@42.23 zeig@42.83 mal@43.07 hin!@43.31", start: 41.49},
{abschnitt: "Chorus", zeile: "Such@44.09 die@44.25 Farben,@44.51 schau@45.33 gut@45.53 hin!@45.83", start: 44.09},
{abschnitt: "Chorus", zeile: "Rot@46.45 und@46.77 Gelb@47.01 und@47.45 Blau@47.61 und@48.05 Grün,@48.23", start: 46.45},
{abschnitt: "Chorus", zeile: "alle@48.99 Farben_\u0001@49.25 das@50.21 ist@50.33 schön!@50.61", start: 48.99},
{abschnitt: "Verse 3", zeile: "Wer@52.23 trägt@52.79 Blau?@53.13 Wer@53.73 trägt@54.01 Blau?@54.35", start: 52.23},
{abschnitt: "Verse 3", zeile: "Blau@55.25 wie@55.97 Pinos@56.13 Mütze!@56.69", start: 55.25},
{abschnitt: "Verse 3", zeile: "Blau@57.61 wie@58.69 Pinos@59.07 Mütze!@60.23", start: 57.61},
{abschnitt: "Verse 4", zeile: "Wer@61.69 trägt@62.59 Grün?@62.93 Wer@63.61 trägt@63.77 Grün?@64.13", start: 61.69},
{abschnitt: "Verse 4", zeile: "Grün@65.25 wie@65.73 Lulus@65.99 Schal!@66.78", start: 65.25},
{abschnitt: "Verse 4", zeile: "Grün@67.57 wie@68.39 Lulus@68.91 Schal!@70.34", start: 67.57},
{abschnitt: "Break: call and response", zeile: "Wo@71.77 ist@72.37 Rot?@72.61", start: 71.77},
{abschnitt: "Break: call and response", zeile: "Wo@73.41 ist@73.57 Gelb?@73.85", start: 73.41},
{abschnitt: "Break: call and response", zeile: "Wo@74.65 ist@74.75 Blau?@75.01", start: 74.65},
{abschnitt: "Break: call and response", zeile: "Wo@75.77 ist@75.95 Grün?@76.27", start: 75.77},
{abschnitt: "Break: call and response", zeile: "Und@76.79 jetzt@77.03 was@77.25 Neues:@77.49 Braun!@78.53", start: 76.79},
{abschnitt: "Break: call and response", zeile: "Wo@79.23 ist@79.61 Braun?@79.87", start: 79.23},
{abschnitt: "Chorus, softer, slowing down at the end", zeile: "Zeig,@101.73 zeig,@102.49 zeig@103.01 mal@103.33 hin!@103.55", start: 101.73},
{abschnitt: "Chorus, softer, slowing down at the end", zeile: "Such@104.27 die@104.49 Farben,@104.73 schau@105.51 gut@105.75 hin!@106.03", start: 104.27},
{abschnitt: "Chorus, softer, slowing down at the end", zeile: "Rot@106.55 und@106.93 Gelb@107.21 und@107.65 Blau@107.81 und@108.25 Grün,@108.43", start: 106.55},
{abschnitt: "Chorus, softer, slowing down at the end", zeile: "alle@109.15 Farben_\u0001@109.39 das@110.93 ist@111.13 schön!@111.37", start: 109.15},
],
};
// es: 124 von 124 Wörtern direkt erkannt, Rest geschätzt
export const ES: {dauer: number; zeilen: Zeile[]} = {
dauer: 121.63,
zeilen: [
{abschnitt: "Chorus", zeile: "¡Mira,@2.71 mira,@3.83 mira!@4.43", start: 2.71},
{abschnitt: "Chorus", zeile: "Busca@4.69 los@5.93 colores,@6.45 ¡a@7.31 ver!@7.51", start: 4.69},
{abschnitt: "Chorus", zeile: "Rojo,@7.83 amarillo,@8.95 azul@9.85 y@10.03 verde,@10.45", start: 7.83},
{abschnitt: "Chorus", zeile: "¡todos@11.09 juntos,@11.45 qué@12.01 alegre!@12.29", start: 11.09},
{abschnitt: "Verse 1", zeile: "¿Quién@12.95 lleva@14.25 rojo?@14.65 ¿Quién?@15.51", start: 12.95},
{abschnitt: "Verse 1", zeile: "Rojo,@16.43 las@16.89 botas@17.27 de@17.75 Noa.@17.97", start: 16.43},
{abschnitt: "Verse 1", zeile: "Rojo,@18.77 las@19.37 botas@19.77 de@20.65 Noa.@20.95", start: 18.77},
{abschnitt: "Verse 2", zeile: "¿Quién@22.07 lleva@22.89 amarillo?@23.23 ¿Quién?@24.21", start: 22.07},
{abschnitt: "Verse 2", zeile: "Amarillo,@24.47 su@25.77 chaqueta.@26.15", start: 24.47},
{abschnitt: "Verse 2", zeile: "Amarillo,@27.39 su@28.27 chaqueta.@28.65", start: 27.39},
{abschnitt: "Chorus", zeile: "¡Mira,@31.29 mira,@32.27 mira!@32.85", start: 31.29},
{abschnitt: "Chorus", zeile: "Busca@33.19 los@34.39 colores,@34.93 ¡a@35.63 ver!@35.97", start: 33.19},
{abschnitt: "Chorus", zeile: "Rojo,@36.29 amarillo,@37.39 azul@38.13 y@38.47 verde,@38.87", start: 36.29},
{abschnitt: "Chorus", zeile: "¡todos@39.69 juntos,@39.89 qué@40.41 alegre!@40.71", start: 39.69},
{abschnitt: "Verse 3", zeile: "¿Quién@41.35 lleva@42.71 azul?@43.07 ¿Quién?@44.01", start: 41.35},
{abschnitt: "Verse 3", zeile: "Azul,@44.85 el@45.47 gorro@45.75 de@46.13 Pino.@46.37", start: 44.85},
{abschnitt: "Verse 3", zeile: "Azul,@47.23 el@47.79 gorro@48.27 de@48.95 Pino.@49.39", start: 47.23},
{abschnitt: "Verse 4", zeile: "¿Quién@50.25 lleva@51.39 verde?@51.75 ¿Quién?@52.65", start: 50.25},
{abschnitt: "Verse 4", zeile: "Verde,@52.87 la@53.95 bufanda@54.15 de@54.73 Lulu.@55.27", start: 52.87},
{abschnitt: "Verse 4", zeile: "Verde,@55.95 la@56.29 bufanda@56.63 de@57.25 Lulu.@57.73", start: 55.95},
{abschnitt: "Break: call and response", zeile: "¿Y@58.45 el@59.79 rojo?@59.99", start: 58.45},
{abschnitt: "Break: call and response", zeile: "¿Y@60.53 el@62.11 amarillo?@62.25", start: 60.53},
{abschnitt: "Break: call and response", zeile: "¿Y@62.97 el@64.71 azul?@64.85", start: 62.97},
{abschnitt: "Break: call and response", zeile: "¿Y@65.23 el@66.59 verde?@66.79", start: 65.23},
{abschnitt: "Break: call and response", zeile: "Y@67.15 ahora,@68.09 algo@68.49 nuevo:@69.23 ¡el@69.69 marrón!@70.19", start: 67.15},
{abschnitt: "Break: call and response", zeile: "¿Y@70.95 el@72.49 marrón?@72.71", start: 70.95},
{abschnitt: "Chorus, softer, slowing down at the end", zeile: "¡Mira,@86.99 mira,@87.93 mira!@88.53", start: 86.99},
{abschnitt: "Chorus, softer, slowing down at the end", zeile: "Busca@88.83 los@90.05 colores,@90.59 ¡a@91.33 ver!@91.63", start: 88.83},
{abschnitt: "Chorus, softer, slowing down at the end", zeile: "Rojo,@91.95 amarillo,@93.05 azul@93.87 y@94.13 verde,@94.57", start: 91.95},
{abschnitt: "Chorus, softer, slowing down at the end", zeile: "¡todos@95.41 juntos,@95.57 qué@96.11 alegre!@96.39", start: 95.41},
],
};
// fr: 116 von 129 Wörtern direkt erkannt, Rest geschätzt
export const FR: {dauer: number; zeilen: Zeile[]} = {
dauer: 131.97,
zeilen: [
{abschnitt: "Chorus", zeile: "Montre,@10.27 montre,@11.17 montre_\u0001@11.73", start: 10.27},
{abschnitt: "Chorus", zeile: "Cherche@12.41 les@12.59 couleurs,@13.23 regarde_\u0001@13.91", start: 12.41},
{abschnitt: "Chorus", zeile: "Rouge,@14.61 jaune,@15.27 bleu@15.95 et@16.35 vert,@16.75", start: 14.61},
{abschnitt: "Chorus", zeile: "que@17.15 de@17.25 couleurs_\u0001@17.76 c'est@18.28 super_\u0001@18.79", start: 17.15},
{abschnitt: "Verse 1", zeile: "Qui@20.21 porte@20.63 du@21.07 rouge_\u0001@21.45", start: 20.21},
{abschnitt: "Verse 1", zeile: "Les@22.43 bottes@22.63 rouges@22.95 de@23.39 Noa_\u0001@23.71", start: 22.43},
{abschnitt: "Verse 1", zeile: "Les@24.43 bottes@25.09 rouges@25.39 de@26.63 Noa_\u0001@26.91", start: 24.43},
{abschnitt: "Verse 2", zeile: "Qui@28.49 porte@29.29 du@30.91 jaune_\u0001@31.31", start: 28.49},
{abschnitt: "Verse 2", zeile: "La@32.41 veste@32.49 jaune@32.81 de@33.39 Noa_\u0001@33.61", start: 32.41},
{abschnitt: "Verse 2", zeile: "La@34.37 veste@34.97 jaune@35.31 de@36.33 Noa_\u0001@36.81", start: 34.37},
{abschnitt: "Chorus", zeile: "Montre,@40.23 montre,@40.91 montre_\u0001@41.41", start: 40.23},
{abschnitt: "Chorus", zeile: "Cherche@42.13 les@42.23 couleurs,@42.87 regarde_\u0001@43.59", start: 42.13},
{abschnitt: "Chorus", zeile: "Rouge,@44.21 jaune,@44.97 bleu@45.63 et@46.01 vert,@46.41", start: 44.21},
{abschnitt: "Chorus", zeile: "que@46.81 de@46.93 couleurs_\u0001@47.44 c'est@47.96 super_\u0001@48.47", start: 46.81},
{abschnitt: "Verse 3", zeile: "Qui@49.29 porte@50.29 du@50.73 bleu_\u0001@51.09", start: 49.29},
{abschnitt: "Verse 3", zeile: "Le@51.91 bonnet@52.27 bleu@52.67 de@53.19 Pino_\u0001@53.33", start: 51.91},
{abschnitt: "Verse 3", zeile: "Le@54.11 bonnet@54.73 bleu@55.19 de@56.25 Pino_\u0001@56.53", start: 54.11},
{abschnitt: "Verse 4", zeile: "Qui@59.15 porte@59.87 du@60.59 vert_\u0001@61.03", start: 59.15},
{abschnitt: "Verse 4", zeile: "L'écharpe@61.66 verte@62.30 de@62.93 Lulu_\u0001@63.21", start: 61.66},
{abschnitt: "Verse 4", zeile: "L'écharpe@64.03 verte@64.85 de@65.67 Lulu_\u0001@66.45", start: 64.03},
{abschnitt: "Break: call and response", zeile: "Où@68.61 est@69.33 le@69.55 rouge_\u0001@69.73", start: 68.61},
{abschnitt: "Break: call and response", zeile: "Où@70.27 est@70.51 le@70.73 jaune_\u0001@70.95", start: 70.27},
{abschnitt: "Break: call and response", zeile: "Où@71.49 est@71.73 le@72.01 bleu_\u0001@72.19", start: 71.49},
{abschnitt: "Break: call and response", zeile: "Où@72.69 est@72.93 le@73.17 vert_\u0001@73.39", start: 72.69},
{abschnitt: "Break: call and response", zeile: "Et@73.77 maintenant_\u0001@74.09 le@75.41 marron_\u0001@75.57", start: 73.77},
{abschnitt: "Break: call and response", zeile: "Où@76.69 est@77.59 le@77.89 marron_\u0001@78.09", start: 76.69},
{abschnitt: "Chorus, softer, slowing down at the end", zeile: "Montre,@99.21 montre,@100.11 montre_\u0001@100.61", start: 99.21},
{abschnitt: "Chorus, softer, slowing down at the end", zeile: "Cherche@101.27 les@101.49 couleurs,@102.09 regarde_\u0001@102.83", start: 101.27},
{abschnitt: "Chorus, softer, slowing down at the end", zeile: "Rouge,@103.45 jaune,@104.07 bleu@104.83 et@105.23 vert,@105.61", start: 103.45},
{abschnitt: "Chorus, softer, slowing down at the end", zeile: "que@105.99 de@106.13 couleurs_\u0001@106.90 c'est@107.68 super_\u0001@108.45", start: 105.99},
],
};

View file

@ -0,0 +1,79 @@
import React from 'react';
import {punkt} from '../../baukasten/helfer';
import {BODEN} from '../../baukasten/orte';
import {SPRACHE} from '../../baukasten/sprache';
import {type Bild, EinfachesLied, Farbklecks, type LiedDaten, PLATZ, type Zeile, wortZeit} from '../einfach/EinfachesLied';
import {DE, ES, FR} from '../einfach/daten/lied02';
// Liedvideo „Rot wie Noas Stiefel“ (Pinos Liederbuch, Lied 2: Farben) in DE/ES/FR.
// Strophe 1–4: Frage („Wer trägt Rot?“) weit, Antwort nah mit Leuchtkreis auf Stiefel, Jacke, Mütze, Schal.
// Mitmach-Teil: Karte mit Farbklecks, das Ding leuchtet; Braun = Pinos Stacheln. Drehbuch drehbuecher/lieder/lied02-farben/.
const ROT = '#c8362c';
const GELB = '#e2b33a';
const BLAU = '#3f6fb5';
const GRUEN = '#5f8a4a';
const BRAUN = '#8a5a2e';
// Dinge an den Figuren (Leinwand-Punkte der Posen *_A3_lachen, gemessen)
const DING = {
stiefel: punkt('noa', PLATZ.noa, BODEN, 339 + 390, 156 + 525),
jacke: punkt('noa', PLATZ.noa, BODEN, 339 + 370, 156 + 300),
muetze: punkt('pino', PLATZ.pino, BODEN, 391 + 370, 69 + 105),
schal: punkt('lulu', PLATZ.lulu, BODEN, 322 + 320, 23 + 300),
stacheln: punkt('pino', PLATZ.pino, BODEN, 391 + 170, 69 + 380),
};
const FARBEN = [
{farbe: ROT, ding: DING.stiefel, r: 80},
{farbe: GELB, ding: DING.jacke, r: 95},
{farbe: BLAU, ding: DING.muetze, r: 55},
{farbe: GRUEN, ding: DING.schal, r: 90},
{farbe: BRAUN, ding: DING.stacheln, r: 70},
];
const nah = (p: {x: number; y: number}, z = 1.9) => ({x: p.x, y: p.y + 60, z});
const LIEDER = {de: DE, es: ES, fr: FR};
const TITEL = {
de: {titel: 'Rot wie\nNoas Stiefel', zeile: 'Zeig, zeig, zeig mal hin!'},
es: {titel: 'Rojo como las\nbotas de Noa', zeile: '¡Mira, mira, mira!'},
fr: {titel: 'Rouge comme les\nbottes de Noa', zeile: 'Montre, montre, montre !'},
};
const DATEN: LiedDaten = {...LIEDER[SPRACHE], datei: `musik/lieder/lied02-${SPRACHE}.mp3`, titel: TITEL[SPRACHE].titel, zeileTitel: TITEL[SPRACHE].zeile};
const FARBWOERTER = [/^(rot|rojo|rouge)/i, /^(gelb|amarillo|jaune)/i, /^(blau|azul|bleu)/i, /^(gr[uü]n|verde|vert)/i];
// Index der Zeile innerhalb ihres Abschnitts (und welcher Abschnitt gleichen Namens)
const lage = (i: number) => {
const z = DATEN.zeilen;
let k = i;
while (k > 0 && z[k - 1].abschnitt === z[i].abschnitt) k--;
return i - k;
};
const szene = (i: number, z: Zeile): Bild => {
const a = z.abschnitt.toLowerCase();
const n = lage(i);
if (a.startsWith('chorus')) {
if (n === 0) return {noa: 'noa_B_zeigen'};
if (n === 2) {
// vier Farbkleckse erscheinen zu den Farbwörtern
const woerter = z.zeile.split(' ').map((w) => w.split('@')[0].replace(/[^\p{L}]/gu, ''));
const reihe = FARBWOERTER.map((re, k) => ({farbe: FARBEN[k].farbe, ab: wortZeit(z, Math.max(0, woerter.findIndex((w) => re.test(w))))}));
return {reihe};
}
return {};
}
const strophe = a.match(/^verse (\d)/);
if (strophe) {
const c = FARBEN[Number(strophe[1]) - 1];
if (n === 0) return {noa: i % 2 ? 'noa_A5_blick-links' : 'noa_A6_blick-rechts', pino: 'pino_A4_staunen', ruhig: true};
return {kamera: nah(c.ding), leuchten: [{...c.ding, farbe: c.farbe, r: c.r}]};
}
if (a.startsWith('break')) {
const c = FARBEN[Math.min(4, n)];
return {noa: 'noa_B_zeigen', karte: <Farbklecks farbe={c.farbe} />, leuchten: n === 4 ? [] : [{...c.ding, farbe: c.farbe, r: c.r}], pino: n >= 4 ? 'pino_A_kugel' : undefined, ruhig: true};
}
return {};
};
export const LIED02_DAUER = Math.round(DATEN.dauer * 30);
export const Lied02: React.FC = () => <EinfachesLied daten={DATEN} szene={szene} />;