youtube/schnitt/pipeline/letztes_bild.py
Lara c27a7ab9f5 Pino-Material aus Laras Chat und Plan-Dokument ins Repo übernehmen
Alle 44 Tabs des Plan-Dokuments (Drehbücher, Sprechtexte DE/ES/FR,
Clip-Prompts, Adventskalender, Kanaltexte, Produktionsliste) als Markdown
unter drehbuecher/, konzept/, ideen/ und veroeffentlichung/. Dazu Projektstand
und Entscheidungen (wissen/), Schnitt-Pipeline, Shorts-Katalog,
Figuren-Referenzbilder und das Kinderkanal-Team als Subagenten
(.claude/agents/). Videos, Startbilder und Ton bleiben außerhalb des Git.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 21:50:01 +01:00

72 lines
2.9 KiB
Python

# -*- coding: utf-8 -*-
"""Speichert das letzte Bild eines Clips als PNG (fuer Clip-Ketten: 'Ende N' = Startbild von Clip N+1).
python -I letztes_bild.py --clip f01_c08
python -I letztes_bild.py --clip f01_c08 f01_c09 t09_c03
python -I letztes_bild.py --datei D:\\irgendwo\\clip.mp4
python -I letztes_bild.py --ordner folge01 (alle Clips dieses Unterordners von clips\\)
Ausgabe: <ausgabe>\\startbilder\\<clipname>_ende.png (Standard: schnitt\\ausgabe\\startbilder)
Fuer den Projektordner startbilder\\ : --ausgabe <Pino-Kanal> (dann liegt es in <Pino-Kanal>\\startbilder)
und --ausserhalb. Ohne diese Option wird nur unter schnitt\\ geschrieben.
"""
import argparse
import os
import sys
from pathlib import Path
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # noetig bei python -I
import pinolib as P # noqa: E402
def letztes_bild(quelle, ziel, trocken=False):
# -sseof: 1 s vor Ende lesen, -update 1: Datei bei jedem Bild ueberschreiben -> bleibt das letzte
P.ffmpeg_lauf(["-sseof", "-1", "-i", str(quelle), "-an", "-update", "1",
"-frames:v", "100000", "-pix_fmt", "rgb24", str(ziel)],
dry=trocken, was="Letztes Bild")
def main():
P.init_konsole()
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--clip", nargs="*", default=[])
ap.add_argument("--datei", nargs="*", default=[])
ap.add_argument("--ordner", help="Unterordner von clips\\ (z. B. folge01)")
ap.add_argument("--clips-ordner", default=str(P.PROJEKT / "clips"), dest="clips_ordner")
ap.add_argument("--ausgabe", default=str(P.AUSGABE))
ap.add_argument("--ueberschreiben", action="store_true")
ap.add_argument("--ausserhalb", action="store_true")
ap.add_argument("--trocken", action="store_true")
args = ap.parse_args()
quellen = []
for t in args.clip:
for tok in P.expandiere(t):
p = P.clip_pfad(tok, args.clips_ordner)
if p is None:
raise P.Fehler(f"Clip nicht gefunden: {tok}")
quellen.append(p)
quellen += [Path(d) for d in args.datei]
if args.ordner:
quellen += sorted((Path(args.clips_ordner) / args.ordner).glob("*.mp4"))
if not quellen:
raise P.Fehler("Nichts angegeben (--clip, --datei oder --ordner).")
ziel_dir = P.pruefe_ziel(Path(args.ausgabe) / "startbilder", args.ausserhalb)
if not args.trocken:
ziel_dir.mkdir(parents=True, exist_ok=True)
for q in quellen:
z = ziel_dir / (Path(q).stem + "_ende.png")
P.schreibschutz(z, args.ueberschreiben)
letztes_bild(q, z, args.trocken)
if not args.trocken:
v = P.probe(q)["video"]
print(f"{z} ({v['width']}x{v['height']})")
if __name__ == "__main__":
try:
main()
except P.Fehler as e:
print(f"FEHLER: {e}")
sys.exit(1)