microduck-reachy-simulator / app /record-sim.mjs
FormaLau's picture
Synchronize Reachy speech in recordings
da8640b
Raw History Blame
3.08 kB
import { chromium } from '@playwright/test';
import fs from 'node:fs';
const browser = await chromium.launch({ channel: 'msedge', headless: true, args: ['--enable-unsafe-swiftshader'] });
const context = await browser.newContext({ viewport: { width: 1440, height: 900 } });
const page = await context.newPage();
await page.goto('http://127.0.0.1:5175/?boot=1');
await page.waitForFunction(() => window.__store?.getState().bootDone);
await page.evaluate(() => window.__gameApi.startEntrance());
await page.waitForFunction(() => window.rl && !window.rl.inputLocked);
await page.waitForFunction(() => window.__audioCaptureStream?.active);
const script = JSON.parse(fs.readFileSync('public/scripts/reachy-microduck-conversation.json', 'utf8'));
await page.evaluate(() => {
const canvas = document.querySelector('canvas');
const videoStream = canvas.captureStream(30);
const tracks = [...videoStream.getVideoTracks(), ...window.__audioCaptureStream.getAudioTracks()];
const recorder = new MediaRecorder(new MediaStream(tracks), { mimeType: 'video/webm;codecs=vp8,opus' });
window.__filmChunks = [];
recorder.ondataavailable = (event) => { if (event.data.size) window.__filmChunks.push(event.data); };
recorder.start(250);
window.__filmRecorder = recorder;
});
await page.evaluate((s) => {
window.rl.chaseCam = false;
window.__speechMarks = [];
window.__filmStart = performance.now();
window.addEventListener('reachy-speech-start', (event) => window.__speechMarks.push({ text: event.detail?.text || '', at: (performance.now() - window.__filmStart) / 1000 }));
window.__gameApi.script.load(s);
window.__gameApi.script.run();
}, script);
await page.evaluate(async () => {
const start = performance.now();
await new Promise((resolve) => {
const frame = (now) => {
const t = (now - start) / 1000;
// One close, continuous orbit keeps both robots large in frame and
// avoids the hard viewpoint jumps between emotion sections.
const a = t * 0.28;
const x = Math.sin(a) * 1.55;
const z = Math.cos(a) * 1.55;
const y = 0.72 + Math.sin(t * 0.22) * 0.04;
window.rl.camera.position.set(x, y, z);
window.rl.controls.target.set(0, 0.23, 0);
window.rl.controls.update();
if (t < 55) requestAnimationFrame(frame); else resolve();
};
requestAnimationFrame(frame);
});
});
await page.waitForTimeout(1200);
const capture = await page.evaluate(() => new Promise((resolve) => {
const recorder = window.__filmRecorder;
recorder.onstop = () => {
const blob = new Blob(window.__filmChunks, { type: 'video/webm' });
const reader = new FileReader();
reader.onload = () => resolve({ b64: reader.result.split(',')[1], marks: window.__speechMarks });
reader.readAsDataURL(blob);
};
recorder.stop();
}));
fs.writeFileSync('reachy-microduck-synced.webm', Buffer.from(capture.b64, 'base64'));
fs.writeFileSync('reachy-microduck-speech-marks.json', JSON.stringify(capture.marks, null, 2));
await context.close();
await browser.close();
console.log('reachy-microduck-synced.webm');