#!/usr/bin/env python3 """Verify this capture's prefix-named light/dark media, outside the repo's suffix gate.""" import hashlib import json import struct import subprocess from pathlib import Path root = Path(__file__).resolve().parent manifest = json.loads((root / 'manifest.json').read_text()) assets = {entry['path']: entry for entry in manifest['assets']} for name, entry in assets.items(): data = (root / name).read_bytes() assert len(data) == entry['bytes'], f'{name}: byte count differs' assert hashlib.sha256(data).hexdigest() == entry['sha256'], f'{name}: SHA differs' def dimensions(path): if path.suffix == '.png': header = path.read_bytes()[:24] assert header[:8] == b'\x89PNG\r\n\x1a\n' return {'width': struct.unpack('>I', header[16:20])[0], 'height': struct.unpack('>I', header[20:24])[0]} result = subprocess.run(['ffprobe','-v','error','-count_frames','-show_entries','stream=codec_name,width,height,r_frame_rate,nb_read_frames:format=duration','-of','json',str(path)],check=True,text=True,capture_output=True) probe = json.loads(result.stdout) videos = [stream for stream in probe['streams'] if 'width' in stream] audios = [stream for stream in probe['streams'] if 'width' not in stream] assert len(videos) == 1 and not audios, f'{path.name}: expected silent video' video = videos[0] return {'width':video['width'],'height':video['height'],'codec':video['codec_name'],'fps':video['r_frame_rate'],'frames':int(video['nb_read_frames']),'durationSeconds':round(float(probe['format']['duration']),2),'audioStreams':0} for pair in manifest['pairs']: light = dimensions(root / pair['light']) dark = dimensions(root / pair['dark']) assert light == dark == pair['expected'], f"{pair['name']}: theme mismatch {light} / {dark}" print(f"Motion assets {len(assets)}/{len(assets)} exact; paired media {len(manifest['pairs'])}/{len(manifest['pairs'])} dimensions, timing and silence match.")