"""립싱크 정합의 **강건한** 통계 = 발화구간/무음구간 입 모션 비.

왜 상관계수를 주 지표로 안 쓰나: 연속 상관(openness·motion vs 포락선)은 같은 자료에서 서로 다른 부호를
낸다 — 입 크기·머리 움직임·박스 포화에 흔들리기 때문이다(실측: ⑤-b 는 상관으론 음수인데 구간별로 보면
화자 귀속이 완벽했다). 반면 "**소리 날 때 무음 때보다 얼마나 더 움직이나**" 는 스케일 불변이고
사람이 읽는 립싱크 개념에 직접 대응한다.

네거티브 = 그 오디오의 시간 역전으로 구간을 다시 잡는다(구간 길이·개수 분포는 같고 위치만 틀어진다).
"""
import argparse
import numpy as np
import importlib.util

spec = importlib.util.spec_from_file_location("an", "analyze.py")
an = importlib.util.module_from_spec(spec)
spec.loader.exec_module(an)
SR, FPS = 16000, 24


def ratio(video, box, skip=0, reverse=False, windows=None, end_s=None):
    """windows=[(start_s, end_s), …] 를 주면 그 구간을 '발화' 로 쓴다(가창처럼 유성구간이 전부라
    자동 검출이 성립 안 할 때). 미지정이면 산출 오디오의 유성구간을 쓴다.

    ★end_s 필수 상황: LTX 는 **영상이 오디오보다 길다**(⑤-c 는 6.04s 영상에 5.18s 오디오).
      그 꼬리를 무음으로 세면 같은 클립이 1.43 과 4.17 사이를 오간다. 경로끼리 비교할 땐
      **오디오 길이로 잘라** 같은 구간을 봐야 한다.
    """
    _, motion, _ = an.mouth_series(video, box)
    motion = motion[skip:]
    if end_s is not None:
        motion = motion[:max(1, int(end_s * FPS) - skip)]
    if windows is None:
        x = an.pcm(video)
        if reverse:
            x = x[::-1].copy()
        windows = [(s["start_s"], s["end_s"]) for s in an.voiced_segments(x)]
    elif reverse:
        # ★명시 windows 에도 역전 네거티브가 성립해야 한다 — 안 그러면 실제값과 네거티브가
        #   똑같이 나와서 "네거티브를 쟀다" 는 착시만 남는다(구간 길이 분포는 보존하고 위치만 뒤집는다).
        span = end_s if end_s is not None else max(b for _, b in windows)
        windows = [(span - b, span - a) for a, b in windows]
    voiced = np.zeros(len(motion), bool)
    for a, b in windows:
        voiced[max(0, int(a * FPS) - skip):max(0, int(b * FPS) - skip)] = True
    if voiced.all() or not voiced.any():
        return None, float(voiced.mean())
    return float(motion[voiced].mean() / motion[~voiced].mean()), float(voiced.mean())


if __name__ == "__main__":
    ap = argparse.ArgumentParser()
    ap.add_argument("video")
    ap.add_argument("box", help="x0,y0,x1,y1")
    ap.add_argument("--skip", type=int, default=0, help="앞 N 프레임 제외(LTX 는 f0 가 입력 이미지)")
    ap.add_argument("--windows", help="발화 구간 'a-b,c-d' (초). 가창처럼 유성비율이 1.0 이라 "
                                      "자동 검출이 성립하지 않을 때 whisper word 타임스탬프로 준다")
    ap.add_argument("--end", type=float, help="이 초 이후 프레임 제외 — 영상이 오디오보다 긴 경로(LTX)와 "
                                              "비교할 땐 **오디오 길이로 잘라야** 같은 구간을 본다")
    a = ap.parse_args()
    box = tuple(float(v) for v in a.box.split(","))
    win = ([tuple(float(t) for t in w.split("-")) for w in a.windows.split(",")]
           if a.windows else None)
    r, frac = ratio(a.video, box, a.skip, windows=win, end_s=a.end)
    rr, _ = ratio(a.video, box, a.skip, reverse=True, windows=win, end_s=a.end)
    if r is None:
        # ★큰소리로 실패한다 — 조용히 None 을 찍으면 "지표가 성립 안 했다" 가 "0 이 나왔다" 로 읽힌다.
        raise SystemExit(f"❌ 유성비율 {frac:.2f} — 대조할 무음 구간이 없어 이 지표는 성립하지 않는다. "
                         f"--windows 로 발화 구간을 직접 줘라(가창은 whisper word 타임스탬프 사용).")
    neg = f"{rr:.2f}" if rr is not None else "성립 X(역전 구간이 전체를 덮음)"
    print(f"{a.video}  발화/무음 모션비 = {r:.2f}   (역전 네거티브 {neg})   유성비율 {frac:.2f}")
