// 把两张图的差异降采样成 ASCII 热力图,好在纯文本里看出"差异落在画面哪里"。 // 用法:node tools/heatmap.mjs [cols] [rows] import { spawn } from "node:child_process"; const [a, b, cols = "48", rows = "20"] = process.argv.slice(2); if (!a || !b) { console.error("usage: node tools/heatmap.mjs [cols] [rows]"); process.exit(2); } const W = Number(cols); const H = Number(rows); const RAMP = " .:-=+*#%@"; const buf = await new Promise((resolve) => { const p = spawn( "ffmpeg", [ "-hide_banner", "-loglevel", "error", "-i", a, "-i", b, "-lavfi", `blend=all_mode=difference,format=gray,scale=${W}:${H}:flags=area`, "-f", "rawvideo", "-pix_fmt", "gray", "-", ], { stdio: ["ignore", "pipe", "pipe"] }, ); const chunks = []; p.stdout.on("data", (d) => chunks.push(d)); p.on("close", () => resolve(Buffer.concat(chunks))); }); if (buf.length < W * H) { console.error(`ffmpeg 只返回 ${buf.length} 字节,期望 ${W * H}`); process.exit(1); } console.log(`${a} vs ${b} (${W}x${H} 降采样,越亮=差异越大)`); console.log(" +" + "-".repeat(W) + "+"); for (let y = 0; y < H; y++) { let line = ""; for (let x = 0; x < W; x++) { const v = buf[y * W + x]; line += RAMP[Math.min(RAMP.length - 1, Math.round((v / 255) * (RAMP.length - 1) * 2))]; } console.log(String(y).padStart(3) + " |" + line + "|"); } console.log(" +" + "-".repeat(W) + "+");