| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- /**
- * aigc_report.mjs — AIGC 分镜拉片报告生成器(JSS-03)
- * 读取 aigc_remake.json + aigc_prompt.md → 产出 aigc_report.html(暗色/手机自适应/相对路径)
- * 纯 ESM, 零依赖。CLI: node aigc_report.mjs workdir
- */
- import fs from "node:fs";
- const esc = (s) => String(s ?? "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
- const fmtTs = (ms) => { const s = Math.floor(ms / 1000); return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`; };
- function scoreBars(dims) {
- return dims.map((d) => `
- <div class="dim"><div class="dim-head"><span>${esc(d.name)}</span><span>${d.score}</span></div>
- <div class="bar"><i style="width:${d.score}%"></i></div><div class="note">${esc(d.note)}</div></div>`).join("");
- }
- function shotRows(board) {
- return board.map((s) => {
- const img = s.file ? `frames/${s.file}` : "";
- return `<details class="shot"><summary>
- <span class="idx">#${s.i + 1}</span><span class="t">${esc(s.t_label)}</span>
- <span class="dur">${s.dur}s</span><span class="role">${esc(s.role.split("(")[0])}</span></summary>
- <div class="shot-body">
- ${img ? `<img loading="lazy" src="${img}" alt="shot ${s.i}">` : ""}
- <p><b>画面:</b> ${esc(s.caption)}</p>
- ${s.lyric ? `<p><b>音频:</b> ${esc(s.lyric)}</p>` : ""}
- ${s.vision?.char ? `<p><b>主体:</b> ${esc(s.vision.char)}</p>` : ""}
- ${s.vision?.shot ? `<p><b>运镜:</b> ${esc(s.vision.shot)}</p>` : ""}
- ${s.vision?.style ? `<p><b>风格:</b> ${esc(s.vision.style)}</p>` : ""}
- ${s.vision?.aigc ? `<p class="aigc"><b>AI痕迹:</b> ${esc(s.vision.aigc)}</p>` : ""}
- </div></details>`;
- }).join("");
- }
- export function render(remake, promptMd) {
- const { video, type, structure, registry, storyboard, score, voc } = remake;
- const st = video.stats || {};
- return `<!DOCTYPE html><html lang="zh-CN" data-theme="dark"><head><meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>AIGC分镜拉片:${esc((video.desc || "").split("#")[0].trim())}</title><style>
- :root{--bg:#0a0e17;--surface:#141a28;--line:#232c42;--tx:#dbe4f5;--dim:#8b98b8;--acc:#7c6cff;--gold:#ffb84d;--green:#3ddc97;--red:#ff6b7a}
- *{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--tx);font:15px/1.7 -apple-system,"PingFang SC","Microsoft YaHei",sans-serif}
- .wrap{max-width:860px;margin:0 auto;padding:16px}
- h1{font-size:20px}h2{font-size:17px;border-left:3px solid var(--acc);padding-left:10px;margin-top:32px}
- .card{background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:14px;margin:10px 0}
- .stats{display:flex;flex-wrap:wrap;gap:8px}.stat{flex:1;min-width:90px;text-align:center;background:var(--surface);border:1px solid var(--line);border-radius:10px;padding:10px 4px}
- .stat b{display:block;font-size:18px;color:var(--gold)}.stat span{font-size:12px;color:var(--dim)}
- .badge{display:inline-block;background:var(--acc);color:#fff;border-radius:20px;padding:2px 12px;font-size:13px;margin-right:6px}
- video{width:100%;border-radius:12px;margin:10px 0;background:#000}
- details.shot{background:var(--surface);border:1px solid var(--line);border-radius:10px;margin:6px 0;overflow:hidden}
- details.shot summary{cursor:pointer;padding:9px 12px;display:flex;gap:10px;align-items:center;flex-wrap:wrap;list-style:none}
- details.shot summary::-webkit-details-marker{display:none}
- .idx{color:var(--acc);font-weight:700;min-width:36px}.t{color:var(--dim);font-size:13px}.dur{color:var(--gold);font-size:13px}
- .role{font-size:12px;color:var(--dim)}
- .shot-body{padding:4px 14px 14px}.shot-body img{width:100%;border-radius:8px;margin:6px 0}
- .shot-body p{margin:4px 0;font-size:14px}.aigc{color:var(--red)}
- .bar{height:8px;background:var(--line);border-radius:4px;overflow:hidden}.bar i{display:block;height:100%;background:linear-gradient(90deg,var(--acc),var(--gold))}
- .dim{margin:10px 0}.dim-head{display:flex;justify-content:space-between;font-size:14px}.note{font-size:12px;color:var(--dim)}
- table{width:100%;border-collapse:collapse;font-size:13px}th,td{border:1px solid var(--line);padding:6px 8px;text-align:left}th{color:var(--gold);font-weight:600}
- pre{white-space:pre-wrap;word-break:break-word;background:#0d1220;border:1px solid var(--line);border-radius:10px;padding:14px;font-size:13px;max-height:420px;overflow:auto}
- a{color:var(--acc)}.sec{font-size:13px;color:var(--dim)}
- footer{text-align:center;color:var(--dim);font-size:12px;padding:24px 0}
- </style></head><body><div class="wrap">
- <h1>🕷️ AIGC 分镜拉片报告</h1>
- <div class="sec">${esc(video.author)} · <a href="${esc(video.url)}">原片</a> · ${fmtTs(video.duration_s * 1000)}</div>
- <div style="margin:10px 0">
- <span class="badge">${esc(type.primaryLabel || "AIGC内容")}</span>
- <span class="badge" style="background:var(--gold);color:#333">置信度 ${type.confidence ?? "?"}</span>
- <span class="badge" style="background:#2a3350">复刻模式: 分镜仿制</span>
- </div>
- <video controls preload="metadata" src="video.mp4"></video>
- <div class="stats">
- <div class="stat"><b>${(st.digg_count ?? 0).toLocaleString()}</b><span>👍 点赞</span></div>
- <div class="stat"><b>${(st.comment_count ?? 0).toLocaleString()}</b><span>💬 评论</span></div>
- <div class="stat"><b>${(st.collect_count ?? 0).toLocaleString()}</b><span>🔖 收藏</span></div>
- <div class="stat"><b>${(st.share_count ?? 0).toLocaleString()}</b><span>↗ 转发</span></div>
- <div class="stat"><b>${score.total}</b><span>综合评分</span></div>
- </div>
- <h2>🧬 复刻资产注册表</h2>
- <div class="card"><b>人物:</b> ${(registry.characters || []).map(esc).join("、") || "—"}<br>
- <b>场景:</b> ${(registry.scenes || []).map(esc).join("、") || "—"}<br>
- <b>风格:</b> ${(registry.styles || []).map(esc).join("、") || "—"}<br>
- <b>节奏:</b> ${structure.shot_count} 镜 / 平均 ${structure.avg_shot_s}s / ${(structure.shot_count / (video.duration_s / 60)).toFixed(0)} 镜每分钟</div>
- <h2>🎬 片体结构</h2>
- <table><tr><th>段落</th><th>时间</th><th>类型</th><th>内容</th></tr>
- ${(structure.sections || []).map((s) => `<tr><td>${esc(s.name)}</td><td class="t">${s.start}s–${s.end}s</td><td>${esc(s.type || "")}</td><td>${esc(s.content || s.lyric || "")}</td></tr>`).join("")}
- </table>
- <h2>🎞️ 分镜表(${storyboard.length} 镜)</h2>
- ${shotRows(storyboard)}
- <h2>📊 六维评分</h2>
- <div class="card">${scoreBars(score.dims)}</div>
- <h2>🧾 仿爆款长提示词</h2>
- <div class="sec">完整版: <a href="aigc_prompt.md" download>aigc_prompt.md</a> — 直接投喂给 AIGC 创作智能体, 替换占位符即可逐镜复刻</div>
- <pre>${esc(promptMd)}</pre>
- <h2>💬 评论区 VOC</h2>
- <div class="card">${(voc?.top || []).map((c) => `<div style="border-bottom:1px solid var(--line);padding:6px 0"><b style="color:var(--gold)">👍${c.likes}</b> ${esc(c.text)}</div>`).join("") || "—"}
- <div class="sec" style="margin-top:6px">共采集 ${voc?.total || 0} 条</div></div>
- <h2>🚀 改进与复刻策略</h2>
- <div class="card">${(remake.suggestions || []).map((s) => `<p>▸ ${esc(s)}</p>`).join("")}</div>
- <footer>skill-video-lapian · JSS-01/02/03 · ${new Date().toISOString().slice(0, 10)}</footer>
- </div></body></html>`;
- }
- if (import.meta.url === `file://${process.argv[1]}`) {
- const wd = process.argv[2] || ".";
- const remake = JSON.parse(fs.readFileSync(`${wd}/aigc_remake.json`, "utf8"));
- const promptMd = fs.existsSync(`${wd}/aigc_prompt.md`) ? fs.readFileSync(`${wd}/aigc_prompt.md`, "utf8") : "(未生成)";
- fs.writeFileSync(`${wd}/aigc_report.html`, render(remake, promptMd));
- console.log(`→ ${wd}/aigc_report.html (${(fs.statSync(`${wd}/aigc_report.html`).size / 1024).toFixed(0)} KB)`);
- }
|