From a4809d408977608113ee828f77865cd03547ed00 Mon Sep 17 00:00:00 2001 From: Lara Date: Sun, 8 Nov 2026 18:54:47 +0000 Subject: [PATCH] =?UTF-8?q?Liedvideo=20Lied=202=20=E2=80=9ERot=20wie=20Noa?= =?UTF-8?q?s=20Stiefel=E2=80=9C=20(DE/ES/FR)=20mit=20neuer=20Vorlage=20f?= =?UTF-8?q?=C3=BCr=20einfache=20Liedvideos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- remotion/src/Root.tsx | 2 + remotion/src/lieder/einfach/EinfachesLied.tsx | 190 ++++++++++++++++++ remotion/src/lieder/einfach/daten/lied02.ts | 115 +++++++++++ remotion/src/lieder/lied02/Lied02.tsx | 79 ++++++++ 4 files changed, 386 insertions(+) create mode 100644 remotion/src/lieder/einfach/EinfachesLied.tsx create mode 100644 remotion/src/lieder/einfach/daten/lied02.ts create mode 100644 remotion/src/lieder/lied02/Lied02.tsx diff --git a/remotion/src/Root.tsx b/remotion/src/Root.tsx index dd235a7..d9b3499 100644 --- a/remotion/src/Root.tsx +++ b/remotion/src/Root.tsx @@ -22,6 +22,7 @@ import {SHORTS_F08} from './shorts/folge08'; import {SZENEN as SZENEN_F01} from './folge01/Folge01'; import {SZENEN as SZENEN_F08} from './folge08/Folge08'; import {LIED01_DAUER, Lied01} from './lieder/lied01/Lied01'; +import {LIED02_DAUER, Lied02} from './lieder/lied02/Lied02'; import {LIED03_DAUER, Lied03} from './lieder/lied03/Lied03'; 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 = () => ( <> + diff --git a/remotion/src/lieder/einfach/EinfachesLied.tsx b/remotion/src/lieder/einfach/EinfachesLied.tsx new file mode 100644 index 0000000..23f6973 --- /dev/null +++ b/remotion/src/lieder/einfach/EinfachesLied.tsx @@ -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 = ({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 ( +
+ ); +}; + +// 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 ( +
+ {children} +
+ ); +}; + +export const Farbklecks: React.FC<{farbe: string}> = ({farbe}) => ( + + + +); + +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 ( + + + + + + + + + + + + + + + + z.zeile)} /> + + ); +}; + +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 ( + <> + + + + + + {bilder.map((bb, k) => + (bb.leuchten ?? []).map((l, j) => ), + )} + + {imRefrain && } + {bilder.map((bb, k) => + bb.karte ? ( + + {bb.karte} + + ) : null, + )} + {bilder.map((bb, k) => + bb.reihe ? ( +
+ {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 ( +
+
+ +
+
+ ); + })} +
+ ) : null, + )} + {/* Liedtitel im ersten Refrain */} +
+ ♫ {daten.zeileTitel} ♫ +
+ + + ); +}; diff --git a/remotion/src/lieder/einfach/daten/lied02.ts b/remotion/src/lieder/einfach/daten/lied02.ts new file mode 100644 index 0000000..8d5799b --- /dev/null +++ b/remotion/src/lieder/einfach/daten/lied02.ts @@ -0,0 +1,115 @@ +import type {Zeile} from '../EinfachesLied'; + +// Zeiten der Suno-Aufnahmen (lied02-.mp3), am 08.11.2026 mit faster-whisper (small) gemessen, +0,43 s, +// Text aus drehbuecher/lieder/lied02-farben/.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}, + ], +}; diff --git a/remotion/src/lieder/lied02/Lied02.tsx b/remotion/src/lieder/lied02/Lied02.tsx new file mode 100644 index 0000000..c77af08 --- /dev/null +++ b/remotion/src/lieder/lied02/Lied02.tsx @@ -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: , 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 = () => ;