- 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>
68 lines
3.6 KiB
Python
68 lines
3.6 KiB
Python
# 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")
|