youtube/remotion/werkzeuge/posen.py
Pascal Scholz cbacc2fd97 Remotion: Baukasten für alle Folgen, Szenen einzeln renderbar, Stimmen-Skript
- gemeinsame Bausteine nach src/baukasten/, Bibliothek nach public/bibliothek/, Orte nach public/orte/
- je Szene eine Komposition (F02-01-…), werkzeuge/render.mjs rendert nur geänderte Szenen
  und klebt sie zusammen; --entwurf mit halber Auflösung und eingeblendeter Zeit/Zeile
- werkzeuge/stimmen.mts erzeugt ElevenLabs-Aufnahmen, Zeitplan übernimmt ihre Länge
- Folge 2: Geh- und Hinsetz-Clips statt Standbild-Wechsel, neue Vögel, fallendes Blatt,
  Noa malt den Kalender (Blick von oben)
- ANLEITUNG_PC.md für Studio und Rendern unter Windows

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:43:56 +00:00

68 lines
3.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Bereitet die freigestellten Standbilder der Figuren-Bibliothek für Remotion auf.
#
# Eingabe: freigestellte PNGs in voller Leinwand (1376 × 768, wie aus der
# Bibliothek), z. B. aus frei.py. Alle Bilder einer Figur werden mit demselben
# Rahmen zugeschnitten, damit Posen beim Überblenden deckungsgleich bleiben.
#
# Ausgabe: public/bibliothek/figuren/<pose>.webp und src/folge02/posen.ts
# python3 werkzeuge/posen.py <ordner_freigestellt> [<ordner_clips_frei>]
import json, os, sys, glob
from PIL import Image
QUELLE = sys.argv[1]
CLIPS = sys.argv[2] if len(sys.argv) > 2 else None
HIER = os.path.dirname(os.path.abspath(__file__))
ZIEL = os.path.join(HIER, "..", "public", "bibliothek", "figuren")
TS = os.path.join(HIER, "..", "src", "baukasten", "posen.ts")
os.makedirs(ZIEL, exist_ok=True)
# Referenzpose je Figur: deren Höhe gilt als „Figurhöhe“, deren Fußpunkt als Anker.
REF = {"pino": "pino_ref_sitzend", "noa": "noa_ref_stehend", "lulu": "lulu_A6_blick-rechts"}
# Clip-Leinwand (1284 × 716) ist die verkleinerte Bild-Leinwand (1376 × 768).
K_CLIP = 1376 / 1284
posen = {}
for figur in REF:
dateien = sorted(glob.glob(f"{QUELLE}/{figur}_*.png"))
bilder = {os.path.basename(d)[:-4]: Image.open(d) for d in dateien}
boxen = {n: b.getchannel("A").getbbox() for n, b in bilder.items()}
rahmen = [min(b[0] for b in boxen.values()) - 4, min(b[1] for b in boxen.values()) - 4,
max(b[2] for b in boxen.values()) + 4, max(b[3] for b in boxen.values()) + 4]
rahmen = [max(0, rahmen[0]), max(0, rahmen[1]), min(1376, rahmen[2]), min(768, rahmen[3])]
for n, b in bilder.items():
b.crop(rahmen).save(f"{ZIEL}/{n}.webp", quality=90, method=5)
posen[n] = {"figur": figur, "datei": f"bibliothek/figuren/{n}.webp", "x": rahmen[0], "y": rahmen[1],
"b": rahmen[2] - rahmen[0], "h": rahmen[3] - rahmen[1], "box": boxen[n]}
# A5 (Blick links) ist eine Spiegelung von A6 an der Bildmitte: zurückschieben,
# damit die Figur beim Wechsel A6 ↔ A5 nicht seitlich springt.
for n in posen:
if n.startswith(f"{figur}_A5_"):
a6 = boxen[n.replace("A5_blick-links", "A6_blick-rechts")]
posen[n]["x"] += (a6[0] + a6[2]) / 2 - (boxen[n][0] + boxen[n][2]) / 2
print(figur, len(bilder), rahmen)
figuren = {}
for figur, ref in REF.items():
b = posen[ref]["box"]
figuren[figur] = {"refHoehe": b[3] - b[1], "fussX": (b[0] + b[2]) / 2, "fussY": b[3]}
clips = {}
if CLIPS:
for j in sorted(glob.glob(f"{CLIPS}/*.json")):
n = os.path.basename(j)[:-5]
d = json.load(open(j))
bx = d["box"]
clips[n] = {"figur": n.split("_")[0], "datei": f"bibliothek/clips/{n}.webm",
"x": bx[0] * K_CLIP, "y": bx[1] * K_CLIP, "b": (bx[2] - bx[0]) * K_CLIP,
"h": (bx[3] - bx[1]) * K_CLIP, "frames": d["frames"]}
with open(TS, "w") as f:
f.write("// Automatisch erzeugt von werkzeuge/posen.py – nicht von Hand ändern.\n")
f.write("// Koordinaten in der Bild-Leinwand der Figur (1376 × 768).\n\n")
f.write("export type Pose = {figur: string; datei: string; x: number; y: number; b: number; h: number; box: number[]};\n")
f.write("export type ClipDaten = {figur: string; datei: string; x: number; y: number; b: number; h: number; frames: number};\n\n")
j = lambda o: json.dumps(o, indent="\t")
f.write("export const FIGUREN = " + j(figuren) + " as const;\n\n")
f.write("export const POSEN: Record<string, Pose> = " + j(posen) + ";\n\n")
f.write("export const CLIPS: Record<string, ClipDaten> = " + j(clips) + ";\n")
print("posen.ts", len(posen), "Posen,", len(clips), "Clips")