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

105 lines
6.6 KiB
Python

"""정식 결과의 계약·참조·실측·원본 및 복사 파일 해시를 대조한다."""
import argparse
import hashlib
import json
from pathlib import Path
import sys
import jsonschema
ROOT=Path(__file__).resolve().parents[3]
PROJECT=ROOT/'projects/water-hydrogen-bond-surface-tension'
sys.path.insert(0,str(ROOT/'contracts'))
from check import semantic_errors
parser=argparse.ArgumentParser()
parser.add_argument('--stage',choices=['10-references','11-images','12-motion'],required=True)
args=parser.parse_args()
BASE=PROJECT/f'stages/{args.stage}/r2'
def read(path):
return json.loads(path.read_text(encoding='utf-8'))
artifact=read(BASE/'artifact.json')
context={s['stage_id']:read(PROJECT.parent.parent/s['artifact_path']) for s in read(PROJECT/'project.json')['stages'] if (ROOT/s['artifact_path']).is_file()}
jsonschema.validate(artifact,read(ROOT/'contracts/artifact.schema.json'))
assert not semantic_errors(artifact,context,complete=True),semantic_errors(artifact,context,complete=True)
request,result=artifact['payload']['request'],artifact['payload']['result']
source=result['scene_file'] if args.stage!='12-motion' else context['11-images']['payload']['result']['scene_file']
assert source['verified'] and hashlib.sha256((ROOT/source['path']).read_bytes()).hexdigest()==source['sha256']
if args.stage!='12-motion':
assert (ROOT/source['path']).is_relative_to(BASE)
model=read(BASE/'model-check.json')
assert model['source_sha256']==source['sha256'] and model['water_containment']['frames']==705
assets={source['path']:source['sha256']}
key,id_key={'10-references':('items','reference_id'),'11-images':('frames','frame_id'),'12-motion':('clips','cut_id')}[args.stage]
if args.stage=='10-references':
assert len(result['items'])==7 and len({i['object_name'] for i in result['items']})==7
assert {i['entity_id'] for i in request['items'] if i['kind']=='background'}=={b['id'] for b in context['09-entities']['payload']['backgrounds']}
if args.stage=='11-images':
assert len(result['frames'])==62
by_id={f['frame_id']:f['media'] for f in result['frames']}
frames={f['id']:f for f in request['frames']}
assert len(by_id)==len(frames)==62
anchors=read(BASE/'anchors.json')
assert anchors['source_sha256']==source['sha256'] and (anchors['width'],anchors['height'])==(1080,1920)
assert anchors['coordinate_system']=='top_left_pixel' and len(anchors['cuts'])==31
for cut,anchor in zip(context['07-cuts']['payload']['cuts'],anchors['cuts']):
start,end=cut['start_ms']//20,cut['end_ms']//20
assert (anchor['cut_id'],anchor['start_frame'],anchor['end_frame'])==(cut['id'],start,end)
assert frames[cut['id']+'-start']['frame_index']==start and frames[cut['id']+'-end']['frame_index']==end
assert frames[cut['id']+'-start']['scene_name']==anchor['scene_name']==frames[cut['id']+'-end']['scene_name']
assert anchor['motion'] and all(track['keys'][0][0]==start and track['keys'][-1][0]==end for track in anchor['motion'])
for connection in context['08-scenes']['payload']['connections']:
left,right=connection['from_cut_id']+'-end',connection['to_cut_id']+'-start'
if connection['mode']=='continuous':
assert by_id[left]['path']==by_id[right]['path']
assert (frames[left]['scene_name'],frames[left]['frame_index'])==(frames[right]['scene_name'],frames[right]['frame_index'])
else:
assert frames[left]['scene_name']!=frames[right]['scene_name']
if args.stage=='12-motion':
assert len(result['clips'])==31 and sum(c['end_frame']-c['start_frame'] for c in request['clips'])==5528
assert sum(c['duration_ms'] for c in request['clips'])==110560
if args.stage!='10-references':
visual=read(BASE/'visual-check.json')
assert visual['source_sha256']==source['sha256']
assert all(v['visual_review']=='checked' for v in visual['views']), '실제 화면 직접 관찰 미완료'
assert len(visual['views'])==(9 if args.stage=='11-images' else 31)
for view in visual['views']:
for asset in [view['sheet']]+view.get('decoded_frames',[])+view.get('render_frames',[]):
path=ROOT/asset['path']
assert path.is_relative_to(BASE) and hashlib.sha256(path.read_bytes()).hexdigest()==asset['sha256']
assets[asset['path']]=asset['sha256']
for item in result[key]:
media=item['media']
path=ROOT/media['path']
assert path.is_file() and path.is_relative_to(BASE) and media['verified']
assert (media['width'],media['height'])==(1080,1920)
evidence=read(path.parent/'evidence.json')
assert evidence['scene_file']==source and evidence['render_profile']==request['render_profile'] and evidence['media']==media
if args.stage=='10-references':
assert evidence['logical_frames'] and len(evidence['logical_frames'])==1
elif args.stage=='11-images':
frame=next(f for f in request['frames'] if f['id']==item['frame_id'])
assert evidence['scene_name']==frame['scene_name'] and evidence['logical_frames']==[frame['frame_index']]
else:
clip=next(c for c in request['clips'] if c['cut_id']==item['cut_id'])
assert evidence['scene_name']==clip['scene_name'] and evidence['logical_frames']==list(range(clip['start_frame'],clip['end_frame']))
assert media['duration_ms']==clip['duration_ms'] and 0<media['duration_ms']<=5000
endpoints=context['11-images']['payload']
end=next(f for f in endpoints['request']['frames'] if f['id']==clip['end_frame_id'])
assert end['frame_index']-evidence['logical_frames'][-1]==1
for logical in [clip['start_frame'],(clip['start_frame']+clip['end_frame'])//2,clip['end_frame']-1]:
frame=path.parent/f'frame-{logical:08}.png'
assets[frame.relative_to(ROOT).as_posix()]=hashlib.sha256(frame.read_bytes()).hexdigest()
for file in [path,path.parent/'evidence.json']:
assets[file.relative_to(ROOT).as_posix()]=hashlib.sha256(file.read_bytes()).hexdigest()
for file in BASE.glob('*.json'):
if file.name!='manifest.json':
assets[file.relative_to(ROOT).as_posix()]=hashlib.sha256(file.read_bytes()).hexdigest()
if (BASE/'review.md').is_file():
file=BASE/'review.md'
assets[file.relative_to(ROOT).as_posix()]=hashlib.sha256(file.read_bytes()).hexdigest()
value=dict(result='GREEN',stage_id=args.stage,source_sha256=source['sha256'],requested=len(request[key]),actual=len(result[key]),render_profile=request['render_profile'],actual_assets=[dict(path=path,sha256=sha) for path,sha in sorted(assets.items())])
(BASE/'manifest.json').write_text(json.dumps(value,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
print('정식 결과 검사 GREEN',args.stage,len(value['actual_assets']))