Files
ElyOffice/site/video/scenes/verify.mjs
T

69 lines
3.0 KiB
JavaScript

// Checks the cut into the exit: same screens, their own recordings.
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { chromium } from "playwright";
import { createServer } from "vite";
import recs from "../montage/recs.json" with { type: "json" };
import { moment } from "./timeline.ts";
import { recording } from "./turn.ts";
const here = dirname(fileURLToPath(import.meta.url));
const server = await createServer({ root: resolve(here, "../.."), configFile: false, logLevel: "error", server: { port: 0 } });
await server.listen();
const base = server.resolvedUrls.local[0];
const browser = await chromium.launch();
/** A film-sized page whose recordings are any one frame. */
async function open(path) {
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
page.on("pageerror", (e) => {
throw e;
});
await page.route("**/rec/**", (route) => route.fulfill({ path: resolve(here, "../../public/film/poster.jpg") }));
await page.goto(`${base}${path}`, { waitUntil: "networkidle" });
return page;
}
/** Each drawn recording: its source and three screen corners. */
const drawn = (page) =>
page.$$eval("#world image", (els) =>
els.map((el) => {
const m = el.getScreenCTM();
const at = (x, y) => [m.a * x + m.c * y + m.e, m.b * x + m.d * y + m.f];
return { href: el.getAttribute("href"), corners: [at(0, 0), at(1280, 0), at(0, 720)] };
}),
);
try {
const cut = recs.first + 129;
const montage = await open("video/montage/index.html");
await montage.waitForFunction(() => window.montage);
const { pins } = await montage.evaluate((f) => window.montage.render(f), cut - 1);
const exit = await open("video/scenes/index.html");
// Every exit frame until the last screen has gone.
for (let f = cut; ; f++) {
await exit.evaluate((f) => window.ely.render("files", f), f);
const shown = await drawn(exit);
const want = moment(f / 30).pieces.map(({ box, land }) => recording(box, land, f)).filter(Boolean);
const got = shown.map((s) => s.href);
if (JSON.stringify([...got].sort()) !== JSON.stringify([...want].sort())) {
throw new Error(`frame ${f}: drew ${JSON.stringify(got)}, not ${JSON.stringify(want)}`);
}
if (f === cut) {
if (got.length !== 6) throw new Error(`the cut shows ${got.length} screens`);
// The montage pins top-left, top-right, bottom-right, bottom-left.
for (const { href, corners } of shown) {
const app = recs.apps.find((a) => href.startsWith(`rec/${a.rec}/`)).app;
const pin = pins[app];
const near = [pin[0], pin[1], pin[3]].every((p, i) => Math.hypot(p[0] - corners[i][0], p[1] - corners[i][1]) < 1);
if (!near) throw new Error(`${app} moves at the cut: ${JSON.stringify(pin)} to ${JSON.stringify(corners)}`);
}
}
if (!want.length) break;
}
console.log("verify: screens hold still across the cut and play their own frames");
} finally {
await browser.close();
await server.close();
}