mirror of
https://github.com/steveseguin/caption-local.git
synced 2026-10-09 17:49:30 +00:00
59 lines
3.9 KiB
Python
59 lines
3.9 KiB
Python
"""Twelve concurrent real browser capture buffers, shared CPU inference, no relay."""
|
|
import argparse
|
|
import asyncio
|
|
import json
|
|
from pathlib import Path
|
|
import time
|
|
from playwright.async_api import async_playwright
|
|
|
|
parser=argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--url',default='http://127.0.0.1:8771')
|
|
parser.add_argument('--seconds',type=int,default=180)
|
|
parser.add_argument('--streams',type=int,default=12)
|
|
parser.add_argument('--output',default='evidence/multistream/browser-base.json')
|
|
args=parser.parse_args()
|
|
root=Path(__file__).resolve().parents[1]
|
|
async def main():
|
|
async with async_playwright() as p:
|
|
browser=await p.chromium.launch(executable_path='/usr/bin/google-chrome',headless=True,args=[
|
|
'--no-sandbox','--use-fake-ui-for-media-stream','--use-fake-device-for-media-stream',
|
|
'--disable-background-timer-throttling','--disable-renderer-backgrounding',
|
|
f'--use-file-for-fake-audio-capture={root/"samples/soak.wav"}'])
|
|
context=await browser.new_context(permissions=['microphone'])
|
|
errors=[]; external=[]; observations=[]; responses=[]
|
|
async def guard(route):
|
|
if route.request.url.startswith(('http://127.0.0.1:','http://localhost:')): await route.continue_()
|
|
else: external.append(route.request.url); await route.abort()
|
|
await context.route('**/*',guard)
|
|
pages=[await context.new_page() for _ in range(args.streams)]
|
|
async def trace(response):
|
|
if '/transcribe?' in response.url:
|
|
responses.append({'status':response.status,'result':await response.json()})
|
|
for page in pages:
|
|
page.on('pageerror',lambda error:errors.append(str(error)))
|
|
page.on('response',trace)
|
|
await asyncio.gather(*(page.goto(args.url) for page in pages))
|
|
await asyncio.gather(*(page.get_by_role('button',name='Start captions',exact=True).click() for page in pages))
|
|
started=time.monotonic()
|
|
while time.monotonic()-started < args.seconds:
|
|
await asyncio.sleep(5)
|
|
snapshot=await asyncio.gather(*(page.evaluate('() => ({streamId,running,buffered:buffer.length/16000,captions:transcript.length,error:document.querySelector("#error").textContent})') for page in pages))
|
|
observations.append({'seconds':round(time.monotonic()-started,2),'streams':snapshot})
|
|
print(json.dumps({'seconds':observations[-1]['seconds'],'max_buffer':max(s['buffered'] for s in snapshot),'min_captions':min(s['captions'] for s in snapshot),'errors':[s['error'] for s in snapshot if s['error']]}),flush=True)
|
|
if any(not s['running'] or s['error'] for s in snapshot): break
|
|
enabled=[]
|
|
for page in pages:
|
|
if await page.get_by_role('button',name='Stop',exact=True).is_enabled(): enabled.append(page)
|
|
await asyncio.gather(*(page.get_by_role('button',name='Stop',exact=True).click() for page in enabled))
|
|
await asyncio.gather(*(page.wait_for_function('() => !processing && !stopping',timeout=120000) for page in pages))
|
|
final=await asyncio.gather(*(page.evaluate('() => ({streamId, transcript, failed, buffered:buffer.length/16000})') for page in pages))
|
|
result={'seconds':round(time.monotonic()-started,2),'streams':args.streams,'observations':observations,'responses':responses,'final':final,'errors':errors,'external':external}
|
|
Path(args.output).write_text(json.dumps(result,indent=2)+'\n')
|
|
await browser.close()
|
|
assert not errors and not external,(errors,external)
|
|
assert len({s['streamId'] for s in final})==args.streams
|
|
assert all(s['running'] and not s['error'] for o in observations for s in o['streams']), 'Capture stopped or errored under load'
|
|
assert all(not s['failed'] and s['buffered']<=0.5 and len(s['transcript'])>=args.seconds/15 for s in final), 'Incomplete drain'
|
|
print('MULTISTREAM BROWSER PASSED',flush=True)
|
|
asyncio.run(main())
|