Files
ShortVideoGenerator/projects/water-hydrogen-bond-surface-tension/blender/render_preview.py
T

69 lines
4.6 KiB
Python

"""기존 실행기로 세 대표 컷의 PNG/MP4를 만든다. 전 컷 제작은 하지 않는다."""
import argparse
import json
from pathlib import Path
import subprocess
from PIL import Image, ImageChops
ROOT = Path(__file__).resolve().parents[3]
BASE = Path('projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3')
BLENDER = 'C:/Program Files/Blender Foundation/Blender 5.2/blender.exe'
PYTHON = 'C:/Users/baram/anaconda3/python.exe'
COMPOSITOR = Path('C:/git/ShortVideoGenerator/projects/water-hydrogen-bond-surface-tension/renderer/node_modules/@remotion/compositor-win32-x64-msvc')
SOURCE = (BASE/'generated-source/preview.blend').as_posix()
ARTIFACT = (BASE/'artifact.json').as_posix()
PROJECT = 'projects/water-hydrogen-bond-surface-tension/project.json'
PLAN = [('c07','BondPair','PairA',1017,1219,1219),('c16','Interface','WaterCutawayBody',2806,3050,3050),('c18','DropletStretch','Droplet',3172,3372,3372)]
def run(args, records):
result=subprocess.run(args,cwd=ROOT,text=True,encoding='utf-8',capture_output=True)
records.append(dict(command=args,exit_code=result.returncode))
(ROOT/BASE/'cli-commands.json').write_text(json.dumps(records,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
if result.returncode:
print(result.stdout,result.stderr,flush=True)
raise RuntimeError('실행 실패; 명령 기록과 기존 시도 보존')
parsed=json.loads(result.stdout)
records[-1]['source_sha256']=parsed['scene_file']['sha256']
records[-1]['scene_name']=parsed.get('scene_name')
if 'media' in parsed:
records[-1]['media']=parsed['media']
records[-1]['evidence_path']=(Path(parsed['media']['path']).parent/'evidence.json').as_posix()
(ROOT/BASE/'cli-commands.json').write_text(json.dumps(records,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
print('EXIT_0',args[5],parsed.get('scene_name'),flush=True)
return parsed
if __name__=='__main__':
parser=argparse.ArgumentParser()
parser.add_argument('--kind',choices=['image','video'],required=True)
parser.add_argument('--cut',choices=['c07','c16','c18'])
args=parser.parse_args()
record_path=ROOT/BASE/'cli-commands.json'
records=json.loads(record_path.read_text(encoding='utf-8')) if record_path.exists() else []
for record in records:
record.pop('stdout',None)
record.pop('stderr',None)
artifact=json.loads((ROOT/ARTIFACT).read_text(encoding='utf-8'))
for cut,scene,obj,start,end,boundary in PLAN:
if args.cut and args.cut!=cut:
continue
assert not any(s['id']==cut+'-'+args.kind for s in artifact['payload']['preview']['samples']), '이미 반영한 sample을 같은 버전에서 재생성하지 않는다'
common=[PYTHON,'-B','-X','utf8','tools/blender/run.py']
inspect=run(common+['inspect','--blender',BLENDER,'--source',SOURCE,'--scene',scene,'--require-object',obj],records)
artifact['payload']['preview']['scene_file']=inspect['scene_file']
(ROOT/ARTIFACT).write_text(json.dumps(artifact,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
options=['--frame',str(boundary)] if args.kind=='image' else ['--start',str(start),'--end',str(end)]
rendered=run(common+['render','--mode','preview','--project',PROJECT,'--artifact',ARTIFACT,'--output-revision','3','--blender',BLENDER,'--source',SOURCE,'--scene',scene,'--require-object',obj,'--cut-id',cut,'--ffmpeg',str(COMPOSITOR/'ffmpeg.exe'),'--ffprobe',str(COMPOSITOR/'ffprobe.exe')]+options,records)
# 실행기의 실제 증거만 미디어 메타데이터로 사용한다.
evidence=rendered
if args.kind=='video':
frames=[ROOT/Path(evidence['media']['path']).parent/f'frame-{f:08}.png' for f in (start,start+(end-start)//2,end-1)]
pixels=[Image.open(f).convert('RGB') for f in frames]
if not (ImageChops.difference(pixels[0],pixels[1]).getbbox() and ImageChops.difference(pixels[0],pixels[2]).getbbox()):
(ROOT/Path(evidence['media']['path']).parent/'visual-failure.json').write_text(json.dumps(dict(error='실제 렌더 프레임이 정지함; 미디어 반영 금지',logical_frames=[start,start+(end-start)//2,end-1]),ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
raise RuntimeError('실제 렌더 프레임이 정지함; 미디어 반영 금지')
artifact['payload']['preview']['samples'].append(dict(id=cut+'-'+args.kind,cut_ids=[cut],media=evidence['media']))
(ROOT/ARTIFACT).write_text(json.dumps(artifact,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
print('READY',cut,args.kind,evidence['media']['path'],flush=True)