# 本文公开核验脚本：仅验证随附原创文件及声明的有限条件。
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))

def vtt(name):
    data=(P/name).read_bytes()
    text=data.decode('utf-8-sig').replace('\r\n','\n').replace('\r','\n')
    first, _, rest=text.partition('\n')
    assert first=='WEBVTT' or first.startswith('WEBVTT '),'文件头签名'
    assert rest.startswith('\n'),'文件头空行'
    out=[]
    for raw in re.split(r'\n[ \t]*\n',rest.strip()):
        lines=raw.splitlines()
        if lines[0]=='STYLE': out.append({'type':'STYLE','text':'\n'.join(lines[1:])}); continue
        if lines[0]=='NOTE' or lines[0].startswith(('NOTE ','NOTE\t')): out.append({'type':'NOTE','text':raw}); continue
        i=0 if '-->' in lines[0] else 1
        assert len(lines)>i and '-->' in lines[i], '缺时间行'
        left,right=lines[i].split('-->'); tokens=right.strip().split()
        start,end=timestamp(left.strip()),timestamp(tokens[0])
        out.append({'type':'cue','id':lines[0] if i else '', 'start':start,'end':end,'settings':tokens[1:],'payload':'\n'.join(lines[i+1:])})
    return out
def timestamp(s):
    m=re.fullmatch(r'(?:(\d{2,}):)?([0-5]\d):([0-5]\d)\.(\d{3})',s)
    assert m, '时间戳语法'
    h,mi,se,ms=m.groups(); return int(h or 0)*3600000+int(mi)*60000+int(se)*1000+int(ms)
def cues(name): return [x for x in vtt(name) if x['type']=='cue']
def reject(fn, value):
    try: fn(value)
    except (AssertionError,ValueError): return True
    return False

x=cues('identifiers.vtt'); ids=[c['id'] for c in x if c['id']];assert len(ids)==len(set(ids));bad=read('cases.json')['duplicate_ids'];assert len(bad)!=len(set(bad))
emit({'author_rule_version':'2026-05-20 CRD','nonempty_ids':ids,'omitted_id_count':sum(not c['id'] for c in x),'duplicate_strings_rejected':True,'browser_acceptance':'未测'})

