#!/usr/bin/env python3 """prompts/ 무결성 검사. 외부 호출 없음. python3 verify_prompts.py --root - 모든 jsonl 행 json.loads 가능 - prompt / text 원문이 원본 jsonl·json·tsv 와 바이트 동일 (UTF-8 인코딩 비교) - music request 가 원본 *.request.json 과 같은 값 + sha256 일치 - final_file·result_file 이 실제로 있음 - templates/examples 를 fill.py 로 채우면 원본 프롬프트와 바이트 동일 - 비밀값 패턴 0건 """ import argparse, hashlib, json, os, re, sys, importlib.util sys.dont_write_bytecode = True HERE = os.path.dirname(os.path.abspath(__file__)) ap = argparse.ArgumentParser() ap.add_argument("--root", required=True, help=" (malfoy-meme 폴더)") ROOT = ap.parse_args().root.rstrip("/") fails, notes = [], [] count = {} def ok(cond, msg): if not cond: fails.append(msg) def load_jsonl(name): rows = [] for n, line in enumerate(open(os.path.join(HERE, name), encoding="utf-8"), 1): try: rows.append(json.loads(line)) except Exception as e: fails.append(f"{name}:{n} json.loads 실패 {e}") count[name] = len(rows) return rows def exists(p, ctx): ok(os.path.exists(os.path.join(ROOT, p)), f"{ctx}: 파일 없음 {p}") def same(a, b, ctx): ok(a.encode("utf-8") == b.encode("utf-8"), f"{ctx}: 원문 불일치") # images for r in load_jsonl("images.jsonl"): s = r["source"] lines = open(os.path.join(ROOT, s["file"]), encoding="utf-8").read().split("\n") src = json.loads(lines[s["line"] - 1]) ok(src["id"] == r["id"], f"images {r['id']}: id 불일치") same(r["prompt"], src["prompt"], f"images {r['id']}") exists(r["final_file"], f"images {r['id']}") for x in r["refs"]: exists(x, f"images {r['id']} ref") # flow for r in load_jsonl("flow_jobs.jsonl"): s = r["source"] src = json.load(open(os.path.join(ROOT, s["file"]), encoding="utf-8"))[s["index"]] ok(src["id"] == r["id"], f"flow {r['id']}: id 불일치") same(r["prompt"], src["prompt"], f"flow {r['id']}") exists(r["result_file"], f"flow {r['id']}") for k in ("first_frame", "last_frame", "video_ref"): if r.get(k): exists(r[k], f"flow {r['id']} {k}") # sfx for r in load_jsonl("audio/sfx.jsonl"): s = r["source"] src = json.load(open(os.path.join(ROOT, s["file"]), encoding="utf-8"))[s["index"]] ok(src[0] == r["id"], f"sfx {r['id']}: id 불일치") same(r["prompt"], src[1], f"sfx {r['id']}") ok(src[2] == r["duration"] and src[3] == r["prompt_influence"], f"sfx {r['id']}: 길이·influence 불일치") exists(r["final_file"], f"sfx {r['id']}") # tts tsv_checked = session_only = 0 for r in load_jsonl("audio/tts.jsonl"): exists(r["final_file"], f"tts {r['line_id']}") exists(r["raw_take"], f"tts {r['line_id']} raw") if r["text_source"].endswith(".tsv"): rows = [l.rstrip("\n").split("\t") for l in open(os.path.join(ROOT, r["text_source"]), encoding="utf-8") if l.strip()] hit = [x for x in rows if x[0] == r["text_source_key"]] ok(len(hit) == 1, f"tts {r['line_id']}: tsv 키 {len(hit)}건") if hit: same(r["text_with_tags"], hit[0][1], f"tts {r['line_id']}") ok(abs(float(hit[0][2]) - r["settings"]["stability"]) < 1e-9, f"tts {r['line_id']}: stability 불일치") tsv_checked += 1 else: session_only += 1 notes.append(f"tts: tsv 대조 {tsv_checked}줄, 세션 기록 출처 {session_only}줄(키트 밖 원본, 키트 생성 시 별도 대조)") # music try: music = json.load(open(os.path.join(HERE, "audio/music.json"), encoding="utf-8")) for ep in music["episodes"]: if not ep.get("request_file"): continue raw = open(os.path.join(ROOT, ep["request_file"]), "rb").read() ok(hashlib.sha256(raw).hexdigest() == ep["request_sha256"], f"music {ep['adopted']}: sha256 불일치") ok(json.loads(raw.decode("utf-8")) == ep["request"], f"music {ep['adopted']}: request 값 불일치") for f in ep.get("request_identical_files", []): ok(open(os.path.join(ROOT, f), "rb").read() == raw, f"music {ep['adopted']}: {f} 바이트 불일치") exists(ep["final_file"], f"music {ep['adopted']}") count["audio/music.json"] = len(music["episodes"]) except Exception as e: fails.append(f"music.json 로드 실패 {e}") # templates T = os.path.join(HERE, "templates") spec = importlib.util.spec_from_file_location("fill", os.path.join(T, "fill.py")) fill = importlib.util.module_from_spec(spec); spec.loader.exec_module(fill) E = lambda n: json.load(open(os.path.join(T, "examples", n), encoding="utf-8")) img_src = {json.loads(l)["id"]: json.loads(l) for l in open(os.path.join(ROOT, "v3_images.jsonl"), encoding="utf-8") if l.strip()} same(fill.fill_text(open(os.path.join(T, "image_template.txt"), encoding="utf-8").read(), E("image_example.vars.json")), img_src["V3_C18_idol"]["prompt"], "template image") v1 = {j["id"]: j for j in json.load(open(os.path.join(ROOT, "06_flow/v1_jobs.json"), encoding="utf-8"))} same(fill.fill_text(open(os.path.join(T, "flow_template.txt"), encoding="utf-8").read(), E("flow_example.vars.json")), v1["V1_S4"]["prompt"], "template flow") tsv = {l.split("\t")[0]: l.rstrip("\n").split("\t") for l in open(os.path.join(ROOT, "04_audio/v3/_work/vo_lines.tsv"), encoding="utf-8")} same(fill.fill_text(open(os.path.join(T, "tts_template.txt"), encoding="utf-8").read(), E("tts_example.vars.json")), tsv["V3_father"][1], "template tts") ok(fill.fill_music(json.load(open(os.path.join(T, "music_plan_template.json"), encoding="utf-8")), E("music_plan_example.vars.json")) == json.load(open(os.path.join(ROOT, "04_audio/_work/bgm_A_swing.request.json"), encoding="utf-8")), "template music: 불일치") for name in ("image_blocks.json", "flow_blocks.json"): json.load(open(os.path.join(T, name), encoding="utf-8")) # secrets PAT = [r"sk-[A-Za-z0-9_\-]{16,}", r"sk_[A-Za-z0-9]{20,}", r"(?i)xi-api-key\"?\s*[:=]\s*\"?[A-Za-z0-9]{16,}", r"(?i)(api[_-]?key|token|secret|password)\"?\s*[:=]\s*\"[A-Za-z0-9_\-]{12,}\"", r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}", r"(?i)x-goog-signature|signature=|expires=\d|token=", r"ghp_[A-Za-z0-9]{20,}", r"AIza[0-9A-Za-z_\-]{30,}", r"/mnt/[a-z]/", r"/home/[a-z]"] hits = [] for dp, _, fs in os.walk(HERE): for f in fs: p = os.path.join(dp, f) if f == "verify_prompts.py": continue txt = open(p, encoding="utf-8", errors="ignore").read() for pat in PAT: for m in re.finditer(pat, txt): hits.append(f"{os.path.relpath(p, HERE)}: {m.group(0)[:30]}") ok(not hits, f"비밀값·절대경로 패턴 {len(hits)}건: {hits[:5]}") print("counts:", json.dumps(count, ensure_ascii=False)) for n in notes: print("note:", n) print("secret_scan_hits:", len(hits)) if fails: print(f"FAIL {len(fails)}") for f in fails: print(" -", f) sys.exit(1) print("PASS")