# 本文公开核验脚本：仅验证随附原创文件及声明的有限条件。
import json, csv, hashlib, re, struct, subprocess, sys, zlib
from fractions import Fraction
from pathlib import Path
P = Path(__file__).resolve().parent
def read(name): return json.loads((P/name).read_text(encoding='utf-8-sig'))
def digest(name): return hashlib.sha256((P/name).read_bytes()).hexdigest()
def probe(name, *args):
    command = ['ffprobe','-v','error',*args,'-of','json',str(P/name)]
    run = subprocess.run(command,capture_output=True,text=True,encoding='utf-8',timeout=25)
    assert run.returncode == 0, run.stderr
    return json.loads(run.stdout)
def decode(name):
    command=['ffmpeg','-v','error','-xerror','-i',str(P/name),'-map','0:v?','-map','0:a?','-f','null','-']
    run=subprocess.run(command,capture_output=True,text=True,encoding='utf-8',timeout=25)
    assert run.returncode == 0, run.stderr
    assert not run.stderr.strip(), run.stderr
    return {'file':name,'exit_code':run.returncode,'stderr':run.stderr}
def packets(name, select='v:0'):
    return probe(name,'-select_streams',select,'-show_packets')['packets']
def streams(name): return probe(name,'-show_streams','-show_format')
def stamp(v,base): return Fraction(int(v))*Fraction(base)
def framehash(name):
    run=subprocess.run(['ffmpeg','-v','error','-i',str(P/name),'-map','0:V:0','-f','framemd5','-'],capture_output=True,text=True,encoding='utf-8',timeout=25)
    assert run.returncode==0,run.stderr
    return [x.split(',')[-1].strip() for x in run.stdout.splitlines() if x and not x.startswith('#')]
def emit(value): print(json.dumps(value,ensure_ascii=False,indent=2))

from PIL import Image, __version__ as pillow_version
def pil_frames(name):
    out=[]
    with Image.open(P/name) as im:
        for i in range(im.n_frames):
            im.seek(i);im.load();rgba=im.convert('RGBA')
            out.append({'index':i,'size':list(rgba.size),'duration_ms':im.info.get('duration'),'pixel_15_15':list(rgba.getpixel((15,15))),'pixel_45_5':list(rgba.getpixel((45,5))),'rgba_sha256':hashlib.sha256(rgba.tobytes()).hexdigest()})
    return out
def png_chunks(name):
    b=(P/name).read_bytes();assert b[:8]==b'\x89PNG\r\n\x1a\n';i=8;out=[]
    while i<len(b):
        n=struct.unpack_from('>I',b,i)[0];t=b[i+4:i+8];d=b[i+8:i+8+n];crc=struct.unpack_from('>I',b,i+8+n)[0]
        assert zlib.crc32(t+d)&0xffffffff==crc
        if t==b'fcTL':
            v=struct.unpack('>IIIIIHHBB',d);out.append(dict(zip(['sequence','width','height','x','y','delay_num','delay_den','dispose','blend'],v)))
        i+=12+n
    assert i==len(b)
    return out
def webp_chunks(name):
    b=(P/name).read_bytes();assert b[:4]==b'RIFF' and b[8:12]==b'WEBP';assert int.from_bytes(b[4:8],'little')+8==len(b);i=12;out=[]
    u=lambda d:int.from_bytes(d,'little')
    while i<len(b):
        t=b[i:i+4];n=u(b[i+4:i+8]);d=b[i+8:i+8+n];assert len(d)==n
        if t==b'ANMF':out.append({'x':u(d[:3])*2,'y':u(d[3:6])*2,'width':u(d[6:9])+1,'height':u(d[9:12])+1,'duration_ms':u(d[12:15]),'flags':d[15]})
        i+=8+n+(n&1)
    assert i==len(b)
    return out
def gif_blocks(name):
    b=(P/name).read_bytes();assert b[:6] in [b'GIF87a',b'GIF89a'];w,h=struct.unpack_from('<HH',b,6);packed=b[10];i=13;gct=b''
    if packed&128:n=3*(2**((packed&7)+1));gct=b[i:i+n];i+=n
    gce={};out=[]
    def skip(j):
        while True:
            n=b[j];j+=1
            if n==0:return j
            j+=n;assert j<=len(b)
    while i<len(b):
        marker=b[i];i+=1
        if marker==0x3b:break
        if marker==0x21:
            label=b[i];i+=1
            if label==0xf9:
                assert b[i]==4;flags,delay,trans=struct.unpack_from('<BHB',b,i+1);gce={'delay_cs':delay,'disposal':(flags>>2)&7,'transparent':bool(flags&1),'transparent_index':trans};i+=6
            else:i=skip(i)
        elif marker==0x2c:
            x,y,fw,fh,flags=struct.unpack_from('<HHHHB',b,i);i+=9;lct=b''
            if flags&128:n=3*(2**((flags&7)+1));lct=b[i:i+n];i+=n
            palette=lct or gct;out.append({'x':x,'y':y,'width':fw,'height':fh,'local_palette':bool(lct),'palette_first_two':[list(palette[:3]),list(palette[3:6])],**gce});i+=1;i=skip(i);gce={}
        else:raise AssertionError(('未知GIF块',marker))
    return {'canvas':[w,h],'frames':out}

x=gif_blocks('delays.gif');y=pil_frames('delays.gif');assert [q['delay_cs'] for q in x['frames']]==[3,6,12];assert [q['duration_ms'] for q in y]==[30,60,120]
emit({'gif':x,'decoded':y,'pillow':pillow_version})

