import fs from 'node:fs'; import { execFileSync } from 'node:child_process'; import { chromium } from '@playwright/test'; const silence = process.argv.includes('--silence'); const dir = `reports/bridge-lipsync-diagnosis-20260914${silence ? '-silence' : ''}`; fs.mkdirSync(dir, { recursive: true }); const input = 'reports/bridge-digital-video-20260914/TASK-VIRTUAL-005-step-03.mp4'; execFileSync('ffmpeg', ['-hide_banner','-loglevel','error','-y','-i',input,'-vn','-ar','16000','-ac','1','-c:a','pcm_s16le',`${dir}/sample.wav`]); if (silence) execFileSync('ffmpeg', ['-hide_banner','-loglevel','error','-y','-f','lavfi','-i','anullsrc=r=16000:cl=mono','-t','8.241','-c:a','pcm_s16le',`${dir}/sample.wav`]); const origin = 'http://1.14.103.234:8123'; const browser = await chromium.launch({ channel:'msedge', headless:true, args:['--autoplay-policy=no-user-gesture-required','--use-gl=angle','--use-angle=swiftshader','--enable-unsafe-swiftshader',`--unsafely-treat-insecure-origin-as-secure=${origin}`] }); try { const context=await browser.newContext({ viewport:{width:480,height:640},acceptDownloads:true }); await context.route(`${origin}/__open-render__`,r=>r.fulfill({contentType:'text/html',body:''})); const page=await context.newPage(), errors=[];page.on('pageerror',e=>errors.push(e.message)); await page.goto(`${origin}/__open-render__`); const frame=page.frames().find(f=>f.url().includes('/viewer/')); await frame.waitForFunction(()=>window.DH_RUNTIME_READY,{},{timeout:120000}); await frame.evaluate(()=>{ window.lipDiagnostic={frames:[],start:performance.now(),events:[]}; const bs=Module._updateBlendShape, set=Module._setAudioBuffer; Module._updateBlendShape=function(p,n){const t=performance.now(),r=bs(p,n);window.lipDiagnostic.frames.push({t:t-window.lipDiagnostic.start,ms:performance.now()-t,values:Array.from(new Float32Array(Module.HEAPU8.buffer,p,12))});return r;}; Module._setAudioBuffer=function(p,n){const r=set(p,n);window.lipDiagnostic.events.push({type:'setAudio',t:performance.now()-window.lipDiagnostic.start,n,result:r});return r;}; }); const capture=fs.readFileSync('../../ai_person/ai_person_api/app/services/open_render_capture.js','utf8'); await page.evaluate(({ script, args }) => eval(`(${script}\n)`)(args), { script: capture, args: {audio:fs.readFileSync(`${dir}/sample.wav`).toString('base64'),background:null,backgroundVideo:false,width:480,height:640,text:'口型同步诊断',subtitles:false,maxSeconds:120} }); await page.waitForFunction(()=>['done','error'].includes(window.openCapture?.status),{},{timeout:120000}); const result=await frame.evaluate(()=>window.lipDiagnostic); result.capture=await page.evaluate(()=>window.openCapture);result.errors=errors; if (result.capture.status === 'done') { const downloadPromise = page.waitForEvent('download'); await page.evaluate(()=>{const a=document.createElement('a');a.href=window.openCapture.url;a.download='diagnostic.webm';a.click();}); await (await downloadPromise).saveAs(`${dir}/diagnostic.webm`); } const active=result.frames.filter(f=>f.t>=result.events[0]?.t); result.summary={frameCount:active.length,elapsedMs:active.at(-1)?.t-active[0]?.t,fps:active.length/((active.at(-1)?.t-active[0]?.t)/1000),maxBlend:Math.max(...active.flatMap(f=>f.values.map(Math.abs))),nonZeroFrames:active.filter(f=>f.values.some(v=>Math.abs(v)>.001)).length}; fs.writeFileSync(`${dir}/runtime.json`,JSON.stringify(result,null,2));console.log(JSON.stringify({summary:result.summary,events:result.events,capture:result.capture.status,errors})); } finally {await browser.close();}