slime/show/video/verify.py

98 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Check rendered media, matching captions, and frozen benchmark provenance."""
import argparse
import hashlib
import json
from pathlib import Path
import av
HERE = Path(__file__).resolve().parent
parser = argparse.ArgumentParser()
parser.add_argument('--quality', default='final', choices=['preview', 'final'])
parser.add_argument('--scene', default='SlimeFilm')
args = parser.parse_args()
name = f'{args.scene}-{args.quality}'
report = json.loads((HERE / f'output/{name}.render.json').read_text())
timeline = json.loads((HERE / f'output/{name}.timeline.json').read_text())
for source, digest in report['source_sha256'].items():
assert hashlib.sha256((HERE/source).read_bytes()).hexdigest() == digest, f'changed after render: {source}'
data = json.loads((HERE/'data/benchmarks.json').read_text())
for record in data['sources']:
path = HERE/record['path']
assert hashlib.sha256(path.read_bytes()).hexdigest() == record['sha256'], f'benchmark source changed: {path}'
# Compare frozen figure values to the original machine-readable measurements.
def read_source(path):
return json.loads((HERE/path).read_text())
mapping = read_source('../../show/results/latest.json')['mapping']
assert data['A']['before_ms'] == mapping[0]['cpu']['median_ms']
assert data['A']['after_ms'] == mapping[0]['gpu']['rows'][0]['scan_median_ms']
for key, item, modes in (('B', mapping[1], (2, 3)), ('C', mapping[2], (3, 4))):
rows = {r['mode']: r for r in item['gpu']['rows']}
assert data[key]['before_ms'] == rows[modes[0]]['kernel_median_ms']
assert data[key]['after_ms'] == rows[modes[1]]['kernel_median_ms']
f = read_source('../../research/experiments/full_search_feasibility/results/summary.json')
e = read_source('../../research/experiments/normal_search/results/summary.json')
g = read_source('../../research/experiments/normal_search/pipeline_results/summary.json')
for key, before, after in (
('D', f['known']['kernel_median_ms']['0'], f['known']['kernel_median_ms']['3']),
('E', e['random65536']['3'], e['random65536']['7']),
('P', e['known']['3'], e['known']['4']),
('G', e['known']['4'], e['known']['7']),
('F', g['mixed512']['1']['host_ms'], g['mixed512']['3']['host_ms']),
):
assert data[key]['before_ms'] == before and data[key]['after_ms'] == after, key
assert data['ROOT']['roots'] == e['known']['info']['jobs']
assert data['ROOT']['lower35'] == e['known']['info']['lower35']
assert data['ROOT']['difference_survivors'] == e['metadata']['cpu_validation']['known_filter_survivors']
assert len(e['known']['info']['matches']) == 24, 'known-window final seed count'
cli = read_source('../../bench/results/local-r512-n5.json')
assert data['CLI']['scan_ms'] == cli['median_scan_wall_ms']
assert data['CLI']['anchors'] == cli['anchors']
assert all(r['root_jobs'] == data['CLI']['root_jobs'] and r['matches'] == data['CLI']['matches']
for r in cli['rounds'])
# Ensure each cue is also present, in order, in the editable exports.
subtitles = (HERE/f'output/{name}.srt').read_text()
narration = (HERE/f'output/{name}.narration.md').read_text()
assert [block.split('\n', 2)[2] for block in subtitles.strip().split('\n\n')] == [c['text'] for c in timeline['cues']]
for cue in timeline['cues']:
assert cue['text'] in narration
if args.scene == 'SlimeFilm':
assert [c['title'] for c in timeline['chapters']] == [
'史莱姆区块:先看单个区块如何判定',
'CPU:先把正确答案算出来',
'CUDA:每个 thread 负责一个 seed',
'改变分工:固定坐标,遍历 seed',
'Outer sum:邻居也可以共享坐标项',
'Random:状态更新与 48 位等价关系',
'有效种子:先反推关系,再引入窗口',
'第一次筛选:只枚举有效种子的低 18 位',
'复用筛选结果:同一坐标类只生成一次根',
'每个根预计算一次,避免中间位循环重复计算',
'中间位:输出差早筛与 16 候选掩码',
'高位:用 θ 区间求交恢复 h',
'完整验证:恢复的种子让全部 25 格通过',
'GPU 执行:按位置与根供给任务,按组搜索',
'整条流程:同一个窗口,每一步省下什么',
]
captions = '\n'.join(c['text'] for c in timeline['cues'])
for explanation in ('同一段的模输出差完全相同', '末尾统一保留低十八位',
'没有消失', '三千三百八十三', '二十四个世界种子'):
assert explanation in captions, f'missing updated explanation: {explanation}'
displays = '\n'.join(c.get('display', '') for c in timeline['cues'])
assert r'\theta=3383' in displays and '15959335054446' in displays
assert captions.index('先只看一个固定区块') < captions.index('再引入窗口')
assert all(token not in captions for token in ('假设 base 为零', '两组示意输出参数', '一百三十点一三六'))
for cue in timeline['cues']:
assert 0 <= cue['start'] < cue['end'] <= timeline['duration']+.1
for a,b in zip(timeline['cues'],timeline['cues'][1:]):
assert a['end'] <= b['start']+.01, 'overlapping captions'
assert len(timeline['cues']) == report['caption_count']
with av.open(str(HERE/'output'/report['file'])) as container:
stream = container.streams.video[0]
expected = (854,480) if args.quality == 'preview' else (1920,1080)
assert (stream.width,stream.height) == expected
duration = float(stream.duration*stream.time_base)
assert abs(duration-timeline['duration']) < .15, 'video and caption timelines differ'
frames = sum(1 for _ in container.decode(stream))
assert frames > 0
print(f'OK: {report["file"]}; {frames} decoded frames; {len(timeline["cues"])} captions; source hashes match')