fix(film): capture resilience + scene fixes; finalize 20-clip durations

Orchestrator: bind ctx.spaNavigate to the page (fixes breadth-flash/role-perspective doing nothing) and wrap each clip in try/catch so one bad scene can't abort the run or lose the manifest. Cursor: guard page.evaluate against navigation-mid-move (fixes storm-intel 'execution context destroyed'). territory-map now leads with an existing Hot-Lead parcel (full populated detail); crane-close shows the full crane (mast+cable) then pans to the swinging card. Manifest holds real ffprobe durations for all 20 clips (film = 14m56s).
This commit is contained in:
Satyam Rastogi
2026-05-31 00:07:44 +05:30
parent f76a9da7b4
commit ba52200d08
5 changed files with 380 additions and 210 deletions
@@ -34,6 +34,7 @@ async function main() {
// Order: public scenes first (no login), then group by role to log in once each.
const order = [...clips].sort((a, b) => roleRank(a.role) - roleRank(b.role));
const failures = [];
for (const clip of order) {
const scene = await loadScene(clip.id);
if (!scene) {
@@ -42,51 +43,62 @@ async function main() {
}
// Fresh per-clip context so each recording is its own .webm.
const tmpVideoDir = join(outDir, `_rec_${clip.id}`);
await rm(tmpVideoDir, { recursive: true, force: true });
const context = await browser.newContext({
viewport: VIEWPORT,
deviceScaleFactor: 1, // recordVideo records CSS px; keep 1 for exact 1080p
recordVideo: { dir: tmpVideoDir, size: VIEWPORT },
});
const tCtx = Date.now(); // recording starts at context creation; measure the lead-in
await context.addInitScript("try{localStorage.setItem('theme','dark')}catch(e){}");
await context.addInitScript(CURSOR_INIT_SCRIPT);
const page = await context.newPage();
const cursor = makeCursor(page);
let context = null;
try {
await rm(tmpVideoDir, { recursive: true, force: true });
context = await browser.newContext({
viewport: VIEWPORT,
deviceScaleFactor: 1, // recordVideo records CSS px; keep 1 for exact 1080p
recordVideo: { dir: tmpVideoDir, size: VIEWPORT },
});
const tCtx = Date.now(); // recording starts at context creation; measure the lead-in
await context.addInitScript("try{localStorage.setItem('theme','dark')}catch(e){}");
await context.addInitScript(CURSOR_INIT_SCRIPT);
const page = await context.newPage();
const cursor = makeCursor(page);
if (clip.role !== "public") {
await login(page, BASE_URL, clip.role);
await spaNavigate(page, clip.route);
} else {
await page.goto(`${BASE_URL}${clip.route}`, { waitUntil: "networkidle" });
if (clip.role !== "public") {
await login(page, BASE_URL, clip.role);
await spaNavigate(page, clip.route);
} else {
await page.goto(`${BASE_URL}${clip.route}`, { waitUntil: "networkidle" });
}
await page.waitForTimeout(800);
const leadInSec = Math.max(0, (Date.now() - tCtx) / 1000 - 0.3); // trim login+nav, keep 0.3s pad
console.log(`▶ recording ${clip.id} (${clip.role}) ... (trimming ${leadInSec.toFixed(1)}s lead-in)`);
await scene(page, cursor, {
baseUrl: BASE_URL,
spaNavigate: (route) => spaNavigate(page, route),
login: (role) => login(page, BASE_URL, role),
role: clip.role,
waitSettle: (ms = 1500) => page.waitForTimeout(ms),
});
await context.close(); // flush the .webm
context = null;
const webm = await newestWebm(tmpVideoDir);
const mp4 = join(outDir, `${clip.id}.mp4`);
await webmToMp4(webm, mp4, FPS, leadInSec);
await rm(tmpVideoDir, { recursive: true, force: true });
const seconds = await probeSeconds(mp4);
clip.durationFrames = secondsToFrames(seconds, FPS);
console.log(`${clip.id}${mp4} (${seconds.toFixed(2)}s = ${clip.durationFrames}f)`);
} catch (e) {
// One bad scene must not abort the whole run or lose the manifest.
failures.push({ id: clip.id, error: e.message });
console.error(`${clip.id} FAILED: ${e.message}`);
try { if (context) await context.close(); } catch {}
await rm(tmpVideoDir, { recursive: true, force: true }).catch(() => {});
}
await page.waitForTimeout(800);
const leadInSec = Math.max(0, (Date.now() - tCtx) / 1000 - 0.3); // trim login+nav, keep 0.3s pad
console.log(`▶ recording ${clip.id} (${clip.role}) ... (trimming ${leadInSec.toFixed(1)}s lead-in)`);
await scene(page, cursor, {
baseUrl: BASE_URL,
spaNavigate,
login: (role) => login(page, BASE_URL, role),
role: clip.role,
waitSettle: (ms = 1500) => page.waitForTimeout(ms),
});
await context.close(); // flush the .webm
const webm = await newestWebm(tmpVideoDir);
const mp4 = join(outDir, `${clip.id}.mp4`);
await webmToMp4(webm, mp4, FPS, leadInSec);
await rm(tmpVideoDir, { recursive: true, force: true });
const seconds = await probeSeconds(mp4);
clip.durationFrames = secondsToFrames(seconds, FPS);
console.log(`${clip.id}${mp4} (${seconds.toFixed(2)}s = ${clip.durationFrames}f)`);
}
await browser.close();
// Persist updated durations.
// Persist updated durations (for every clip that succeeded).
await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
console.log(`\nDone. Captured ${clips.length} clip(s); manifest durations updated.`);
console.log(`\nDone. ${clips.length - failures.length}/${clips.length} clip(s) captured; manifest updated.`);
if (failures.length) console.log(`Failed: ${failures.map((f) => f.id).join(", ")}`);
}
function roleRank(role) {