Blender 원본 검사와 승인 경계를 지키는 로컬 렌더 실행기 추가

This commit is contained in:
NINI
2026-10-09 23:54:55 +09:00
parent 15e84fb7c9
commit 31efdd6681
7 changed files with 1092 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
"""작은 3D 영상과 같은 논리 프레임의 서로 다른 Scene을 실제로 확인한다."""
import argparse
import json
from pathlib import Path
import shutil
import run
from test_runner import fixture, production_fixture, endpoint_fixture, ROOT
from test_runner import write
def main():
parser = argparse.ArgumentParser(description="설치 정보가 아닌 실제 렌더·디코드 검사")
parser.add_argument("--blender", required=True)
parser.add_argument("--ffmpeg", required=True)
parser.add_argument("--ffprobe", required=True)
parser.add_argument("--engine", choices=("CYCLES", "BLENDER_EEVEE"), default="CYCLES")
args = parser.parse_args()
folder, project, artifacts, job = fixture()
if args.engine == "BLENDER_EEVEE":
artifacts["08-scenes"]["payload"]["preview"]["render_profile"].update(engine="BLENDER_EEVEE", device="GPU")
write(ROOT / job["artifact"], artifacts["08-scenes"])
source = ROOT / job["source"]
# fixture의 검사 전 바이트는 생성 원본이 아니다. 실패 흔적은 별도 파일로 보존한다.
source.rename(source.with_suffix(".uninspected"))
run.execute([args.blender, "--background", "--factory-startup", "--disable-autoexec", "--python-exit-code", "1", "--python",
Path(__file__).with_name("smoke_fixture.py"), "--", source])
video = run.render(ROOT, job, args.blender, args.ffmpeg, args.ffprobe)
require = run.require
require(video["media"]["duration_ms"] == 1000, "검사 영상 길이 불일치")
attempt = (ROOT / video["media"]["path"]).parent
require(run.digest(attempt / "frame-00000000.png") != run.digest(attempt / "frame-00000009.png"), "3D 움직임이 없습니다")
still_job = dict(job, frame=10, start=None, end=None)
first = run.render(ROOT, still_job, args.blender, args.ffmpeg, args.ffprobe)
second = run.render(ROOT, dict(still_job, scene="SceneB"), args.blender, args.ffmpeg, args.ffprobe)
require(first["identity"] != second["identity"] and run.digest(ROOT / first["media"]["path"]) != run.digest(ROOT / second["media"]["path"]),
"같은 논리 프레임의 다른 Scene 카메라가 혼입되었습니다")
for invalid in (dict(still_job, scene="Missing"), dict(still_job, require_object=["Missing"])):
try:
run.render(ROOT, invalid, args.blender, args.ffmpeg, args.ffprobe)
except ValueError:
pass
else:
raise ValueError("없는 Scene/오브젝트를 거부하지 않았습니다")
for change in ("external", "short_scene"):
bad_source = source.with_name(change + ".blend")
options = ["--external-image", ROOT / first["media"]["path"]] if change == "external" else ["--short-scene"]
run.execute([args.blender, "--background", "--factory-startup", "--disable-autoexec", "--python-exit-code", "1", "--python",
Path(__file__).with_name("smoke_fixture.py"), "--", bad_source, *options])
artifacts["08-scenes"]["payload"]["preview"]["scene_file"]["path"] = bad_source.relative_to(ROOT).as_posix()
write(ROOT / job["artifact"], artifacts["08-scenes"])
try:
run.render(ROOT, dict(job, source=bad_source.relative_to(ROOT).as_posix()), args.blender, args.ffmpeg, args.ffprobe)
except ValueError:
pass
else:
raise ValueError("외부 참조/실제 Scene 범위 오류를 거부하지 않았습니다")
approved_folder, approved_project, approved_artifacts, approved_job = production_fixture()
old_source = ROOT / approved_artifacts["08-scenes"]["payload"]["preview"]["scene_file"]["path"]
old_source.rename(old_source.with_suffix(".uninspected"))
shutil.copyfile(source, old_source)
approved_artifacts["08-scenes"]["payload"]["preview"]["scene_file"].update(sha256=run.digest(old_source), blender_version=video["scene_file"]["blender_version"])
write(approved_folder / "stages/08-scenes/r2/artifact.json", approved_artifacts["08-scenes"])
production_source = ROOT / approved_job["source"]
production_source.rename(production_source.with_suffix(".uninspected"))
shutil.copyfile(source, production_source)
production = run.render(ROOT, approved_job, args.blender, args.ffmpeg, args.ffprobe)
require(production["media"]["kind"] == "image", "승인된 10 기준 PNG가 아닙니다")
actual_motion = None
for motion in (False, True):
target_folder, target_project, target_artifacts, target_job = endpoint_fixture(motion)
for stage in ("08-scenes", "10-references", "11-images"):
item = target_artifacts[stage]["payload"]
result = item["preview"] if stage == "08-scenes" else item["result"]
metadata = result["scene_file"] if result else None
path = ROOT / (metadata["path"] if metadata else item["request"]["scene_path"])
path.rename(path.with_suffix(".uninspected"))
shutil.copyfile(source, path)
if metadata:
metadata.update(sha256=run.digest(path), blender_version=video["scene_file"]["blender_version"])
(item["preview"] if stage == "08-scenes" else item["request"])["render_profile"].update(
engine=args.engine, device="GPU" if args.engine == "BLENDER_EEVEE" else "CPU")
if motion:
target_artifacts["12-motion"]["payload"]["request"]["render_profile"].update(
engine=args.engine, device="GPU" if args.engine == "BLENDER_EEVEE" else "CPU")
for pointer in target_project["stages"]:
if pointer["stage_id"] in target_artifacts:
write(ROOT / pointer["artifact_path"], target_artifacts[pointer["stage_id"]])
actual = run.render(ROOT, target_job, args.blender, args.ffmpeg, args.ffprobe)
require(actual["media"]["kind"] == ("video" if motion else "image"), "승인된 11/12 렌더 형식이 다릅니다")
if motion:
actual_motion = actual["media"]
for failure in folder.rglob("failure.json"):
require(not list(failure.parent.glob("*.png")), "실패 검사에서 출력 PNG를 썼습니다")
print(json.dumps(dict(video=video["media"], first_scene=first["media"], second_scene=second["media"],
production_reference=production["media"],
production_clip=actual_motion,
renderer=video["render_profile"], source=video["scene_file"], result="실제 렌더·디코드·움직임·다중 Scene·승인된 기준 생성·외부 참조 거부 검사 통과"), ensure_ascii=False, indent=2))
if __name__ == "__main__":
main()