75 lines
3.1 KiB
JavaScript
75 lines
3.1 KiB
JavaScript
/**
|
||
* 🤖 300 个 bottts 机器人 → 白底方形 PNG(1024×1024)
|
||
* 与 preview-300.html 的 seed 序列一一对应(共享 bot-common 逻辑,过滤负面表情),
|
||
* 白底 + bottts 居中放大,Chrome headless 并发截图。
|
||
*
|
||
* 用法:node scripts/gen-300-bot-png.js
|
||
*/
|
||
'use strict';
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
const vm = require('vm');
|
||
const { spawn } = require('child_process');
|
||
const { generateSeeds } = require('./bot-common');
|
||
|
||
const ROOT = path.resolve(__dirname, '..');
|
||
const OUT_DIR = path.join(ROOT, 'output', 'agent-avatars-png', 'bot-png');
|
||
const TMP_DIR = path.join(ROOT, 'output', '.tmp-bot');
|
||
const CHROME = 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe';
|
||
const SIZE = 1024;
|
||
const COUNT = 300;
|
||
|
||
// —— 1. 加载 bottts ——
|
||
global.window = global;
|
||
vm.runInThisContext(fs.readFileSync(path.join(ROOT, 'js', 'data', 'sources', 'dicebear-bottts.js'), 'utf8'), { filename: 'dicebear-bottts.js' });
|
||
vm.runInThisContext(fs.readFileSync(path.join(ROOT, 'js', 'core', 'dicebear.js'), 'utf8'), { filename: 'dicebear.js' });
|
||
|
||
// —— 2. 生成 300 个 seed(与预览页一致)——
|
||
const cards = generateSeeds(seed => HGB_Dicebear.renderAvatar('bottts', seed), COUNT);
|
||
|
||
// —— 3. 生成 HTML(白底 + bottts 居中,SVG 放大到 880)——
|
||
fs.mkdirSync(TMP_DIR, { recursive: true });
|
||
const htmlFiles = [];
|
||
for (let i = 0; i < cards.length; i++) {
|
||
const { svg } = cards[i];
|
||
const html = `<!DOCTYPE html><html><head><meta charset="utf-8">
|
||
<style>html,body{margin:0;padding:0;width:${SIZE}px;height:${SIZE}px;overflow:hidden;background:#ffffff}
|
||
.wrap{width:${SIZE}px;height:${SIZE}px;display:flex;align-items:center;justify-content:center}</style>
|
||
</head><body><div class="wrap">${svg.replace('<svg ', '<svg width="880" height="880" ')}</div></body></html>`;
|
||
const f = path.join(TMP_DIR, `a${i + 1}.html`);
|
||
fs.writeFileSync(f, html, 'utf8');
|
||
htmlFiles.push(f);
|
||
}
|
||
|
||
// —— 4. 并发截图(8 进程)——
|
||
function shot(file, out) {
|
||
return new Promise((res) => {
|
||
const p = spawn(CHROME, [
|
||
'--headless=new', '--disable-gpu', '--hide-scrollbars',
|
||
'--force-device-scale-factor=1',
|
||
'--window-size=' + SIZE + ',' + SIZE, '--virtual-time-budget=800',
|
||
'--screenshot=' + out,
|
||
'file:///' + file.replace(/\\/g, '/')
|
||
], { stdio: 'ignore' });
|
||
p.on('close', () => res());
|
||
p.on('error', () => res());
|
||
});
|
||
}
|
||
|
||
(async () => {
|
||
fs.mkdirSync(OUT_DIR, { recursive: true });
|
||
const jobs = htmlFiles.map((f, i) => ({
|
||
f, out: path.join(OUT_DIR, 'agent-' + String(i + 1).padStart(2, '0') + '.png')
|
||
}));
|
||
let done = 0, t0 = Date.now();
|
||
const CONC = 8;
|
||
for (let s = 0; s < jobs.length; s += CONC) {
|
||
const batch = jobs.slice(s, s + CONC);
|
||
await Promise.all(batch.map(j => shot(j.f, j.out)));
|
||
done += batch.length;
|
||
console.log(`⏳ ${done}/${jobs.length} ${((Date.now() - t0) / 1000).toFixed(1)}s`);
|
||
}
|
||
fs.rmSync(TMP_DIR, { recursive: true, force: true });
|
||
console.log('🎉 完成 → output/agent-avatars-png/bot-png/ (' + jobs.length + ' 张,白底 1024×1024)');
|
||
})();
|