// 抓图:视频阶段「合成完整视频 / 播放成片 / 下载成片」+ 项目列表播放键播成片 import { chromium } from "playwright"; import { mkdirSync } from "node:fs"; const BASE = process.env.BASE || "http://127.0.0.1:5173"; const API = process.env.API || "http://127.0.0.1:8010"; const OUT = process.argv[2] || "shots-merge"; mkdirSync(OUT, { recursive: true }); const login = await (await fetch(`${API}/api/auth/login/`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: "airshelf", password: "Restraint2026" }) })).json(); const tok = login.token; const auth = { Authorization: `Token ${tok}`, "Content-Type": "application/json" }; // 挑一个「所有场次都已出片」的项目:合成按钮才是可点态 const projects = (await (await fetch(`${API}/api/projects/?page_size=100`, { headers: auth })).json()).results || []; console.log("项目数:", projects.length, "· 已有成片:", projects.filter((p) => p.final_video_url).length); let target = null; for (const p of projects.filter((p) => p.status === "completed" || p.current_stage === "video")) { const detail = await (await fetch(`${API}/api/projects/${p.id}/`, { headers: auth })).json(); const segs = detail.video_segments || []; if (segs.length && segs.every((s) => s.status === "succeeded" && s.adopted_asset)) { target = detail; break; } } if (!target) throw new Error("没有全部场次出片的项目,无法抓合成态"); console.log("target:", target.id, target.name, "· 场次:", target.video_segments.length, "· 成片:", target.final_video_url || "(未合成)"); const browser = await chromium.launch(); const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 2 }); await ctx.addInitScript((t) => localStorage.setItem("airshelf_token", t), tok); const page = await ctx.newPage(); page.on("pageerror", (e) => console.error(" pageerror:", e.message)); await page.goto(`${BASE}/pipeline/${target.id}#stage-4`, { waitUntil: "networkidle", timeout: 40000 }); await page.waitForTimeout(2500); const foot = page.locator('[data-stage-pane="4"] .stage-foot'); await foot.waitFor({ timeout: 15000 }); await page.screenshot({ path: `${OUT}/1-stage4-merge-bar.png`, clip: await foot.boundingBox() }); console.log("shot 1-stage4-merge-bar"); // 点「播放成片」→ 灯箱播成片 const play = page.locator('[data-stage-pane="4"] .stage-foot button', { hasText: "播放成片" }); if (await play.count()) { await play.click(); await page.waitForTimeout(2500); await page.screenshot({ path: `${OUT}/2-stage4-play-final.png` }); console.log("shot 2-stage4-play-final"); await page.keyboard.press("Escape"); } await page.goto(`${BASE}/projects`, { waitUntil: "networkidle", timeout: 40000 }); await page.waitForTimeout(1500); await page.screenshot({ path: `${OUT}/3-projects-list.png`, fullPage: false }); console.log("shot 3-projects-list"); // 列表行末播放键:有成片 → 直接弹窗播,不再跳流水线 const row = page.locator(`tr[data-name="${target.name}"] .row-action a`).first(); if (await row.count()) { await row.click(); await page.waitForTimeout(2500); await page.screenshot({ path: `${OUT}/4-projects-play-final.png` }); console.log("shot 4-projects-play-final · 当前地址:", page.url()); } await browser.close(); console.log("DONE ->", OUT);