"""Kamera A sesinden (alıcı Merged: Mik 1 + Mik 2) eğitmen mikrofonunu (Mik 2) çıkarır → kalan ≈ seyirci mikrofonu.

Mik 2'nin vericide kaydedilen temiz WAV'ı referanstır. Kameraya kablosuz giden kopya ile arasında
~0.5 ms gecikme ve ~2 ppm saat kayması var; her 2 dk'lık parçada 3 sn'lik bloklarla ölçülüp düz
çizgiyle modellenir, Mik 2 buna göre örnek altı hassasiyetle kaydırılır. Sonra her frekans için
karışımdaki Mik 2 aktarımı (seviye/faz) ~12 sn'lik kayan ortalamayla tahmin edilip çıkarılır,
kalan iz hafif bir süzgeçle bastırılır. Sadece numpy + ffmpeg.

Kullanım: python3 seyirci_ayir.py   (zaman.json'daki Eğitim / Kamera A kliplerinden, Mik 1 WAV'ı olmayanlar)
Çıktı: Eğitim/Seyirci ayrılmış/<klip>_Seyirci.WAV (BWF time_reference ile)
"""
import json, os, subprocess
import numpy as np

KOK = "/home/stark/Desktop/Eğitim - 01"
S = 48000; N = 2048; HOP = 512; TAU = 12.0
PARCA = 120.0; BINDIR = 6.0; PAY = 0.02


def oku(p, bas, sure):
    r = subprocess.run(["ffmpeg", "-v", "error", "-nostdin", "-ss", f"{max(bas, 0):.5f}", "-t", f"{sure:.5f}", "-i", p, "-map", "0:a:0",
                        "-ac", "1", "-ar", str(S), "-f", "f32le", "-"], capture_output=True).stdout
    x = np.frombuffer(r, np.float32).astype(np.float64)
    if bas < 0:
        x = np.concatenate([np.zeros(int(-bas * S)), x])
    return x


W = np.hanning(N + 1)[:-1]


def stft(x):
    n = 1 + (len(x) - N) // HOP
    idx = np.arange(N)[None, :] + HOP * np.arange(n)[:, None]
    return np.fft.rfft(x[idx] * W, axis=1).T


def istft(X, uzunluk):
    fr = np.fft.irfft(X.T, n=N, axis=1) * W
    y = np.zeros(uzunluk); z = np.zeros(uzunluk)
    for i, f in enumerate(fr):
        y[i * HOP:i * HOP + N] += f[:max(0, min(N, uzunluk - i * HOP))]; z[i * HOP:i * HOP + N] += (W ** 2)[:max(0, min(N, uzunluk - i * HOP))]
    return y / np.maximum(z, 1e-8)


def gecikmeler(A, M, blok=3 * S):
    out = []; P = int(PAY * S)
    for i in range(0, len(A) - blok, blok):
        a = A[i:i + blok]; m = M[i:i + blok + 2 * P]
        n = 1 << int(np.ceil(np.log2(len(m) + len(a))))
        c = np.fft.irfft(np.fft.rfft(m, n) * np.conj(np.fft.rfft(a, n)), n)[:2 * P + 1]
        k = int(np.argmax(c)); d = 0.0
        if 0 < k < 2 * P:
            y0, y1, y2 = c[k - 1], c[k], c[k + 1]; d = 0.5 * (y0 - y2) / (y0 - 2 * y1 + y2)
        r = c[k] / (np.linalg.norm(a) * np.linalg.norm(m[k:k + blok]) + 1e-12)
        out.append(((i + blok / 2) / S, (k + d) / S, r))
    return np.array(out)


def ayir(A, M, onceki):
    g = gecikmeler(A, M); iy = g[g[:, 2] > 0.85]
    if len(iy) >= 5:
        eg, kes = np.polyfit(iy[:, 0], iy[:, 1], 1)
    else:                                   # yeterli temiz blok yok: önceki parçanın kayması, sabit kısım ortancadan
        eg = onceki[0] if onceki else 0.0
        iy = g[g[:, 2] > 0.6] if len(g[g[:, 2] > 0.6]) else g
        kes = np.median(iy[:, 1] - eg * iy[:, 0])
    SA = stft(A); n = SA.shape[1]; f = np.fft.rfftfreq(N, 1 / S)
    SM = np.empty_like(SA)
    for i in range(n):
        t = (i * HOP + N / 2) / S; dl = (kes + eg * t) * S; k = int(np.floor(dl)); fr = dl - k
        seg = M[i * HOP + k:i * HOP + k + N]
        if len(seg) < N: seg = np.pad(seg, (0, N - len(seg)))
        SM[:, i] = np.fft.rfft(seg * W) * np.exp(2j * np.pi * f * fr / S)
    a = np.exp(-HOP / S / TAU)
    P1 = np.empty_like(SA); Q1 = np.empty(SA.shape); p = np.zeros(len(f), complex); q = np.full(len(f), 1e-12)
    for i in range(n):
        p = a * p + (1 - a) * SA[:, i] * np.conj(SM[:, i]); q = a * q + (1 - a) * np.abs(SM[:, i]) ** 2; P1[:, i] = p; Q1[:, i] = q
    p = np.zeros(len(f), complex); q = np.full(len(f), 1e-12); R = np.empty_like(SA)
    for i in range(n - 1, -1, -1):
        p = a * p + (1 - a) * SA[:, i] * np.conj(SM[:, i]); q = a * q + (1 - a) * np.abs(SM[:, i]) ** 2
        E = (P1[:, i] + p) / (Q1[:, i] + q + 1e-12) * SM[:, i]; Ri = SA[:, i] - E
        R[:, i] = Ri * np.maximum(1 - 0.225 * np.abs(E) ** 2 / (np.abs(Ri) ** 2 + 1e-12), 0.15)
    return istft(R, len(A)), (eg, kes), g


def isle(klip, kbas, ksure, mik2, cikti):
    out = np.zeros(int(ksure * S)); onceki = None; rapor = []
    t = 0.0
    while t < ksure:
        c0 = max(t - BINDIR, 0); c1 = min(t + PARCA + BINDIR, ksure)
        A = oku(klip, c0, c1 - c0)
        m = next(w for w in mik2 if w["bas"] <= kbas + c0 and kbas + c1 <= w["bas"] + w["sure"])
        M = oku(m["yol"], kbas + c0 - m["bas"] - PAY, c1 - c0 + 2 * PAY + 0.1)
        R, onceki, g = ayir(A, M, onceki)
        # yumuşak geçiş: parçanın başında/sonunda BINDIR boyunca
        w = np.ones(len(R))
        if c0 > 0: w[:int(BINDIR * S)] = np.linspace(0, 1, int(BINDIR * S))
        if c1 < ksure: w[-int(BINDIR * S):] = np.linspace(1, 0, int(BINDIR * S))
        i0 = int(c0 * S); L = min(len(R), len(out) - i0)
        out[i0:i0 + L] += (R * w)[:L]
        tek = g[g[:, 2] > 0.9]
        rapor.append((t, onceki[0] * 1e6, len(tek)))
        t += PARCA
    tr = round(kbas * S)
    subprocess.run(["ffmpeg", "-v", "error", "-nostdin", "-y", "-f", "f32le", "-ar", str(S), "-ac", "1", "-i", "-",
                    "-c:a", "pcm_s24le", "-write_bext", "1", "-metadata", f"time_reference={tr}",
                    "-metadata", "comment=Kamera A sesinden Mik 2 çıkarıldı (seyirci_ayir.py)", cikti],
                   input=out.astype(np.float32).tobytes(), check=True)
    return rapor


if __name__ == "__main__":
    z = json.load(open(os.path.join(KOK, "Önizleme/medya/zaman.json")))["bolumler"]["Eğitim"]
    m1 = z["ses"]["Mikrofon 1"]; m2 = z["ses"]["Mikrofon 2"]
    hedef = os.path.join(KOK, "Eğitim", "Seyirci ayrılmış"); os.makedirs(hedef, exist_ok=True)
    for k in z["video"]["A"]:
        orta = k["bas"] + k["sure"] / 2
        if any(w["bas"] <= orta <= w["bas"] + w["sure"] for w in m1 if w["sure"] > 60):
            continue                         # o sırada seyirci mikrofonunun kendi kaydı var
        ad = os.path.basename(k["yol"])[:-4]
        rapor = isle(k["yol"], k["bas"], k["sure"], m2, os.path.join(hedef, f"{ad}_Seyirci.WAV"))
        print(ad, "kayma (ppm) / temiz blok:", " ".join(f"{p:.2f}/{n}" for _, p, n in rapor), flush=True)
