Compare commits
5 commits
083e878b97
...
135700a481
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
135700a481 | ||
|
|
5091887695 | ||
|
|
1db1001dfc | ||
|
|
7de2b609ba | ||
|
|
4f83fbc587 |
30 changed files with 359 additions and 105 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -15,3 +15,7 @@ node_modules/
|
|||
__pycache__/
|
||||
schnitt/ausgabe/
|
||||
schnitt/tmp/
|
||||
|
||||
# Zugangsschlüssel (ElevenLabs u. a.), nie ins Git
|
||||
.env
|
||||
.env.local
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ mit `node werkzeuge/render.mjs FNN --sprache es`.
|
|||
| `.claude/agents/` | das Kinderkanal-Team als Subagenten (Kinderpsychologie, Eltern, Finanzen, Cutter, Dolmetscher …) |
|
||||
|
||||
## Zuerst lesen
|
||||
`wissen/stand-und-entscheidungen.md` – ganz oben steht, wo zuletzt
|
||||
weitergemacht werden sollte (Stand 28.10.2026: Lulu-Blumen-Szene in Folge 1
|
||||
neu, drei Startbilder vorgeschlagen, Freigabe ausstehend). Die Dateien unter
|
||||
`wissen/stand-und-entscheidungen.md` – der Block „STAND SESSION-ENDE“ mit dem neuesten Datum steht, wo zuletzt
|
||||
weitergemacht werden sollte (Stand 02.11.2026: ElevenLabs-Stimmen für die
|
||||
Figuren festlegen, Folgen 1 und 2 sind als Remotion-Entwurf fertig). Die Dateien unter
|
||||
`wissen/` stammen aus Laras lokalem Claude-Gedächtnis; neue Entscheidungen
|
||||
ab jetzt hier im Repo nachtragen.
|
||||
|
||||
|
|
|
|||
|
|
@ -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 | |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
BIN
figuren/bibliothek/noa_A_klatschen.png
Normal file
BIN
figuren/bibliothek/noa_A_klatschen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
84
remotion/package-lock.json
generated
84
remotion/package-lock.json
generated
|
|
@ -6,6 +6,7 @@
|
|||
"": {
|
||||
"name": "pino-remotion",
|
||||
"dependencies": {
|
||||
"@elevenlabs/elevenlabs-js": "^2.75.0",
|
||||
"@remotion/cli": "^4.0.300",
|
||||
"@remotion/google-fonts": "^4.0.300",
|
||||
"react": "^18.3.1",
|
||||
|
|
@ -32,6 +33,41 @@
|
|||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@elevenlabs/elevenlabs-js": {
|
||||
"version": "2.75.0",
|
||||
"resolved": "https://registry.npmjs.org/@elevenlabs/elevenlabs-js/-/elevenlabs-js-2.75.0.tgz",
|
||||
"integrity": "sha512-sCEddZAQS+JdAvD+jnqXund75mygRkHzgigqbWsQpcJozwe15FOtymS2+28YMOY06Pmja9l8EUE1LlXAtlW0Nw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"command-exists": "^1.2.9",
|
||||
"node-fetch": "^2.7.0",
|
||||
"ws": "^8.18.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@elevenlabs/elevenlabs-js/node_modules/ws": {
|
||||
"version": "8.22.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.22.1.tgz",
|
||||
"integrity": "sha512-oyL6OPYme/CmiFvNuGzeoxCJ4nOrZE2O1FwM9MWM1AsGri6nLXB2pg+lNA8aLeXMSav0J6eIVS1j6vUPzUtK9g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
|
||||
|
|
@ -1276,6 +1312,12 @@
|
|||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/command-exists": {
|
||||
"version": "1.2.9",
|
||||
"resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz",
|
||||
"integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
|
|
@ -2117,6 +2159,26 @@
|
|||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.60",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.60.tgz",
|
||||
|
|
@ -2745,6 +2807,12 @@
|
|||
"integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
|
|
@ -3295,6 +3363,12 @@
|
|||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.112.2",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.112.2.tgz",
|
||||
|
|
@ -3413,6 +3487,16 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
"render": "remotion render"
|
||||
},
|
||||
"dependencies": {
|
||||
"@elevenlabs/elevenlabs-js": "^2.75.0",
|
||||
"@remotion/cli": "^4.0.300",
|
||||
"@remotion/google-fonts": "^4.0.300",
|
||||
"react": "^18.3.1",
|
||||
|
|
|
|||
BIN
remotion/public/bibliothek/figuren/noa_A_klatschen.webp
Normal file
BIN
remotion/public/bibliothek/figuren/noa_A_klatschen.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
42
remotion/src/baukasten/clipkorrektur.ts
Normal file
42
remotion/src/baukasten/clipkorrektur.ts
Normal file
|
|
@ -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<string, {bildBreite: number; fussY: number; werte: Messung[]}> = {
|
||||
// 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};
|
||||
|
|
@ -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).
|
||||
|
|
@ -19,7 +20,8 @@ export const posenStand = (frame: number, folge: Posenfolge, blende = 5) => {
|
|||
let i = 0;
|
||||
while (i + 1 < folge.length && frame >= folge[i + 1][0]) i++;
|
||||
const [start, pose] = folge[i];
|
||||
const mix = i > 0 ? Math.min(1, (frame - start) / blende) : 1;
|
||||
const lin = i > 0 ? Math.min(1, (frame - start) / blende) : 1;
|
||||
const mix = lin * lin * (3 - 2 * lin); // weich an- und auslaufen
|
||||
return {pose, von: mix < 1 ? folge[i - 1][1] : undefined, mix};
|
||||
};
|
||||
|
||||
|
|
@ -81,13 +83,24 @@ type FigurProps = Omit<Lage, 'children' | 'figur' | 'hoehe'> & {
|
|||
|
||||
export const NOA_HOEHE = 340;
|
||||
|
||||
// Manche Bilder der Bibliothek sind auf dieselbe Höhe gezeichnet wie die stehende Figur,
|
||||
// obwohl die Figur darin hockt, sitzt oder liegt. Hier die richtige Größe relativ zur
|
||||
// Standhöhe; beim Überblenden geht die Größe fließend mit (der Kopf wandert nach unten).
|
||||
export const POSEN_SKALA: Record<string, number> = {
|
||||
noa_B_hocken: 0.72,
|
||||
noa_B_sitzt: 0.7,
|
||||
lulu_B_liegt: 0.65,
|
||||
};
|
||||
const skala = (pose: string) => POSEN_SKALA[pose] ?? 1;
|
||||
|
||||
export const Figur: React.FC<FigurProps> = ({pose, hoehe, frame, blende, ...lage}) => {
|
||||
const jetzt = useCurrentFrame();
|
||||
const folge: Posenfolge = typeof pose === 'string' ? [[0, pose]] : pose;
|
||||
const st = posenStand(frame ?? jetzt, folge, blende);
|
||||
const figur = POSEN[st.pose].figur as FigurName;
|
||||
const k = st.von ? skala(st.von) + (skala(st.pose) - skala(st.von)) * st.mix : skala(st.pose);
|
||||
return (
|
||||
<Rahmen {...lage} figur={figur} hoehe={hoehe ?? NOA_HOEHE * GROESSE[figur]}>
|
||||
<Rahmen {...lage} figur={figur} hoehe={(hoehe ?? NOA_HOEHE * GROESSE[figur]) * k}>
|
||||
{st.von && <PosenBild name={st.von} />}
|
||||
<PosenBild name={st.pose} deckkraft={st.mix} />
|
||||
</Rahmen>
|
||||
|
|
@ -137,9 +150,15 @@ export const ClipFigur: React.FC<ClipProps> = ({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 (
|
||||
<Rahmen {...lage} figur={figur} hoehe={hoehe ?? NOA_HOEHE * GROESSE[figur]}>
|
||||
<div style={{position: 'absolute', left: c.x, top: c.y, width: c.b, height: c.h}}>
|
||||
<div style={{position: 'absolute', left: c.x, top: c.y, width: c.b, height: c.h, ...korStil}}>
|
||||
<Freeze frame={Math.floor(t)}>
|
||||
<OffthreadVideo src={staticFile(c.datei)} transparent muted style={{width: '100%', height: '100%'}} />
|
||||
</Freeze>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -69,20 +69,22 @@ export const Blaetterregen: React.FC<{start: number; x0: number; y0: number; bod
|
|||
);
|
||||
};
|
||||
|
||||
// Ein Vogel von der Seite (fliegt nach rechts). `schlag` -1 … 1 = Flügel unten … oben.
|
||||
// Ein Vogel wie im Bilderbuch: zwei weiche Flügelbögen („m“), kleiner Körper.
|
||||
// `schlag` -1 … 1 = Flügel unten … oben.
|
||||
const Vogel: React.FC<{x: number; y: number; g: number; schlag: number; neigung: number}> = ({x, y, g, schlag, neigung}) => {
|
||||
const h = -8 - schlag * 24; // Flügelspitze relativ zur Schulter
|
||||
const fluegel = (dx: number, dy: number) => `M ${3 + dx} ${-3 + dy} Q ${-3 + dx} ${h * 0.55 + dy} ${-15 + dx} ${h + dy} Q ${-6 + dx} ${h * 0.35 + dy} ${-7 + dx} ${1 + dy} Z`;
|
||||
const h = 4 + schlag * 13; // Höhe der Flügelbögen (oben = positiv)
|
||||
const spitze = 3 - schlag * 9; // Flügelspitzen sinken beim Abschlag
|
||||
return (
|
||||
<g transform={`translate(${x} ${y}) rotate(${neigung}) scale(${g / 30})`}>
|
||||
<path d={fluegel(4, -1)} fill="#3a3540" opacity={0.85} />
|
||||
<path d="M -14 0 L -27 -6 L -25 0 L -27 6 Z" fill="#4a4550" />
|
||||
<ellipse cx={0} cy={0} rx={16} ry={7} fill="#4f4a55" />
|
||||
<ellipse cx={1} cy={3} rx={11} ry={3.5} fill="#8a7f78" opacity={0.6} />
|
||||
<circle cx={15} cy={-4} r={6.5} fill="#4f4a55" />
|
||||
<path d="M 20 -6 L 28 -3.5 L 20 -1.5 Z" fill="#c98a3c" />
|
||||
<circle cx={17} cy={-5.5} r={1.4} fill="#f4ead8" />
|
||||
<path d={fluegel(0, 0)} fill="#5c5662" />
|
||||
<path
|
||||
d={`M -26 ${spitze} Q -13 ${-h - 4}, -1 0 Q 13 ${-h - 4}, 26 ${spitze}`}
|
||||
stroke="#4a4550"
|
||||
strokeWidth={4.5}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
fill="none"
|
||||
/>
|
||||
<ellipse cx={0} cy={1.5} rx={4.5} ry={3.2} fill="#4a4550" />
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
|
@ -99,10 +101,10 @@ export const Voegel: React.FC<{start: number; anzahl?: number; skala?: number}>
|
|||
const x = 160 + t * 5.5 - Math.abs(reihe) * 75 * skala + r('x') * 20;
|
||||
const y = 600 - t * 1.5 + reihe * 42 * skala + Math.sin(t * 0.05 + r('w') * 6) * 8;
|
||||
// Flügelschlag ohne Pause, je Vogel etwas anders schnell
|
||||
const takt = t * (0.34 + r('f') * 0.1) + r('p') * 6;
|
||||
const takt = t * (0.22 + r('f') * 0.06) + r('p') * 6;
|
||||
const schlag = Math.sin(takt);
|
||||
const g = (34 + r('g') * 8) * skala * (1 - Math.abs(reihe) * 0.03);
|
||||
return <Vogel key={i} x={x} y={y} g={g} schlag={schlag} neigung={-12 + Math.cos(takt) * 3} />;
|
||||
return <Vogel key={i} x={x} y={y} g={g} schlag={schlag} neigung={-6 + Math.cos(takt) * 2} />;
|
||||
})}
|
||||
</svg>
|
||||
);
|
||||
|
|
@ -174,7 +176,7 @@ export const NEST_BILD = {b: 440, h: 260, x: 220, y: 180}; // Bildgröße und La
|
|||
export const NestBlaetter: React.FC<{voll?: number; seite: 'hinten' | 'vorne'}> = ({voll = 1, seite}) => (
|
||||
<>
|
||||
{seite === 'hinten' && (
|
||||
<div style={{position: 'absolute', left: -170, top: -30, width: 340, height: 60, borderRadius: '50%', background: 'radial-gradient(closest-side, rgba(70,45,25,0.45), rgba(70,45,25,0))'}} />
|
||||
<div style={{position: 'absolute', left: -170, top: -30, width: 340, height: 60, borderRadius: '50%', background: 'radial-gradient(closest-side, rgba(70,45,25,0.45), rgba(70,45,25,0))', opacity: Math.min(1, voll * 3)}} />
|
||||
)}
|
||||
{NEST[seite].map((b, i) =>
|
||||
b.reihe < voll ? <Blatt key={i} x={b.x * (0.5 + 0.5 * voll)} y={b.y * (0.4 + 0.6 * voll)} r={b.r} s={b.s} farbe={b.farbe} /> : null,
|
||||
|
|
@ -190,9 +192,6 @@ export const Nest: React.FC<{x: number; y: number; voll?: number; seite: 'hinten
|
|||
/>
|
||||
) : (
|
||||
<div style={{position: 'absolute', left: x, top: y, transform: `scale(${s})`, transformOrigin: '0 0', filter}}>
|
||||
{seite === 'hinten' && (
|
||||
<div style={{position: 'absolute', left: -170, top: -30, width: 340, height: 60, borderRadius: '50%', background: 'radial-gradient(closest-side, rgba(70,45,25,0.45), rgba(70,45,25,0))'}} />
|
||||
)}
|
||||
<NestBlaetter voll={voll} seite={seite} />
|
||||
</div>
|
||||
);
|
||||
|
|
@ -351,3 +350,31 @@ export const Laubhaufen: React.FC<{x: number; y: number; s?: number; wackeln?: n
|
|||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
// Klatsch-Hände fürs Lied (Noas rote Fäustlinge): klatschen auf jeden zweiten Schlag,
|
||||
// damit Kinder den Rhythmus mitklatschen können. `takt` = Bilder pro Schlag.
|
||||
export const Klatschen: React.FC<{x: number; y: number; takt: number; s?: number; deckkraft?: number}> = ({x, y, takt, s = 1, deckkraft = 1}) => {
|
||||
const frame = useCurrentFrame();
|
||||
// 0 = Hände zusammen (Klatsch auf Schlag 1, 3, 5 …), 1 = weit auseinander
|
||||
const auf = Math.abs(Math.sin((Math.PI * (frame - takt)) / (2 * takt)));
|
||||
const weit = 18 + auf * 52;
|
||||
const klatsch = auf < 0.18;
|
||||
const Faust: React.FC<{seite: 1 | -1}> = ({seite}) => (
|
||||
<g transform={`translate(${seite * weit} 0) scale(${-seite} 1) rotate(${-8 + auf * 14})`}>
|
||||
<path d="M -30 -34 C -46 -30, -50 6, -34 30 C -24 44, 6 46, 20 30 C 30 18, 30 -10, 20 -26 C 10 -40, -14 -42, -30 -34 Z" fill="#c8362c" />
|
||||
<path d="M 14 -18 C 30 -26, 40 -12, 30 -2 C 24 4, 16 2, 12 -4 Z" fill="#c8362c" />
|
||||
<rect x={-38} y={26} width={58} height={20} rx={8} fill="#e9e2d2" />
|
||||
<path d="M -22 -20 C -30 -8, -28 10, -20 20" stroke="#e8786a" strokeWidth={5} strokeLinecap="round" fill="none" opacity={0.6} />
|
||||
</g>
|
||||
);
|
||||
return (
|
||||
<svg width={360} height={200} viewBox="-180 -100 360 200" style={{position: 'absolute', left: x - 180 * s, top: y - 100 * s, transform: `scale(${s})`, transformOrigin: '0 0', opacity: deckkraft, overflow: 'visible'}}>
|
||||
{klatsch &&
|
||||
[-60, -30, 0, 30, 60].map((w) => (
|
||||
<path key={w} d="M 0 -58 L 0 -84" stroke="#e0a020" strokeWidth={6} strokeLinecap="round" transform={`rotate(${w})`} />
|
||||
))}
|
||||
<Faust seite={-1} />
|
||||
<Faust seite={1} />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -501,7 +501,7 @@ export const KalenderAufTisch: React.FC<{p: number}> = ({p}) => {
|
|||
export const KALENDER_TUER1 = {x: 330 + 4 * 215 + 90, y: 250 + 1 * 180 + 75}; // Türchen „1“ (Index 10)
|
||||
|
||||
// Noa malt den Kalender (Blick von oben auf das Blatt, über `kalender_leer`).
|
||||
// `p` 0…1: Titel, dann die Türchen Zeile für Zeile. Die Stiftspitze folgt dem Strich.
|
||||
// `p` 0…1: Titel, dann die Türchen Zeile für Zeile.
|
||||
const drehe = (x: number, y: number) => {
|
||||
const w = (-2 * Math.PI) / 180;
|
||||
const dx = x - 960;
|
||||
|
|
@ -569,22 +569,8 @@ export const KalenderMalen: React.FC<{p: number; wackeln: number}> = ({p, wackel
|
|||
})}
|
||||
</g>
|
||||
</g>
|
||||
{/* Noas Hand mit Buntstift, von unten rechts */}
|
||||
<g transform={`translate(${s.x} ${s.y}) rotate(-28)`}>
|
||||
<path d="M -9 -4 L 0 -24 L 9 -4 Z" fill="#f1d2a6" />
|
||||
<path d="M -3 -17 L 0 -24 L 3 -17 Z" fill={farbe} />
|
||||
<rect x={-11} y={-6} width={22} height={260} rx={4} fill={farbe} />
|
||||
<rect x={-11} y={-6} width={7} height={260} fill="rgba(255,255,255,0.18)" />
|
||||
{/* Ärmel */}
|
||||
<path d="M -62 150 L 66 150 L 120 900 L -150 900 Z" fill="#d39b3a" />
|
||||
<path d="M -66 140 Q 2 164 70 140 L 74 176 Q 2 198 -70 176 Z" fill="#c1862c" />
|
||||
{/* Handrücken und Finger, die den Stift halten */}
|
||||
<ellipse cx={4} cy={118} rx={52} ry={44} fill="#f2c4a2" stroke="#dba585" strokeWidth={2} />
|
||||
<ellipse cx={-30} cy={98} rx={15} ry={24} fill="#f2c4a2" stroke="#dba585" strokeWidth={2} transform="rotate(-25 -30 98)" />
|
||||
<ellipse cx={-6} cy={72} rx={17} ry={13} fill="#f2c4a2" stroke="#dba585" strokeWidth={2} />
|
||||
<ellipse cx={20} cy={80} rx={16} ry={12} fill="#f2c4a2" stroke="#dba585" strokeWidth={2} />
|
||||
<ellipse cx={38} cy={94} rx={14} ry={11} fill="#f2c4a2" stroke="#dba585" strokeWidth={2} />
|
||||
</g>
|
||||
{/* Aus Noas Sicht aufs Blatt: die Striche entstehen von selbst, ohne Hand und Stift
|
||||
(der Arm von unten wirkte wie ein schwebender Stift) */}
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -513,6 +513,20 @@ export const POSEN: Record<string, Pose> = {
|
|||
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",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import {random, useCurrentFrame} from 'remotion';
|
||||
import {Figur, Kamera} from '../baukasten/figur';
|
||||
import {Blende, Vignette} from '../baukasten/helfer';
|
||||
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';
|
||||
|
|
@ -57,19 +57,22 @@ 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 (
|
||||
<>
|
||||
<Kamera x={1000} y={600} zoom={1.3}>
|
||||
<Kamera x={1000} y={690} zoom={1.3}>
|
||||
<Ort name="garten_herbst" />
|
||||
<Laubhaufen x={HAUFEN_X + 380} y={BODEN + 6} />
|
||||
<Figur pose={luluPose} x={1300} y={BODEN} drehung={schwank(0.5, 2.2)} stauchY={1 - 0.012 * schlag} blende={8} />
|
||||
<Figur pose={noaPose} x={980} y={BODEN + wipp(0.3, 10)} drehung={schwank(0, 3)} blende={8} />
|
||||
<Figur pose={noaPose} x={980} y={BODEN + wipp(0.3, 10)} drehung={schwank(0, 3)} blende={3} />
|
||||
<Figur pose={pinoPose} x={760} y={BODEN + wipp(0, 16)} drehung={schwank(1, 4)} stauchY={1 - 0.03 * schlag} blende={8} />
|
||||
{f >= z.s4.start && f < z.w1.start && <Blume x={764} y={BODEN - 40} s={0.5} />}
|
||||
</Kamera>
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ export const Szene2: React.FC = () => {
|
|||
{imHaufen && <Figur pose={noaPose} x={noaX} y={noaY} stauchY={atmen(f, 2) * hopfStauch(f, landung)} schatten={false} />}
|
||||
<Laubhaufen x={HAUFEN_X} y={BODEN + 6} wackeln={wackeln} flach={flach} />
|
||||
{imHaufen ? null : noaGeht ? (
|
||||
<Geher figur="noa" spiegeln x={noaX} y={BODEN} tempo={1.5} />
|
||||
<Geher figur="noa" spiegeln x={noaX} y={BODEN} tempo={1.25} />
|
||||
) : (
|
||||
<Figur pose={noaPose} x={noaX} y={noaY} stauchY={atmen(f, 2) * hopfStauch(f, landung) * hockeStauch} blende={f >= hocke ? HOCKE_BLENDE : 5} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ export const Szene3: React.FC = () => {
|
|||
<Laubhaufen x={HAUFEN_X} y={BODEN + 6} />
|
||||
{noaDa &&
|
||||
(noaGeht ? (
|
||||
<Geher figur="noa" spiegeln x={noaX} y={BODEN} tempo={1.5} />
|
||||
<Geher figur="noa" spiegeln x={noaX} y={BODEN} tempo={1.25} />
|
||||
) : (
|
||||
<Figur pose={noaPose} x={noaX} y={noaY} stauchY={atmen(f, 2) * (1 + 0.04 * zehen)} blende={10} />
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export const LULU_APFEL_X = APFEL.x + 149;
|
|||
// Den Hals senkt sie mit dem Clip lulu_C9_hals-runter; der Apfel bleibt dabei im Maul
|
||||
// (Maul-Bahn gemessen, luluMaulC9). Unten liegt das Maul direkt neben Pino.
|
||||
const LULU_WARTE_X = 930; // bis zum Pflücken etwas rechts vom Apfel
|
||||
const C9_TEMPO = 1.3;
|
||||
const C9_TEMPO = 1.25; // 1,25 × 24 fps = 30 fps: jedes Videobild genau ein Clipbild (ruckelfrei)
|
||||
const C9_START = 17; // vorher steht sie im Clip still
|
||||
const C9_UNTEN = 105; // Kopf unten
|
||||
const c9Frames = (bilder: number) => (bilder * 30) / 24 / C9_TEMPO;
|
||||
|
|
@ -140,7 +140,7 @@ export const Szene4: React.FC = () => {
|
|||
<Blume x={BLUME_X} y={BLUME_Y} s={0.8} />
|
||||
{apfelAmBaum && <Apfel x={APFEL.x} y={APFEL.y} s={0.9} r={Math.sin(f * 0.05) * 4} />}
|
||||
{luluGeht ? (
|
||||
<Geher figur="lulu" x={luluX} y={luluY} tempo={1} />
|
||||
<Geher figur="lulu" x={luluX} y={luluY} tempo={1.25} />
|
||||
) : imClip ? (
|
||||
f < hoch ? (
|
||||
<ClipFigur name="lulu_C9_hals-runter" start={runter - c9Frames(C9_START)} tempo={C9_TEMPO} bis={C9_UNTEN / 24} x={luluX} y={luluY} />
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export const Szene5: React.FC = () => {
|
|||
const {z, dauer} = S5;
|
||||
|
||||
// Lulu senkt den Hals (Clip) bis zur Hälfte, bleibt wackelnd stecken, hebt ihn wieder (Clip rückwärts)
|
||||
const C9_TEMPO = 1.1;
|
||||
const C9_TEMPO = 1.25; // ruckelfrei (siehe Szene 4)
|
||||
const C9_START = 17;
|
||||
const C9_HALB = 52; // so weit kommt sie
|
||||
const c9 = (bilder: number) => (bilder * 30) / 24 / C9_TEMPO;
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ export const Szene6: React.FC = () => {
|
|||
const BLENDE = 14;
|
||||
// Hände: noa_B_hocken auf den Knien (Leinwand ca. 700, 520), noa_A_arme-hoch rechte Hand (800, 190); Fuß (675, 700), Maßstab 340/535
|
||||
const m = 340 / 535;
|
||||
const HAND_HOCKE = {x: NOA_BLUME_X + (700 - 675) * m, y: BODEN + (520 - 700) * m};
|
||||
const HAND_HOCKE = {x: NOA_BLUME_X + (700 - 675) * m * 0.72, y: BODEN + (520 - 700) * m * 0.72}; // Hocke-Bild wird auf 72 % verkleinert (POSEN_SKALA)
|
||||
const HAND_OBEN = {x: NOA_BLUME_X + (800 - 675) * m, y: BODEN + (190 - 700) * m};
|
||||
let blume: {x: number; y: number; s: number} = {x: BLUME_X, y: BLUME_Y, s: 0.8};
|
||||
if (f >= pflueck) blume = {x: pfoten.x, y: pfoten.y + 20, s: 0.55};
|
||||
|
|
@ -148,9 +148,9 @@ export const Szene6: React.FC = () => {
|
|||
<Figur pose={luluPose} x={LULU_BLUME_X} y={BODEN - 6} stauchY={atmen(f, 3, 0.006)} blende={12} />
|
||||
<Figur pose={noaPose} x={NOA_BLUME_X} y={BODEN + huepfer(f, aufstehen + BLENDE, 14, 10)} stauchY={atmen(f, 2) * noaStauch * hopfStauch(f, aufstehen + BLENDE + 10)} blende={BLENDE} />
|
||||
{pinoGehtHin ? (
|
||||
<Geher figur="pino" spiegeln x={pinoX} y={pinoBoden} tempo={1.4} />
|
||||
<Geher figur="pino" spiegeln x={pinoX} y={pinoBoden} tempo={1.25} />
|
||||
) : pinoGehtZurueck ? (
|
||||
<Geher figur="pino" x={pinoX} y={pinoBoden} tempo={1.4} />
|
||||
<Geher figur="pino" x={pinoX} y={pinoBoden} tempo={1.25} />
|
||||
) : (
|
||||
<Figur pose={pinoPose} x={pinoX} y={pinoBoden + huepfer(f, z.l1.ende, 24, 12)} stauchY={atmen(f, 1, 0.01)} blende={10} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import {random, useCurrentFrame} from 'remotion';
|
||||
import {Figur, Kamera} from '../baukasten/figur';
|
||||
import {Blende, Vignette} from '../baukasten/helfer';
|
||||
import {Blende, Vignette, klatschFolge} from '../baukasten/helfer';
|
||||
import {BODEN, Ort} from '../baukasten/orte';
|
||||
import {SCHRIFT, Untertitel} from '../baukasten/untertitel';
|
||||
import {TEXTE, plane} from './plan';
|
||||
|
|
@ -36,17 +36,20 @@ export const Lied: React.FC = () => {
|
|||
const schwank = (phase: number, g: number) => Math.sin((f * Math.PI) / (2 * TAKT) + phase) * g;
|
||||
return (
|
||||
<>
|
||||
<Kamera x={980} y={600} zoom={1.3}>
|
||||
<Kamera x={980} y={690} zoom={1.3}>
|
||||
<Ort name="garten" />
|
||||
<Figur pose="lulu_A3_lachen" x={1240} y={BODEN} drehung={schwank(0.5, 2.2)} stauchY={1 - 0.012 * schlag} />
|
||||
<Figur
|
||||
pose={[
|
||||
[0, 'noa_A3_lachen'],
|
||||
// Noa klatscht im Takt (jeder zweite Schlag)
|
||||
...klatschFolge(z.r1.start, z.r4.start, TAKT),
|
||||
[z.r4.start, 'noa_A_arme-hoch'],
|
||||
]}
|
||||
x={960}
|
||||
y={BODEN + wipp(0.3, 10)}
|
||||
drehung={schwank(0, 3)}
|
||||
blende={3}
|
||||
/>
|
||||
<Figur pose="pino_A3_lachen" x={720} y={BODEN + wipp(0, 16)} drehung={schwank(1, 4)} stauchY={1 - 0.03 * schlag} />
|
||||
</Kamera>
|
||||
|
|
|
|||
|
|
@ -125,8 +125,8 @@ export const Szene2: React.FC = () => {
|
|||
<>
|
||||
<Kamera {...kam}>
|
||||
<Ort name="garten" />
|
||||
{noaGeht ? <Geher figur="noa" spiegeln x={noaX} y={BODEN} /> : <Figur pose={noaPose} x={noaX} y={noaY} stauchY={atmen(f, 2) * kichStauch * (1 + 0.02 * vorbeugen)} drehung={kichDreh + 7 * vorbeugen} />}
|
||||
{!pinoDa ? null : pinoGeht ? <Geher figur="pino" x={pinoX} y={BODEN} tempo={1.8} /> : <Figur pose={pinoPose} x={pinoX} y={pinoY} stauchY={pinoStauchY} drehung={pinoDreh} blende={4} />}
|
||||
{noaGeht ? <Geher figur="noa" spiegeln x={noaX} y={BODEN} tempo={1.25} /> : <Figur pose={noaPose} x={noaX} y={noaY} stauchY={atmen(f, 2) * kichStauch * (1 + 0.02 * vorbeugen)} drehung={kichDreh + 7 * vorbeugen} />}
|
||||
{!pinoDa ? null : pinoGeht ? <Geher figur="pino" x={pinoX} y={BODEN} tempo={2.5} /> : <Figur pose={pinoPose} x={pinoX} y={pinoY} stauchY={pinoStauchY} drehung={pinoDreh} blende={4} />}
|
||||
<Zzz x={PINO_X + 40} y={BODEN - 150} start={z.p2.ende} ende={schreck} s={0.7} />
|
||||
</Kamera>
|
||||
<Blaetterwind anzahl={6} />
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import {useCurrentFrame} from 'remotion';
|
||||
import {kurve} from '../bewegung';
|
||||
import {ClipFigur, Figur, Geher, Kamera, NoaHocke} from '../baukasten/figur';
|
||||
import {ClipFigur, Figur, Geher, Kamera} from '../baukasten/figur';
|
||||
import {Blende, Vignette, atmen, hopfStauch, huepfer, kamFahrt} from '../baukasten/helfer';
|
||||
import {Blaetterwind, Blatt, Voegel} from '../baukasten/natur';
|
||||
import {BODEN, Ort} from '../baukasten/orte';
|
||||
|
|
@ -55,11 +55,11 @@ export const Szene3: React.FC = () => {
|
|||
let lulu: React.ReactNode;
|
||||
const luluY = BODEN - 10;
|
||||
if (luluGeht) {
|
||||
lulu = <Geher figur="lulu" x={luluX} y={luluY} />;
|
||||
lulu = <Geher figur="lulu" x={luluX} y={luluY} tempo={1.25} />;
|
||||
} else if (f < halsHoch) {
|
||||
lulu = <ClipFigur name="lulu_C9_hals-runter" start={halsRunter} tempo={1.25} x={LULU_X} y={luluY} />;
|
||||
} else if (f < halsHoch + 110) {
|
||||
lulu = <ClipFigur name="lulu_C9_hals-runter" start={halsHoch} tempo={1.5} rueckwaerts x={LULU_X} y={luluY} />;
|
||||
lulu = <ClipFigur name="lulu_C9_hals-runter" start={halsHoch} tempo={1.25} rueckwaerts x={LULU_X} y={luluY} />;
|
||||
} else {
|
||||
lulu = (
|
||||
<Figur
|
||||
|
|
@ -82,9 +82,10 @@ export const Szene3: React.FC = () => {
|
|||
[aufsBlatt, PINO_X],
|
||||
[aufsBlatt + 24, GROSSES_BLATT_X],
|
||||
]);
|
||||
// Das große Blatt segelt vom Apfelbaum herunter und landet neben Pino
|
||||
const blattFaellt = aufsBlatt - 80;
|
||||
const u = Math.min(1, Math.max(0, (f - blattFaellt) / 70));
|
||||
// Das große Blatt liegt von Anfang an neben Pino im Gras (vorher fiel es außerhalb des
|
||||
// Bildausschnitts und tauchte dann plötzlich unter ihm auf); Pino setzt sich darauf
|
||||
const blattFaellt = -1;
|
||||
const u = 1;
|
||||
const schaukeln = Math.sin(u * 8) * (1 - u);
|
||||
const blattX = GROSSES_BLATT_X - 180 * (1 - u) + schaukeln * 70;
|
||||
const blattY = BODEN - 8 - 560 * (1 - u);
|
||||
|
|
@ -125,7 +126,11 @@ export const Szene3: React.FC = () => {
|
|||
[z.l1.start + 15, 'noa_A3_lachen'],
|
||||
[z.e1.start, 'noa_A6_blick-rechts'],
|
||||
[z.bild2.start + 30, 'noa_B1_traurig'],
|
||||
[knien, 'noa_B_hocken'],
|
||||
];
|
||||
// in die Hocke: Standbilder weich überblenden, dabei kurz absinken
|
||||
const HOCKE_BLENDE = 14;
|
||||
const hockeStauch = f >= knien && f < knien + HOCKE_BLENDE ? 1 - 0.05 * Math.sin(((f - knien) / HOCKE_BLENDE) * Math.PI) : 1;
|
||||
const noaGeht = f >= knien - 36 && f < knien;
|
||||
|
||||
const kam = kamFahrt(f, [
|
||||
|
|
@ -154,7 +159,7 @@ export const Szene3: React.FC = () => {
|
|||
) : (
|
||||
<Kamera {...kam}>
|
||||
<Ort name="garten" />
|
||||
{noaGeht ? <Geher figur="noa" spiegeln x={noaX} y={BODEN} /> : f >= knien ? <NoaHocke start={knien} x={noaX} y={BODEN} /> : <Figur pose={noaPose} x={noaX} y={BODEN} stauchY={atmen(f, 2)} />}
|
||||
{noaGeht ? <Geher figur="noa" spiegeln x={noaX} y={BODEN} tempo={1.25} /> : <Figur pose={noaPose} x={noaX} y={BODEN} stauchY={atmen(f, 2) * hockeStauch} blende={f >= knien ? HOCKE_BLENDE : 5} />}
|
||||
{f > blattFaellt && <Blatt x={blattX} y={blattY} r={blattR} s={2.6} farbe="#c8642c" />}
|
||||
<Figur pose={pinoPose} x={pinoX} y={pinoY} stauchY={pinoStauchY} drehung={pinoDreh} blende={6} />
|
||||
{lulu}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import {useCurrentFrame} from 'remotion';
|
||||
import {kurve} from '../bewegung';
|
||||
import {Figur, Geher, Kamera, NOA_HOCKE_FRAMES, NoaHocke} from '../baukasten/figur';
|
||||
import {Figur, Geher, Kamera} from '../baukasten/figur';
|
||||
import {Blende, LULU_MAUL_A1, LULU_MAUL_LINKS, Vignette, atmen, hopfStauch, huepfer, kamFahrt, punkt, ruck} from '../baukasten/helfer';
|
||||
import {Blaetterregen, Blatt, LAUB, Nest, Ruf} from '../baukasten/natur';
|
||||
import {BODEN, NEST_X, Ort} from '../baukasten/orte';
|
||||
|
|
@ -54,13 +54,13 @@ export const Szene4: React.FC = () => {
|
|||
|
||||
// Noa
|
||||
const noaGehtVon = zumStapel + 20;
|
||||
const noaGehtBis = noaGehtVon + 90;
|
||||
const noaGehtBis = noaGehtVon + 60;
|
||||
const noaX = kurve(f, [
|
||||
[noaGehtVon, NOA_X + 140],
|
||||
[noaGehtBis, NOA_ZAEHL_X],
|
||||
]);
|
||||
const noaGeht = f > noaGehtVon && f < noaGehtBis;
|
||||
const noaPose: [number, string][] = [
|
||||
const noaStehPose: [number, string][] = [
|
||||
[0, 'noa_A3_lachen'],
|
||||
[noaGehtBis, 'noa_A6_blick-rechts'],
|
||||
[z.c5.ende, 'noa_A3_lachen'],
|
||||
|
|
@ -68,21 +68,23 @@ export const Szene4: React.FC = () => {
|
|||
[z.l2.ende, 'noa_A3_lachen'],
|
||||
[z.e4.start, 'noa_A3_lachen'],
|
||||
];
|
||||
// Hocke: Clip vorwärts (runter) oder rückwärts (aufstehen); sonst Standbilder
|
||||
const hocke = (() => {
|
||||
const w = (start: number, auf: boolean) => ({start, auf});
|
||||
if (f < z.n1.start + 30) return w(-1000, false);
|
||||
if (f < z.n1.start + 30 + NOA_HOCKE_FRAMES) return w(z.n1.start + 30, true);
|
||||
if (f >= z.n2.start - 15 && f < z.c5.ende) return w(z.n2.start - 15, false);
|
||||
if (f >= z.c5.ende && f < z.c5.ende + NOA_HOCKE_FRAMES) return w(z.c5.ende, true);
|
||||
if (f >= zurueck && f < z.e4.start) return w(-1000, false);
|
||||
if (f >= z.e4.start && f < z.e4.start + NOA_HOCKE_FRAMES) return w(z.e4.start, true);
|
||||
return null;
|
||||
})();
|
||||
// Hocke: Abschnitte, in denen Noa hockt; Wechsel als weiche Überblendung der Standbilder
|
||||
// (der Hinsetz-Clip ist anders gezeichnet), dabei kurz absinken bzw. sich strecken
|
||||
const HOCKE_BLENDE = 14;
|
||||
const hockZeiten: [number, number][] = [
|
||||
[-1000, z.n1.start + 30],
|
||||
[z.n2.start - 15, z.c5.ende],
|
||||
[zurueck, z.e4.start],
|
||||
];
|
||||
const inHocke = hockZeiten.some(([a, b]) => f >= a && f < b);
|
||||
const stehPose = (t: number) => noaStehPose.filter(([s]) => s <= t).pop()![1];
|
||||
const noaPose: [number, string][] = [...noaStehPose, ...hockZeiten.flatMap(([a, b]): [number, string][] => [[a, 'noa_B_hocken'], [b, stehPose(b)]])].sort((p, q) => p[0] - q[0]);
|
||||
const bump = (t: number) => (f >= t && f < t + HOCKE_BLENDE ? Math.sin(((f - t) / HOCKE_BLENDE) * Math.PI) : 0);
|
||||
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;
|
||||
|
|
@ -104,10 +106,12 @@ export const Szene4: React.FC = () => {
|
|||
const pinoGeht = (f > pinoGehtVon && f < pinoGehtBis) || (f > zurueck + 10 && f < zurueck + 60);
|
||||
const pinoSpiegel = f > pinoGehtVon && f < pinoGehtBis; // nach rechts
|
||||
const imNest = f >= z.bild5.start;
|
||||
// Pino sitzt beim Zählen rechts von Noa: seine Bilder schauen nach rechts, darum gespiegelt (schaut zu ihr)
|
||||
const pinoZuNoa = f >= pinoGehtBis && f <= zurueck + 10;
|
||||
const pinoPose: [number, string][] = [
|
||||
[0, 'pino_A1_augen-zu'],
|
||||
[z.n1.start + 20, 'pino_A2_augen-halb'],
|
||||
[pinoGehtBis, 'pino_A5_blick-links'],
|
||||
[pinoGehtBis, 'pino_A6_blick-rechts'],
|
||||
...zaehlen.map((c) => [c, 'pino_A4_staunen'] as [number, string]),
|
||||
[z.c5.ende, 'pino_A3_lachen'],
|
||||
[niesen + 4, 'pino_B2_erschrocken'],
|
||||
|
|
@ -198,7 +202,7 @@ export const Szene4: React.FC = () => {
|
|||
kam.y += ruck(f, niesen, 9, 22);
|
||||
|
||||
const lulu = <Figur pose={luluPose} x={LULU_BAUM_X} y={luluY} stauchY={luluStauch} drehung={luluDreh} />;
|
||||
const luluBeimNest = f >= zurueck && <Figur pose="lulu_A_hals-unten" x={2030} y={BODEN - 4} drehung={schieben * 1.2} />;
|
||||
const luluBeimNest = f >= zurueck && <Figur pose="lulu_A_hals-unten" x={2030} y={BODEN - 4} />;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -217,11 +221,16 @@ export const Szene4: React.FC = () => {
|
|||
)}
|
||||
<Nest x={NEST_X} y={BODEN} seite="hinten" voll={f < zurueck ? 0 : nestVoll} />
|
||||
{noaGeht ? (
|
||||
<Geher figur="noa" spiegeln x={noaX} y={BODEN} />
|
||||
) : hocke ? (
|
||||
<NoaHocke start={hocke.start} auf={hocke.auf} x={noaX + schieben * 10} y={BODEN} stauchY={1 - 0.03 * beugen} drehung={beugen * 3 + schieben * 2} />
|
||||
<Geher figur="noa" spiegeln x={noaX} y={BODEN} tempo={1.25} />
|
||||
) : (
|
||||
<Figur pose={noaPose} x={noaX} y={BODEN} stauchY={atmen(f, 2)} />
|
||||
<Figur
|
||||
pose={noaPose}
|
||||
x={noaX}
|
||||
y={BODEN}
|
||||
stauchY={atmen(f, 2) * hockeStauch * (inHocke ? 1 - 0.03 * beugen : 1)}
|
||||
drehung={inHocke ? beugen * 3 : 0}
|
||||
blende={HOCKE_BLENDE}
|
||||
/>
|
||||
)}
|
||||
{/* das große Blatt aus Szene 3 bleibt liegen */}
|
||||
{f < zurueck + 40 && <Blatt x={GROSSES_BLATT_X} y={BODEN - 8} r={88} s={2.6} farbe="#c8642c" deckkraft={f > zurueck ? 1 - (f - zurueck) / 40 : 1} />}
|
||||
|
|
@ -230,10 +239,11 @@ export const Szene4: React.FC = () => {
|
|||
<Figur pose={pinoPose} x={NEST_X} y={pinoImNestY} schatten={false} />
|
||||
</div>
|
||||
) : pinoGeht ? (
|
||||
<Geher figur="pino" spiegeln={pinoSpiegel} x={pinoX} y={BODEN} tempo={1.3} />
|
||||
<Geher figur="pino" spiegeln={pinoSpiegel} x={pinoX} y={BODEN} tempo={1.25} />
|
||||
) : (
|
||||
<Figur
|
||||
pose={pinoPose}
|
||||
spiegeln={pinoZuNoa}
|
||||
x={pinoX}
|
||||
y={BODEN - 6 * sitzt + zaehlHuepfer + huepfer(f, niesen + 2, 30, 12) + insNest}
|
||||
stauchY={atmen(f, 1) * hopfStauch(f, niesen + 2) * hopfStauch(f, z.bild5.start - 20, 20)}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ export const Szene5: React.FC = () => {
|
|||
stauchY={atmen(f, 3, 0.006)}
|
||||
/>
|
||||
) : (
|
||||
<ClipFigur name="lulu_C9_hals-runter" start={z.bild3.start + 10} tempo={1.4} bis={3.6} x={LULU_X5} y={BODEN - 10} />
|
||||
<ClipFigur name="lulu_C9_hals-runter" start={z.bild3.start + 10} tempo={1.25} bis={3.6} x={LULU_X5} y={BODEN - 10} />
|
||||
);
|
||||
|
||||
const ideeStart = z.bild4.start;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import {useCurrentFrame} from 'remotion';
|
||||
import {kurve} from '../bewegung';
|
||||
import {ClipFigur, Figur, Kamera} from '../baukasten/figur';
|
||||
import {ClipFigur, Figur, Kamera, POSEN_SKALA} from '../baukasten/figur';
|
||||
import {Blende, Vignette, atmen, huepfer, kamFahrt} from '../baukasten/helfer';
|
||||
import {Funkeln} from '../baukasten/natur';
|
||||
import {KALENDER_TUER1, KalenderAufTisch, KalenderMalen, Ort} from '../baukasten/orte';
|
||||
|
|
@ -143,7 +143,7 @@ export const Szene6: React.FC = () => {
|
|||
]}
|
||||
x={NOA_X6}
|
||||
y={1000}
|
||||
hoehe={N}
|
||||
hoehe={N / POSEN_SKALA.noa_B_sitzt} // Szene ist auf die volle Bildgröße gebaut (Noa sitzt nah an der Kamera am Tisch)
|
||||
stauchY={atmen(f, 2)}
|
||||
drehung={malt ? Math.sin(f * 0.3) * 0.8 : 0}
|
||||
schatten={false}
|
||||
|
|
@ -151,7 +151,7 @@ export const Szene6: React.FC = () => {
|
|||
<Ort name="zimmer_tisch" />
|
||||
{f >= z.malen.start && <KalenderAufTisch p={malP} />}
|
||||
{imSprung ? (
|
||||
<ClipFigur name="pino_C3_freudensprung" start={sprung} tempo={1.4} x={PINO_X6} y={TISCH_Y} hoehe={N * 0.4} />
|
||||
<ClipFigur name="pino_C3_freudensprung" start={sprung} tempo={1.25} x={PINO_X6} y={TISCH_Y} hoehe={N * 0.4} />
|
||||
) : (
|
||||
<Figur
|
||||
pose={[
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ export const Szene7: React.FC = () => {
|
|||
filter={NACHT}
|
||||
/>
|
||||
) : (
|
||||
<ClipFigur name="noa_C4_winken" start={winken} tempo={1.2} x={NOA_X7} y={BODEN} filter={NACHT} />
|
||||
<ClipFigur name="noa_C4_winken" start={winken} tempo={1.25} x={NOA_X7} y={BODEN} filter={NACHT} />
|
||||
)}
|
||||
<Nest x={NEST_X} y={BODEN} seite="hinten" filter={NACHT} />
|
||||
<div style={{position: 'absolute', inset: 0, clipPath: `inset(0 0 ${1080 - BODEN - 4}px 0)`}}>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
{
|
||||
"modell": "eleven_multilingual_v2",
|
||||
"modell": "eleven_v4",
|
||||
"format": "mp3_44100_128",
|
||||
"einstellungen": {"stability": 0.5, "similarity_boost": 0.75, "style": 0.2},
|
||||
"einstellungen": {
|
||||
"stability": 0.5,
|
||||
"similarity_boost": 0.75
|
||||
},
|
||||
"stimmen": {
|
||||
"ERZ": "",
|
||||
"ERZ": "8TMmdpPgqHKvDOGYP2lN",
|
||||
"PINO": "",
|
||||
"NOA": "",
|
||||
"LULU": "",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
// … F02 --probe nur anzeigen, was erzeugt würde (kostet nichts)
|
||||
// … F02 --sprache es andere Sprache (de, es, fr; ohne Angabe de)
|
||||
//
|
||||
// Der Schlüssel kommt nur aus der Umgebungsvariable, nie aus einer Datei im Repo.
|
||||
// Der Schlüssel kommt aus ELEVENLABS_API_KEY bzw. aus youtube/.env.local (angelegt mit
|
||||
// `npx -y @elevenlabs/cli@latest onboard init`, nicht im Git) – nie aus dem Code.
|
||||
// Stimmen-IDs und Modell stehen in werkzeuge/stimmen.config.json (je Sprache
|
||||
// lassen sich dort unter „sprachen“ eigene Stimmen eintragen).
|
||||
// Aufnahmen landen in public/stimmen/folgeNN/<sprache>/ (nicht im Git; auf dem VPS ein
|
||||
|
|
@ -19,9 +20,14 @@ import {spawnSync} from 'node:child_process';
|
|||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import {fileURLToPath} from 'node:url';
|
||||
import {Readable} from 'node:stream';
|
||||
import {ElevenLabsClient} from '@elevenlabs/elevenlabs-js';
|
||||
|
||||
const WURZEL = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
process.chdir(WURZEL);
|
||||
// Schlüssel aus der .env.local im Repo-Wurzelordner laden (falls nicht schon gesetzt)
|
||||
const envDatei = path.join(WURZEL, '..', '.env.local');
|
||||
if (!process.env.ELEVENLABS_API_KEY && fs.existsSync(envDatei)) process.loadEnvFile(envDatei);
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const kuerzel = args.find((a) => !a.startsWith('--'));
|
||||
|
|
@ -42,7 +48,8 @@ const cfg = JSON.parse(fs.readFileSync('werkzeuge/stimmen.config.json', 'utf8'))
|
|||
einstellungen?: Record<string, unknown>;
|
||||
};
|
||||
const schluessel = process.env.ELEVENLABS_API_KEY;
|
||||
if (!schluessel && !probe) throw new Error('ELEVENLABS_API_KEY ist nicht gesetzt.');
|
||||
if (!schluessel && !probe) throw new Error('ELEVENLABS_API_KEY ist nicht gesetzt (siehe youtube/.env.local).');
|
||||
const client = probe ? null : new ElevenLabsClient(); // liest ELEVENLABS_API_KEY
|
||||
|
||||
const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
||||
const ausgabe = spawnSync(npx, ['tsx', 'werkzeuge/szenenliste.mts', `./src/folge${nummer}/Folge${nummer}.tsx`, kuerzel], {encoding: 'utf8', shell: process.platform === 'win32', env: {...process.env, SPRACHE: sprache}});
|
||||
|
|
@ -91,13 +98,17 @@ for (const s of szenen) {
|
|||
console.log(`~ ${schl} (${z.wer}): ${z.text}`);
|
||||
continue;
|
||||
}
|
||||
const antwort = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${stimme}?output_format=${cfg.format}`, {
|
||||
method: 'POST',
|
||||
headers: {'xi-api-key': schluessel!, 'Content-Type': 'application/json', Accept: 'audio/mpeg'},
|
||||
body: JSON.stringify({text: z.text, model_id: cfg.modell, voice_settings: cfg.einstellungen}),
|
||||
// Sprache festlegen (bessere Aussprache); eleven_v4 kennt nur stability und similarity_boost
|
||||
const audio = await client!.textToSpeech.convert(stimme, {
|
||||
text: z.text,
|
||||
modelId: cfg.modell,
|
||||
languageCode: sprache,
|
||||
outputFormat: cfg.format as never,
|
||||
voiceSettings: {stability: cfg.einstellungen?.stability as number, similarityBoost: cfg.einstellungen?.similarity_boost as number},
|
||||
});
|
||||
if (!antwort.ok) throw new Error(`${schl}: ElevenLabs antwortet ${antwort.status} ${await antwort.text()}`);
|
||||
fs.writeFileSync(pfad, Buffer.from(await antwort.arrayBuffer()));
|
||||
const teile: Buffer[] = [];
|
||||
for await (const t of Readable.fromWeb(audio as never)) teile.push(Buffer.from(t));
|
||||
fs.writeFileSync(pfad, Buffer.concat(teile));
|
||||
aufnahmen[schl] = {datei, dauer: laenge(pfad), text: z.text};
|
||||
fs.writeFileSync(jsonDatei, JSON.stringify(aufnahmen, null, '\t') + '\n');
|
||||
erzeugt++;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ title: YouTube-Kinderkanal mit KI-Videos — Ziel, Strategie, Stand
|
|||
tags: [projekt, youtube, kinder, ki-video, monetarisierung, mehrsprachig, feiertage, higgsfield, adventskalender, pino]
|
||||
description: Wenn es um den geplanten YouTube-Kanal für Kindervideos geht — Konzept, Wachstum, Sprachen, Feiertagsplanung, nächste Schritte oder „machen wir weiter mit YouTube".
|
||||
erstellt: 2026-10-25
|
||||
aktualisiert: 2026-10-28
|
||||
aktualisiert: 2026-11-02
|
||||
---
|
||||
|
||||
## ENTSCHIEDEN 02.11.2026
|
||||
|
|
@ -11,7 +11,34 @@ aktualisiert: 2026-10-28
|
|||
- **Kanalname: „Pinos kleine Welt“** (DE). ES „El pequeño mundo de Pino“ und FR „Le petit monde de Pino“ (ebenfalls entschieden 02.11.). Im ganzen Repo umgestellt (Intro-Titel und gesprochenes Intro, Kanaltext, Titelmuster, Agenten). Ältere Einträge zum Kanalnamen weiter unten sind damit erledigt.
|
||||
- **Format für alle neuen Folgen: Remotion nach dem Muster von Folge 2** (`remotion/src/folge02/`). Offen vor Folge 3: Sprechtexte je Sprache getrennt (DE/ES/FR) und nur an einer Stelle; Musik/Geräusche; Stimmen aufnehmen.
|
||||
|
||||
## >>> STAND SESSION-ENDE 28.10.2026 — 29.10. GENAU HIER WEITERMACHEN <<<
|
||||
## >>> STAND SESSION-ENDE 02.11.2026 — 03.11. GENAU HIER WEITERMACHEN <<<
|
||||
|
||||
**Nächster Schritt: Stimmen (ElevenLabs).**
|
||||
1. Nutzerin prüft den ElevenLabs-API-Schlüssel **…55ad** (Developers → API Keys): Recht **Voices = Write** fehlt noch
|
||||
(Fehler `missing_permissions voices_write`, auch nach ihrer Freigabe am 02.11. abends) → danach erneut versuchen.
|
||||
2. Mit Schreibrecht: Erzählstimme **Gregory Grumble `8TMmdpPgqHKvDOGYP2lN`** (Bibliothek, DE/ES/FR geprüft; von Nutzerin
|
||||
gewählt, schon in `remotion/werkzeuge/stimmen.config.json` als ERZ) per `client.voices.share(public_owner_id, id, …)` zu
|
||||
„Meine Stimmen“ hinzufügen.
|
||||
3. Nutzerin wählt Entwurf je Figur (Voice Design, `textToVoice.design`, eleven_ttv_v3), dann `client.textToVoice.create(
|
||||
{voiceName, voiceDescription, generatedVoiceId})` → Voice-IDs in `stimmen.config.json` (PINO/NOA/LULU):
|
||||
- Pino: noch keine Wahl — Entwürfe 1 `ltJMmAFouy3MnfG3mjaf`, 2 `yRkKzJ2oVloml0Y5VkgX`, 3 `UEaXv1VRYXWLjjmmwc8F`
|
||||
- Noa: noch keine Wahl — 1 `d0nYhi0uYakUNAHn5VgB`, 2 `rzvlLFwRO4hNYHzYU0y7`, 3 `zPreEwpmnSlF8Z0JQd5U`
|
||||
- Lulu: **Entwurf 1 `TAxEFZVDGH6NHy0wH1Lh` oder 3 `I9BlCOvyDbKlCBCrrLeU`** gefallen (zwischen beiden entscheiden)
|
||||
- Hörproben: `C:\Users\lk-lo\Pino-Kanal\Stimmen-Entwuerfe\` (+ entwuerfe.json). generatedVoiceIds können verfallen →
|
||||
notfalls neu entwerfen (Beschreibungen siehe unten).
|
||||
- Beschreibungen, die durchgingen: „An adult (female) voice actor/actress performing … in an animated cartoon“ + Stimmart.
|
||||
ElevenLabs blockiert alles, was als Kind beschrieben wird (auch Probetext „Ich bin erst fünf!“) — keine Umwege.
|
||||
4. Dann `npx tsx werkzeuge/stimmen.mts F01 --probe`, danach echt erzeugen (DE, dann ES/FR mit `--sprache`), Folgen mit Ton rendern.
|
||||
|
||||
**Stand der Folgen (Remotion, beide vollständig, Entwürfe ohne Ton):** `Pino-Kanal\F01-Entwurf.mp4`, `F02-Entwurf.mp4`
|
||||
(+ `-klein.mp4` fürs Handy). Am 02.11. umgesetzt: Folge 1 komplett neu (Herbstgarten, alter Apfelbaum, Laubhaufen wie
|
||||
Higgsfield, Apfel-/Blumenübergabe), Folge 2 nach denselben Lehren überarbeitet, ruckelfreie Clips (Tempo 1,25),
|
||||
POSEN_SKALA (Hocke/Sitzen/Liegen), Lulus Clip-Zoom zurückgerechnet (`clipkorrektur.ts`), neue Pose `noa_A_klatschen`
|
||||
(1,5 Higgsfield-Credits) – Noa klatscht im Lied, Bilderbuch-Vögel, Kalender ohne Stift.
|
||||
**Kanäle:** DE „Pinos kleine Welt“ + ES „El pequeño mundo de Pino“ eingerichtet (Beschreibung, Profilbild, Banner, Land,
|
||||
Schlagwörter); FR wartet auf Ausweisprüfung bei YouTube. „Speziell für Kinder“ setzt die Nutzerin selbst.
|
||||
|
||||
## (älter) STAND SESSION-ENDE 28.10.2026 — 29.10. GENAU HIER WEITERMACHEN <<<
|
||||
(Details in den 28.10.-Absätzen direkt darunter.)
|
||||
|
||||
**Nutzerin: „wir machen morgen genau da weiter“ = Lulu-Blumen-Szene in Folge 1 neu.** Kritik 28.10.: Lulu-Versuch, die Blume
|
||||
|
|
|
|||
Loading…
Reference in a new issue