"""Read-only authored RIFF/WAVE INFO inspector; no uploads or third-party modules.
python riff-info-authored-local-inspector.py            # reproducible memory fixture
python riff-info-authored-local-inspector.py FILE.wav   # read file; never rewrite it
Only little-endian RIFF/WAVE; not RF64, RIFX, MP3 or a privacy certification.
"""
import hashlib,io,json,math,struct,sys,wave
def sha(b):return hashlib.sha256(b).hexdigest()
def chunk(tag,payload):
    assert len(tag)==4
    return tag+struct.pack('<I',len(payload))+payload+(b'\0' if len(payload)%2 else b'')
def chunks(buf,start,end):
    pos=start
    while pos<end:
        if pos+8>end:raise ValueError('truncated chunk header')
        tag=buf[pos:pos+4];n=struct.unpack_from('<I',buf,pos+4)[0]
        stop=pos+8+n;following=stop+(n%2)
        if following>end:raise ValueError('chunk body or word padding exceeds parent')
        yield tag,buf[pos+8:stop],n
        pos=following
    assert pos==end
def inspect(buf):
    if len(buf)<12 or buf[:4]!=b'RIFF' or buf[8:12]!=b'WAVE':raise ValueError('only RIFF little-endian WAVE supported')
    declared=struct.unpack_from('<I',buf,4)[0]+8
    if declared!=len(buf):raise ValueError('declared RIFF length differs from actual bytes')
    records=[];infos=[];data=[]
    for tag,payload,n in chunks(buf,12,declared):
        name=tag.decode('ascii',errors='backslashreplace')
        records.append({'id':name,'payload_bytes':n,'word_padding_bytes':n%2})
        if tag==b'data':data.append(sha(payload))
        if tag==b'LIST' and payload[:4]==b'INFO':
            for ident,value,size in chunks(payload,4,len(payload)):
                label=ident.decode('ascii',errors='backslashreplace')
                row={'id':label,'payload_bytes':size,'word_padding_bytes':size%2,'raw_hex':value.hex(),'null_terminated':value.endswith(b'\0')}
                if ident in (b'INAM',b'IART',b'ICMT'):
                    row['ascii_value']=value.rstrip(b'\0').decode('ascii',errors='backslashreplace')
                    row['interpretation']='ASCII demonstration only; no universal encoding assumed'
                else:row['interpretation']='unknown INFO field; retained as bytes, not declared harmless'
                infos.append(row)
    return {'file_bytes':len(buf),'file_sha256':sha(buf),'riff_declared_bytes_including_header':declared,'top_level_chunks':records,'info_fields':infos,'pcm_data_chunk_sha256':data,'privacy_certified':False,'file_modified':False}
def demo():
    b=io.BytesIO()
    pcm=b''.join(struct.pack('<h',int(4000*math.sin(2*math.pi*440*i/8000))) for i in range(800))
    with wave.open(b,'wb') as w:w.setnchannels(1);w.setsampwidth(2);w.setframerate(8000);w.writeframes(pcm)
    base=b.getvalue()
    info=b'INFO'+chunk(b'INAM',b'Demo\0')+chunk(b'IART',b'Fictional author\0')+chunk(b'ICMT',b'Authored local fixture\0')+chunk(b'XTRA',b'\x01\x02\0')
    tail=base[8:]+chunk(b'LIST',info)
    tagged=b'RIFF'+struct.pack('<I',len(tail))+tail
    first,second=inspect(base),inspect(tagged)
    assert first['info_fields']==[] and len(second['info_fields'])==4
    assert first['pcm_data_chunk_sha256']==second['pcm_data_chunk_sha256']==[sha(pcm)]
    assert first['file_sha256']!=second['file_sha256']
    assert second['info_fields'][0]['word_padding_bytes']==1
    failures={}
    oversized=bytearray(tagged);struct.pack_into('<I',oversized,16,len(tagged)*2)
    for name,buf in [('truncated_file',tagged[:-1]),('oversized_first_chunk',bytes(oversized)),('unsupported_RIFX',b'RIFX'+tagged[4:])]:
        try:inspect(buf)
        except ValueError as e:failures[name]=str(e)
        else:raise AssertionError(name)
    assert len(failures)==3
    return {'fixture':'own 0.1-second 440-Hz PCM16 mono 8000-Hz, not speech or customer data','plain':first,'with_authored_INFO':second,'same_data_chunk_hash':True,'different_whole_file_hash':True,'negative_cases_rejected':failures,'limits':['INFO absence is not anonymity; voice content itself can identify someone','bext/iXML/ID3 contents, RF64/RIFX, MP3 and arbitrary text encodings not interpreted','no privacy verdict, audio listening, metadata removal or third-party service executed']}
if __name__=='__main__':
    if len(sys.argv)==1:out=demo()
    elif len(sys.argv)==2:
        with open(sys.argv[1],'rb') as stream:out=inspect(stream.read())
    else:raise SystemExit('usage: python SCRIPT.py [FILE.wav]')
    print(json.dumps(out,ensure_ascii=False,indent=2))
