smodusermc/offline-games / _tools /test_audio_manifest_decode.py
smodusermc's picture
download
raw
4.2 kB
"""Browser-decode all enumerated audio from bucket copies, with no external browser network."""
import os,sys,json,shutil,threading,importlib.util,requests,hashlib
from pathlib import Path
from playwright.sync_api import sync_playwright
H=Path('/home/user/game-archive');R=H/'reports';sys.argv=[sys.argv[0]]
spec=importlib.util.spec_from_file_location('av',H/'tools/verify_games.py');v=importlib.util.module_from_spec(spec);spec.loader.exec_module(v)
reports=[];audits=json.loads((R/'lazy-audio-manifests.json').read_text())
for ident in [6,101,55]:
m=json.loads((R/f'game-{ident}.json').read_text());records={x['path']:x for x in m['files']}
if ident==6:paths=[x['path'] for x in json.loads((R/'thirty-dollar-audio-audit.json').read_text())['files']]+[x['path'] for x in json.loads((R/'upstream-audio-restorations.json').read_text()) if x['game_id']==6]
else:
audit=next(x for x in audits if x['game_id']==ident);paths=[x['path'] for x in audit['verified']+audit['failures']]
paths=list(dict.fromkeys(paths));files=[records[k] for k in paths if k in records];report={'game_id':ident,'expected':len(paths),'scope':'Decode tests from bucket files in a local fixture, not all in-game audio events.','missing_bucket_manifest_paths':[p for p in paths if p not in records]}
shutil.rmtree(v.STAGE,ignore_errors=True);v.STAGE.mkdir();server=None
try:
v.api.download_bucket_files(v.BUCKET,[(x['path'],v.STAGE/x['path']) for x in files],raise_on_missing_files=True)
(v.STAGE/'site/__audio_test__.html').write_text('<!doctype html><meta charset="utf-8"><title>Audio file audit</title>Audio decoding fixture')
state=v.State({'files':files});state.repair=False;server=v.ThreadingHTTPServer(('127.0.0.1',0),v.Handler);server.state=state;server.daemon_threads=False;threading.Thread(target=server.serve_forever,daemon=True).start()
with sync_playwright() as p:
b=p.chromium.launch(headless=True,args=['--no-sandbox','--disable-dev-shm-usage']);c=b.new_context(service_workers='block')
c.route('**/*',lambda route:route.continue_() if route.request.url.startswith(f'http://127.0.0.1:{server.server_port}/') else route.abort())
page=c.new_page();page.goto(f'http://127.0.0.1:{server.server_port}/__audio_test__.html')
report['files']=page.evaluate('''async(paths)=>{const ctx=new AudioContext();const result=[];for(const path of paths){try{const r=await fetch(path);if(!r.ok)throw Error('HTTP '+r.status);const bytes=await r.arrayBuffer();const a=await ctx.decodeAudioData(bytes);let peak=0;const d=a.getChannelData(0);for(let i=0;i<d.length;i+=Math.max(1,Math.floor(d.length/4096)))peak=Math.max(peak,Math.abs(d[i]));result.push({path,decoded:true,duration:a.duration,frames:a.length,channels:a.numberOfChannels,sampled_peak:peak});}catch(e){result.push({path,decoded:false,error:String(e)});}}await ctx.close();return result;}''',['/'+x.removeprefix('site/') for x in paths if x in records]);b.close()
report['decoded']=sum(x['decoded'] for x in report['files']);report['all_expected_decode']=report['decoded']==len(paths)
if ident==55:
key='site/games/basket-random/media/intro-sound.webm';f=v.STAGE/key
with requests.get('https://garbsoftball.com/games/basket-random/media/intro-sound.webm',timeout=20) as response:
response.raise_for_status();sourcehash=hashlib.sha256(response.content).hexdigest()
storedhash=hashlib.sha256(f.read_bytes()).hexdigest()
report['size_discrepancy']={'path':key,'declared_bytes':38492,'stored_bytes':f.stat().st_size,'matches_current_source_sha256':sourcehash==storedhash,'browser_decodes':next(x['decoded'] for x in report['files'] if x['path'].endswith('intro-sound.webm')),'interpretation':'Manifest byte count differs, but the stored audio matches current source bytes and decodes successfully. No replacement made.'}
reports.append(report);print('Audio decode',ident,report['decoded'],'/',len(paths),flush=True)
finally:
if server:server.shutdown();server.server_close()
shutil.rmtree(v.STAGE,ignore_errors=True)
(R/'audio-manifest-browser-tests.json').write_text(json.dumps(reports,indent=2));v.api.batch_bucket_files(v.BUCKET,add=[(R/'audio-manifest-browser-tests.json','_verification/audio-manifest-browser-tests.json')])

Xet Storage Details

Size:
4.2 kB
·
Xet hash:
9cdb4f273b7c0fe75a3967bb40f88fc06f546900aa2a478d0eb3b18e4516d349

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.