"""산출 영상을 등간격 프레임 스트립으로 굽는다 (사람 눈으로 입 모양을 볼 수 있게).

각 프레임에 **타임코드 + 그 시점 오디오 RMS(dBFS)** 를 새긴다 — 립싱크 판정은 "입이 열렸나" 가
아니라 "**소리가 나는 시점에** 열렸나" 라서, 두 정보가 같은 그림 안에 있어야 눈으로 판정이 된다.
(③ 에서 무음 구간에 입이 열려 있던 반례가 이 배치 덕에 보였다.)
"""
import argparse
import json
import subprocess

import numpy as np
from PIL import Image, ImageDraw

SR = 16000


def probe(path):
    o = subprocess.run(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
                        "stream=width,height,nb_frames,r_frame_rate", "-of", "json", path],
                       capture_output=True, text=True).stdout
    return json.loads(o)["streams"][0]


def frames(path, w, h):
    raw = subprocess.run(["ffmpeg", "-v", "error", "-i", path, "-vf", f"scale={w}:{h}",
                          "-f", "rawvideo", "-pix_fmt", "rgb24", "-"],
                         capture_output=True, check=True).stdout
    return np.frombuffer(raw, dtype=np.uint8).reshape(-1, h, w, 3)


def audio_db(path):
    p = subprocess.run(["ffmpeg", "-v", "error", "-i", path, "-ac", "1", "-ar", str(SR),
                        "-f", "f32le", "-"], capture_output=True)
    x = np.frombuffer(p.stdout, dtype=np.float32)
    return x


if __name__ == "__main__":
    ap = argparse.ArgumentParser()
    ap.add_argument("video")
    ap.add_argument("out_png")
    ap.add_argument("--cols", type=int, default=8)
    ap.add_argument("--rows", type=int, default=3)
    ap.add_argument("--width", type=int, default=240, help="셀 가로 px")
    a = ap.parse_args()

    v = probe(a.video)
    fps = eval(v["r_frame_rate"])  # noqa: S307 — "24/1" 형식
    cw = a.width
    ch = int(cw * int(v["height"]) / int(v["width"]))
    f = frames(a.video, cw, ch)
    x = audio_db(a.video)

    n = a.cols * a.rows
    idx = np.linspace(0, len(f) - 1, n).astype(int)
    sheet = Image.new("RGB", (cw * a.cols, (ch + 18) * a.rows), (16, 16, 16))
    d = ImageDraw.Draw(sheet)
    for k, i in enumerate(idx):
        r, c = divmod(k, a.cols)
        sheet.paste(Image.fromarray(f[i]), (c * cw, r * (ch + 18)))
        t = i / fps
        s = int(t * SR)
        win = x[max(0, s - 200):s + 200]
        db = 20 * np.log10(np.sqrt((win ** 2).mean()) + 1e-9) if len(win) else -99
        d.text((c * cw + 4, r * (ch + 18) + ch + 3),
               f"f{i} {t:5.2f}s  {db:6.1f}dB", fill=(230, 230, 230))
    sheet.save(a.out_png)
    print(f"→ {a.out_png}  {sheet.size}  ({len(f)} frames @ {fps:g}fps)")
