69 lines
4.6 KiB
Python
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)
|