"""Eğitim kurgusu: kesit listesi (kurgu.json), mpv EDL'leri ve Premiere XML.

Girdi:  zaman.json (klipler), Transkript/Eğitim_kelimeler.json, poz.json (kare başı kişi/poz, WSL'de YOLO ile)
Çıktı:  Önizleme/kurgu/kurgu.json, kurgu_video.edl, kurgu_ses.edl, Egitim_kurgu.xml (Premiere / FCP7 XML)

Kurallar
- Bölümler: giriş + 1. teori (hoş geldiniz → "soruyu düşünün"), 2. bölüm (kamera açılınca ilk cümle → selamlaşma).
- Teori kısımlarında 4 sn'den uzun konuşmasız boşluklar 1.2 sn'ye kısaltılır. Egzersiz/doğaçlama kısmında
  (pantomim var) sessizliğe dokunulmaz.
- Kamera seçimi kare başı puanla: konuşanın (hoca ya da seyirci) görünürlüğü, yüzün kameraya dönüklüğü,
  kişinin büyüklüğü, görüntü netliği (el kamerası sallantısı). Kesimler kelime aralarına (≥0.25 sn) oturtulur.
  Çekim süresi teoride 5–20 sn, egzersizde 3–15 sn. Egzersiz kısmında Kamera A (el kamerası) öncelikli.
- Ses: miks, kesitlerle aynı gerçek saat aralığından (senkron kendiliğinden korunur).
"""
import json, os, bisect
import numpy as np

KOK = "/home/stark/Desktop/Eğitim - 01"
OUT = f"{KOK}/Önizleme/kurgu"
FPS = 50
z = json.load(open(f"{KOK}/Önizleme/medya/zaman.json"))["bolumler"]["Eğitim"]
W = json.load(open(f"{KOK}/Transkript/Eğitim_kelimeler.json"))
POZ_YOL = f"{OUT}/poz.json"
MIKS = z["ses"]["Miks"][0]

# --- bölümler (gerçek saat, sn)
BOLUMLER = [
    dict(ad="1. bölüm", bas=71222.6, son=73480.6, tur="teori"),
    dict(ad="2. bölüm", bas=74726.4, son=75999.0, tur="teori"),      # ilk tam cümleye kaydırılır
    dict(ad="Egzersiz ve doğaçlama", bas=75999.0, son=77692.0, tur="egzersiz"),
]
# 21:06:39 civarı "Benim girizgah kısmım bu kadardı... ayağa kalkın" → egzersiz başı
for x in W:
    if 75950 < x["saat"] < 76200 and x["kelime"].lower().startswith("girizgah"):
        BOLUMLER[1]["son"] = BOLUMLER[2]["bas"] = x["saat"] - 3.0
        break

kel_bas = [x["saat"] for x in W]


def cumle_basi(t):
    """t'den sonraki ilk cümle başı (önceki kelime . ? ! ile bitiyor)."""
    i = bisect.bisect_left(kel_bas, t)
    while i < len(W) and not (i > 0 and W[i - 1]["kelime"].rstrip()[-1:] in ".?!"):
        i += 1
    return W[i]["saat"] - 0.25 if i < len(W) else t


for i, x in enumerate(W):                                   # 1. bölüm tam "Arkadaşlar hepiniz hoş geldiniz" ile başlar
    if 71100 < x["saat"] < 71400 and x["kelime"].startswith("Arkadaşlar") and W[i + 1]["kelime"].lower().startswith("hepiniz"):
        BOLUMLER[0]["bas"] = x["saat"] - 0.12; break
A_bas = min(k["bas"] for k in z["video"]["A"] if k["bas"] > 74000)
BOLUMLER[1]["bas"] = cumle_basi(A_bas + 0.3)


# --- konuşmasız boşlukları kısalt (sadece teori)
def parcala(b):
    """Kesim yok: bölümün tamamı (sessizlikler, nefes araları dahil). Ayıklama kurgucuya bırakıldı."""
    return [(b["bas"], b["son"])]


# --- kamera puanı (poz.json yoksa: hep B, egzersizde A)
def klip_bul(kam, t):
    for k in z["video"][kam]:
        if k["bas"] + 0.05 <= t < k["bas"] + k["sure"] - 0.05:
            return k
    return None


puan = {"A": {}, "B": {}}
if os.path.exists(POZ_YOL):
    P = json.load(open(POZ_YOL))
    for kam in "AB":
        for k in z["video"][kam]:
            ad = os.path.basename(k["yol"])[:-4]
            net_hepsi = [s[1] for s in P.get(ad, [])]
            netm = np.median(net_hepsi) if net_hepsi else 1
            for t, net, kisiler in P.get(ad, []):
                # ana kişi: en büyük ayakta duran, yoksa en büyük
                ana = max(kisiler, key=lambda p: (p[5], p[3] - p[1]), default=None)
                if ana is None:
                    s = 0.0
                else:
                    boy = ana[3] - ana[1]; orta = (ana[0] + ana[2]) / 2
                    s = 0.45 * ana[4] + 0.35 * min(boy / 0.7, 1) + 0.2 * (1 - abs(orta - 0.5) * 2)
                    if ana[0] < 0.01 or ana[2] > 0.99: s *= 0.7          # kadrajdan taşıyor
                s *= min(net / (0.6 * netm), 1.0)                      # bulanık/sallantılı kare
                puan[kam][round(t / 2) * 2] = (s, len(kisiler))


# --- kamera hareketi (hareket.json: kişiler maskelenmiş arka plan farkı, 2 sn arayla)
hareketli = {"A": set(), "B": set()}
if os.path.exists(f"{OUT}/hareket.json"):
    for ad, v in json.load(open(f"{OUT}/hareket.json")).items():
        kam = "A" if ad.startswith("C19") else "B"
        for t, ham, _, kay in v:
            if ham > 15 or (ham > 8 and kay > 0):              # arka plan kaydı: kamera döndü/sallandı
                for g in (round(t / 2) * 2, round(t / 2) * 2 - 2):   # hareket önceki kareden bu kareye kadar
                    hareketli[kam].add(g)

# sert hareket (hızlı çevirme, savrulma): el kamerasının oyuncuyu takip etmesi değil, sadece en sert ~%15
sert_hareket = {"A": set(), "B": set()}
if os.path.exists(f"{OUT}/hareket.json"):
    for ad, v in json.load(open(f"{OUT}/hareket.json")).items():
        kam = "A" if ad.startswith("C19") else "B"
        for t, ham, _, kay in v:
            if ham > 45:
                for g in (round(t / 2) * 2, round(t / 2) * 2 - 2): sert_hareket[kam].add(g)

# --- görüntülü model kararları (vlm.jsonl, WSL'de Qwen): kamera başına puan, hareket, konuşan görünüyor mu
vlm = {}
if os.path.exists(f"{OUT}/vlm.jsonl"):
    for l in open(f"{OUT}/vlm.jsonl"):
        try: r = json.loads(l)
        except ValueError: continue
        if "t" in r and "hata" not in r and "yok" not in r: vlm[int(round(r["t"]))] = r


def p_al(kam, t):
    if klip_bul(kam, t) is None:
        return -1.0
    g = round(t / 2) * 2
    r = vlm.get(int(g)) or vlm.get(int(g) - 2) or vlm.get(int(g) + 2)
    if r and isinstance(r.get(kam), dict):
        v = r[kam]; s = float(v.get("puan", 5)) / 10
        if v.get("hareketli_bulanik"): s -= 0.5
        if v.get("konusan_veya_oynayan_gorunuyor") is False: s -= 0.3
        if v.get("gorunen") == "kimse": s -= 0.3
    else:
        y = puan[kam].get(g) or puan[kam].get(g - 2) or puan[kam].get(g + 2)
        s = y[0] if y else 0.3
    if g in hareketli[kam]: s -= 0.4
    if kam == "B": s = max(s, 0.25)      # B sabit kamera: ikisi de kötüyse B'den devam (kesilmez)
    return max(s, -0.9)


def kotu_an(t):
    """İki kamera da kullanılmaz durumda mı (hareketli / konuşan görünmüyor / yok)."""
    return max(p_al("A", t), p_al("B", t)) < 0.25


def seyirci_konusuyor(t0, t1):
    ks = [x for x in W if t0 <= x["saat"] < t1]
    return ks and sum(x["konusmaci"] != "EĞİTMEN" for x in ks) > 0.6 * len(ks)


def kesim_noktalari(t0, t1):
    """Kelime arası (≥0.25 sn) noktalar."""
    i = bisect.bisect_left(kel_bas, t0); out = []
    while i + 1 < len(W) and W[i]["saat"] < t1:
        if W[i + 1]["saat"] - W[i]["bitis"] >= 0.25:
            out.append((W[i]["bitis"] + W[i + 1]["saat"]) / 2)
        i += 1
    return out


def kamera_sec(a, b, tur, onceki=None):
    """[a, b) aralığını kamera çekimlerine böler. onceki: boşluk kesiminden önceki kamera (zıplama olmasın diye değiştirilir)."""
    en_az, en_cok = (5.0, 20.0) if tur == "teori" else (3.0, 15.0)
    nok = [t for t in kesim_noktalari(a, b) if a + en_az <= t <= b - en_az] + [b]
    cekim = []; t = a
    zorla = onceki
    while t < b - 0.05:
        aday = [n for n in nok if t + en_az <= n <= t + en_cok] or [min([n for n in nok if n > t] or [b])]
        en_iyi = None
        for n in aday:
            for kam in "AB":
                orn = [p_al(kam, x) for x in np.arange(t, n, 2.0)] or [p_al(kam, t)]
                if min(orn) < 0: continue                              # o kamerada görüntü yok
                s = float(np.mean(orn))
                if tur == "egzersiz" and kam == "A" and min(orn) > 0.3: s += 0.05   # el kamerası, sadece sabit/iyiyken
                if seyirci_konusuyor(t, n):
                    s += 0.1 * np.mean([puan[kam].get(round(x / 2) * 2, (0, 0))[1] for x in np.arange(t, n, 2.0)] or [0]) / 4
                if kam == onceki: s -= 0.12                            # aynı kamerada kalma cezası (çeşitlilik)
                if zorla and t == a and kam == zorla: s -= 1.0          # boşluk kesiminde aynı kamera = zıplama
                s += 0.02 * min(n - t, 12) / 12                        # çok kısa çekimlerden kaçın
                if en_iyi is None or s > en_iyi[0]:
                    en_iyi = (s, kam, n)
        if en_iyi is None:                                             # iki kamerada da yok (olmamalı)
            n = aday[0]; kam = onceki or "B"
        else:
            _, kam, n = en_iyi
        if cekim and cekim[-1]["kamera"] == kam:
            cekim[-1]["son"] = n
        else:
            cekim.append(dict(bas=t, son=n, kamera=kam))
        onceki = kam; t = n
    return cekim


# --- egzersiz/doğaçlama: saniyelik inceleme (vlm_egzersiz.jsonl) — sırası gelen kişinin görünürlüğü
vlm2 = {}
if os.path.exists(f"{OUT}/vlm_egzersiz.jsonl"):
    for l in open(f"{OUT}/vlm_egzersiz.jsonl"):
        try: r = json.loads(l)
        except ValueError: continue
        if "t" in r and "hata" not in r and "yok" not in r: vlm2[int(round(r["t"]))] = r
GORUNUR = {"tam": 1.0, "cogu": 0.75, "az": 0.2, "yok": 0.0}


def p2(kam, t):
    """Saniyelik puan: sırası gelen kişi ne kadar görünüyor + kamera durumu."""
    if klip_bul(kam, t) is None: return -1.0
    r = vlm2.get(int(t)) or vlm2.get(int(t) - 1) or vlm2.get(int(t) + 1)   # teori 2 sn'de bir incelendi
    if not r or not isinstance(r.get(kam), dict): return p_al(kam, t)
    v = r[kam]; s = 0.6 * GORUNUR.get(v.get("g"), 0.3) + 0.3 * float(v.get("p", 5)) / 10 + 0.1 * float(v.get("y", 0))
    if v.get("h"): s -= 0.4
    if r.get("t_") == kam: s += 0.15                        # modelin bu an için kendi tercihi
    if round(t / 2) * 2 in sert_hareket[kam]: s -= 0.3
    return s


def kamera_sec_egzersiz(a, b, onceki=None, en_az=2):
    """Saniye saniye: o an sırası gelen kişiyi en iyi gösteren kamera. Kişi bir kamerada azalınca/kaybolunca
    öbüründe daha iyi görünüyorsa 1 sn içinde geçilir. En kısa çekim 2 sn; kararsız durumda mevcut kamera."""
    ts = np.arange(int(a), int(b) + 1)
    pa = np.array([p2("A", t) for t in ts]); pb = np.array([p2("B", t) for t in ts])
    cur = onceki if onceki in ("A", "B") else ("A" if pa[0] > pb[0] else "B")
    sec = []
    for i in range(len(ts)):
        pc, po = (pa[i], pb[i]) if cur == "A" else (pb[i], pa[i])
        nxt = (pa[i + 1], pb[i + 1]) if i + 1 < len(ts) else (pa[i], pb[i])
        po2 = nxt[1] if cur == "A" else nxt[0]; pc2 = nxt[0] if cur == "A" else nxt[1]
        if pc < 0 or (po > pc + 0.12 and po2 > pc2 + 0.06) or (pc < 0.3 and po > pc + 0.1):
            if po >= 0: cur = "B" if cur == "A" else "A"
        sec.append(cur)
    # 2 sn'den kısa çekimleri komşuya kat
    for _ in range(3):
        i = 0
        while i < len(sec):
            j = i
            while j < len(sec) and sec[j] == sec[i]: j += 1
            if j - i < en_az and 0 < i and j < len(sec) and sec[i - 1] == sec[j]:
                sec[i:j] = [sec[i - 1]] * (j - i)
            i = j
    cekim = []
    for i, t in enumerate(ts):
        bas = max(float(t) - 0.5, a) if i else a
        if not cekim or cekim[-1]["kamera"] != sec[i]:
            if cekim: cekim[-1]["son"] = bas
            cekim.append(dict(bas=bas, son=b, kamera=sec[i]))
    cekim[-1]["son"] = b
    return [c for c in cekim if c["son"] > c["bas"] + 0.05]


def kare(t):
    return round(t * FPS) / FPS


kesitler = []
for b in BOLUMLER:
    son_kam = None
    for a, c in parcala(b):
        kapsam = sum(1 for t in range(int(a), int(c)) if t in vlm2 or t - 1 in vlm2 or t + 1 in vlm2) / max(c - a, 1)
        if kapsam > 0.8:                                        # saniyelik inceleme varsa saniyelik seçim
            cekimler = kamera_sec_egzersiz(a, c, son_kam, en_az=2 if b["tur"] == "egzersiz" else 3)
        else:
            cekimler = kamera_sec(a, c, b["tur"], son_kam)
        # 2.5 sn'den kısa çekimleri komşusuna kat (aynı kesit parçası içinde)
        i = 0
        while len(cekimler) > 1 and i < len(cekimler):
            if cekimler[i]["son"] - cekimler[i]["bas"] < 2.5:
                j = i - 1 if i > 0 else i + 1
                cekimler[j]["bas"] = min(cekimler[j]["bas"], cekimler[i]["bas"]); cekimler[j]["son"] = max(cekimler[j]["son"], cekimler[i]["son"])
                del cekimler[i]; i = 0; continue
            i += 1
        son_kam = cekimler[-1]["kamera"] if cekimler else son_kam
        for ck in cekimler:
            k = klip_bul(ck["kamera"], ck["bas"]) or klip_bul("B" if ck["kamera"] == "A" else "A", ck["bas"])
            kam = ck["kamera"] if klip_bul(ck["kamera"], ck["bas"]) else ("B" if ck["kamera"] == "A" else "A")
            if k is None: continue                                     # iki kamerada da görüntü yok
            son = min(ck["son"], k["bas"] + k["sure"] - 0.05)
            kesitler.append(dict(bolum=b["ad"], kamera=kam, yol=k["yol"], klip_bas=k["bas"],
                                 bas=ck["bas"], son=son))
            if son < ck["son"] - 0.05:                                 # klip bitti, kalan kısım öbür kameradan
                o = "B" if kam == "A" else "A"; k2 = klip_bul(o, son + 0.1)
                if k2:
                    kesitler.append(dict(bolum=b["ad"], kamera=o, yol=k2["yol"], klip_bas=k2["bas"],
                                         bas=son, son=ck["son"]))

os.makedirs(OUT, exist_ok=True)
toplam = sum(k["son"] - k["bas"] for k in kesitler)
json.dump(dict(miks=MIKS, kesitler=kesitler, sure=toplam), open(f"{OUT}/kurgu.json", "w"), ensure_ascii=False, indent=1)


# --- mpv EDL
def edl_yol(y):
    return f"%{len(y.encode())}%{y}"


with open(f"{OUT}/kurgu_video.edl", "w") as f:
    f.write("# mpv EDL v0\n")
    for k in kesitler:
        f.write(f"{edl_yol(k['yol'])},{k['bas'] - k['klip_bas']:.4f},{k['son'] - k['bas']:.4f}\n")
with open(f"{OUT}/kurgu_ses.edl", "w") as f:
    f.write("# mpv EDL v0\n")
    for k in kesitler:
        f.write(f"{edl_yol(MIKS['yol'])},{k['bas'] - MIKS['bas']:.4f},{k['son'] - k['bas']:.4f}\n")

# --- Premiere XML (FCP7 xmeml v4)
from xml.sax.saxutils import escape
from urllib.parse import quote


def url(y):
    return "file://localhost" + quote(y)


dosyalar = {}
def dosya_xml(y, video, sure_kare):
    if y in dosyalar:
        return f'<file id="{dosyalar[y]}"/>'
    fid = f"dosya-{len(dosyalar) + 1}"; dosyalar[y] = fid
    med = ("<video><samplecharacteristics><rate><timebase>50</timebase><ntsc>FALSE</ntsc></rate>"
           "<width>3840</width><height>2160</height></samplecharacteristics></video>" if video else "")
    med += "<audio><samplecharacteristics><depth>24</depth><samplerate>48000</samplerate></samplecharacteristics><channelcount>1</channelcount></audio>"
    return (f'<file id="{fid}"><name>{escape(os.path.basename(y))}</name><pathurl>{url(y)}</pathurl>'
            f"<rate><timebase>50</timebase><ntsc>FALSE</ntsc></rate><duration>{sure_kare}</duration><media>{med}</media></file>")


def sure_bul(y):
    for d in (z["video"]["A"], z["video"]["B"]):
        for k in d:
            if k["yol"] == y: return round(k["sure"] * FPS)
    return round(MIKS["sure"] * FPS)


v_items, a_items = [], []
rate = "<rate><timebase>50</timebase><ntsc>FALSE</ntsc></rate>"
# Kesim noktaları her bölümün başına göre ORTAK 50 fps ızgarada yuvarlanır (hata birikmez, kesimler arası boşluk olmaz).
# Ses her bölümde tek, kesintisiz parça.
pos = 0; i = 0
bolum_gruplari = []
for k in kesitler:
    if bolum_gruplari and abs(bolum_gruplari[-1][-1]["son"] - k["bas"]) < 0.05: bolum_gruplari[-1].append(k)
    else: bolum_gruplari.append([k])
for g in bolum_gruplari:
    gb = g[0]["bas"]; gn = round((g[-1]["son"] - gb) * FPS)
    for k in g:
        s0 = pos + round((k["bas"] - gb) * FPS); s1 = pos + round((k["son"] - gb) * FPS)
        if s1 <= s0: continue
        i_in = round((k["bas"] - k["klip_bas"]) * FPS)
        v_items.append(f'<clipitem id="v{i}"><name>{escape(os.path.basename(k["yol"]))} ({k["kamera"]})</name>{rate}'
                       f"<start>{s0}</start><end>{s1}</end><in>{i_in}</in><out>{i_in + s1 - s0}</out>"
                       f'{dosya_xml(k["yol"], True, sure_bul(k["yol"]))}</clipitem>'); i += 1
    a_in = round((gb - MIKS["bas"]) * FPS)
    a_items.append(f'<clipitem id="a{len(a_items)}"><name>Eğitim_miks</name>{rate}'
                   f"<start>{pos}</start><end>{pos + gn}</end><in>{a_in}</in><out>{a_in + gn}</out>"
                   f'{dosya_xml(MIKS["yol"], False, sure_bul(MIKS["yol"]))}<sourcetrack><mediatype>audio</mediatype><trackindex>1</trackindex></sourcetrack></clipitem>')
    pos += gn
xml = ('<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE xmeml>\n<xmeml version="4"><sequence id="egitim01">'
       f"<name>Eğitim 01 – kurgu</name><duration>{pos}</duration><rate><timebase>50</timebase><ntsc>FALSE</ntsc></rate>"
       "<media><video><format><samplecharacteristics><rate><timebase>50</timebase><ntsc>FALSE</ntsc></rate>"
       "<width>3840</width><height>2160</height><pixelaspectratio>square</pixelaspectratio></samplecharacteristics></format>"
       f"<track>{''.join(v_items)}</track></video>"
       f"<audio><track>{''.join(a_items)}</track></audio></media></sequence></xmeml>\n")
open(f"{OUT}/Egitim_kurgu.xml", "w", encoding="utf-8").write(xml)

say = {"A": 0, "B": 0}
for k in kesitler: say[k["kamera"]] += k["son"] - k["bas"]
print(f"{len(kesitler)} kesit, {toplam / 60:.1f} dk; Kamera A {say['A'] / 60:.1f} dk, B {say['B'] / 60:.1f} dk")
for b in BOLUMLER:
    ks = [k for k in kesitler if k["bolum"] == b["ad"]]
    print(f"  {b['ad']}: {len(ks)} kesit, {sum(k['son'] - k['bas'] for k in ks) / 60:.1f} dk, ort. çekim {np.mean([k['son'] - k['bas'] for k in ks]):.1f} sn")
