# -*- coding: utf-8 -*- """Prueft katalog_shorts.csv: Format, Regeln, Laengen, fehlende Clips, Abdeckung der Veroeffentlichungs-Slots. python -I katalog_pruefen.py python -I katalog_pruefen.py --katalog X.csv --clips-ordner D:\\clips --slots-bis 2026-11-27 Liest nur. Schreibt nichts ausser (optional) einem Bericht nach schnitt\\berichte\\ mit --bericht. Slots: je Kanal alle 2 Tage 2 Shorts, Start 08.11.2026 (08., 10., 12. ...). """ import argparse import datetime import os import re import sys sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # noetig bei python -I import pinolib as P # noqa: E402 PFLICHT = ["id", "clips", "tempo", "start_s", "ende_s", "modus", "titel_de", "titel_es", "titel_fr", "frueheste_veroeffentlichung"] def datum(s): return datetime.date.fromisoformat(s) def main(): P.init_konsole() ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument("--katalog", default=str(P.SCHNITT / "katalog_shorts.csv")) ap.add_argument("--clips-ordner", default=str(P.PROJEKT / "clips"), dest="clips_ordner") ap.add_argument("--slots-start", default="2026-11-08", dest="slots_start") ap.add_argument("--slots-bis", nargs="*", default=["2026-11-27", "2026-12-10", "2026-12-31"], dest="slots_bis") ap.add_argument("--bericht", action="store_true") args = ap.parse_args() z = P.lese_csv(args.katalog) out, fehler, warn = [], [], [] if not z: raise P.Fehler("Katalog leer.") fehlend_spalten = [c for c in PFLICHT if c not in z[0]] if fehlend_spalten: raise P.Fehler("Spalten fehlen: " + ", ".join(fehlend_spalten)) ids, vorhanden, fehlt_clips = set(), 0, set() gesamt_clips = set() for r in z: i = r["id"] if i in ids: fehler.append(f"{i}: ID doppelt") ids.add(i) try: tokens = P.expandiere(r["clips"]) tempo = P.zahl(r["tempo"], 1.0) n = len(tokens) d = [] for t in tokens: p = P.clip_pfad(t, args.clips_ordner) gesamt_clips.add(t) if p is None: fehlt_clips.add(t) d.append(5.0) else: d.append(P.dauer(p)) seg = [round(((x) / tempo) * P.FPS) / P.FPS for x in d] laenge = sum(seg) - (n - 1) * 0.5 ende = P.zahl(r["ende_s"], laenge) start = P.zahl(r["start_s"], 0.0) L = min(ende, laenge) - start if not 20 <= L <= 45: fehler.append(f"{i}: Laenge {L:.1f} s ausserhalb 20-45 s") if tempo < 0.8 or tempo > 1.0: fehler.append(f"{i}: tempo {tempo} ausserhalb 0,8-1,0") if ende > laenge + 0.3: warn.append(f"{i}: ende_s {ende} > Clipfolge {laenge:.1f} s") if r["modus"] not in ("crop", "blur") and "|" not in r["modus"] and "," not in r["modus"]: fehler.append(f"{i}: modus '{r['modus']}' ungueltig") for t in tokens: m = re.match(r"^t(\d+)_c", t) if m and datum(r["frueheste_veroeffentlichung"]) < datetime.date(2026, 12, int(m.group(1))): fehler.append(f"{i}: {t} vor Veroeffentlichung von Tuerchen {int(m.group(1))}") m = re.match(r"^f(\d+)_c", t) frei = {1: "2026-11-08", 2: "2026-11-15", 3: "2026-11-29"} if m and int(m.group(1)) in frei and datum(r["frueheste_veroeffentlichung"]) < datum(frei[int(m.group(1))]): fehler.append(f"{i}: {t} vor Veroeffentlichung von Folge {int(m.group(1))}") for l in ("de", "es", "fr"): if not r.get(f"titel_{l}"): fehler.append(f"{i}: titel_{l} fehlt") if re.search(r"(?i)winterschlaf|schl[aƤ]ft ein|nest", r["thema"] + r["titel_de"]) and "Winterschlaf" not in r.get("ton_hinweis", ""): warn.append(f"{i}: Nest/Schlaf-Thema ohne Winterschlaf-Hinweis in ton_hinweis") except P.Fehler as e: fehler.append(f"{i}: {e}") out.append(f"Katalog: {len(z)} Momente, {len(gesamt_clips)} verschiedene Clips verwendet, " f"{len(gesamt_clips) - len(fehlt_clips)} davon unter {args.clips_ordner} vorhanden") if fehlt_clips: out.append(f"Fehlende Clipdateien: {len(fehlt_clips)} (Laengen mit 5,0 s angenommen)") start = datum(args.slots_start) out.append("") out.append("Abdeckung (je Kanal; 2 Shorts alle 2 Tage ab " + args.slots_start + "):") for bis in args.slots_bis: b = datum(bis) tage = (b - start).days slots = (tage // 2 + 1) * 2 if tage >= 0 else 0 verf = sum(1 for r in z if datum(r["frueheste_veroeffentlichung"]) <= b) status = "OK" if verf >= slots else f"FEHLBETRAG {slots - verf}" out.append(f" bis {bis}: {verf} Momente, {slots} Plaetze -> {status}") fenster = {} for r in z: fenster[r.get("fenster", "?")] = fenster.get(r.get("fenster", "?"), 0) + 1 out.append("Momente je Fenster: " + ", ".join(f"{k}: {v}" for k, v in sorted(fenster.items()))) modi = {} for r in z: modi[r["modus"]] = modi.get(r["modus"], 0) + 1 out.append("Modus: " + ", ".join(f"{k}: {v}" for k, v in modi.items())) out.append("") out.append("Fehler:" if fehler else "Fehler: keine") out += [" " + f for f in fehler] out.append("Hinweise:" if warn else "Hinweise: keine") out += [" " + w for w in warn] print("\n".join(out)) if args.bericht: P.BERICHTE.mkdir(parents=True, exist_ok=True) pf = P.BERICHTE / f"katalog_{datetime.datetime.now():%Y%m%d_%H%M%S}.txt" pf.write_text("\n".join(out) + "\n", encoding="utf-8") print("Bericht:", pf) sys.exit(1 if fehler else 0) if __name__ == "__main__": try: main() except P.Fehler as e: print(f"FEHLER: {e}") sys.exit(1)